@muten/shadcn 0.1.5 → 0.1.7
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/kit.css +16 -8
- package/package.json +1 -1
- package/registry/agenda.js +5 -2
- package/registry/agenda.muten +4 -2
- package/registry/kit/core.js +42 -12
package/kit.css
CHANGED
|
@@ -1537,10 +1537,11 @@ p.cx-text { margin: 0; }
|
|
|
1537
1537
|
|
|
1538
1538
|
/* ═════ Calendar ═════ */
|
|
1539
1539
|
.cx-cal { position: relative; display: inline-flex; flex-direction: column; gap: 8px; padding: 12px; border-radius: calc(var(--radius) * 1.2); box-shadow: 0 0 0 1px var(--border); background: var(--background); }
|
|
1540
|
-
|
|
1541
|
-
.cx-cal-
|
|
1540
|
+
/* the title row: ‹ month › (with two months, ‹ beside the first and › beside the last) */
|
|
1541
|
+
.cx-cal-head { display: grid; grid-template-columns: 28px 1fr 28px; align-items: center; gap: 4px; margin-bottom: 4px; }
|
|
1542
|
+
.cx-cal-head > .cn-button { width: 28px; height: 28px; }
|
|
1542
1543
|
.cx-cal-months { display: flex; gap: 24px; flex-wrap: wrap; }
|
|
1543
|
-
.cx-cal-title { height: 28px; display: grid; place-items: center;
|
|
1544
|
+
.cx-cal-title { height: 28px; display: grid; place-items: center; }
|
|
1544
1545
|
.cx-cal-grid { display: grid; grid-template-columns: repeat(7, 32px); row-gap: 2px; }
|
|
1545
1546
|
.cx-cal-wd { height: 28px; display: grid; place-items: center; }
|
|
1546
1547
|
.cx-cal-day { font-variant-numeric: tabular-nums; font-weight: 400; position: relative; }
|
|
@@ -1554,7 +1555,7 @@ p.cx-text { margin: 0; }
|
|
|
1554
1555
|
.cx-range-presets .cn-button { width: 9.5rem; }
|
|
1555
1556
|
|
|
1556
1557
|
/* ═════ Agenda ═════ */
|
|
1557
|
-
.cx-agenda { display: flex; flex-direction: column; gap: 12px; width: 100%; max-width:
|
|
1558
|
+
.cx-agenda { display: flex; flex-direction: column; gap: 12px; width: 100%; max-width: var(--agenda-max, none); margin: 0 auto; }
|
|
1558
1559
|
.cx-ag-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; }
|
|
1559
1560
|
.cx-ag-scroll { max-height: 520px; overflow: auto; }
|
|
1560
1561
|
.cx-ag { position: relative; display: grid; grid-template-rows: 36px 1fr; min-width: calc(56px + var(--cols) * 140px); }
|
|
@@ -1800,7 +1801,7 @@ p.cx-text { margin: 0; }
|
|
|
1800
1801
|
.cx-ag-row[data-kind="free"]:hover { border-color: var(--selected); color: var(--selected); }
|
|
1801
1802
|
.cx-ag-row[data-kind="block"] { --tone: var(--muted-foreground); cursor: default; background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--foreground) 5%, transparent) 0 5px, transparent 5px 10px); }
|
|
1802
1803
|
.cx-rcpt-mini { display: block; width: 120px; aspect-ratio: 3 / 4; border-radius: 8px; box-shadow: inset 0 0 0 1px var(--border); }
|
|
1803
|
-
@container (max-width: 700px) { .cx-ag-bar .cx-text[data-variant="heading"] { flex-basis: 100%; order: 9; } }
|
|
1804
|
+
@container (max-width: 700px) { .cx-ag-bar > .cx-text[data-variant="heading"] { flex-basis: 100%; order: 9; } }
|
|
1804
1805
|
|
|
1805
1806
|
/* time and menu triggers: field-like buttons, icon and chevron with real padding */
|
|
1806
1807
|
.cx-time-btn { gap: 6px; padding-inline: 10px 12px; font-variant-numeric: tabular-nums; }
|
|
@@ -1963,6 +1964,12 @@ p.cx-text { margin: 0; }
|
|
|
1963
1964
|
.cx-ag-listwrap { padding: 12px; }
|
|
1964
1965
|
.cx-ag-head { background: var(--card, var(--background)); }
|
|
1965
1966
|
.cx-ag-off[data-all] { display: block; text-align: center; padding-top: 14px; }
|
|
1967
|
+
/* a closed day: the whole column hatched, «Cerrado» in the middle where the eye lands (no veil on top, past or not) */
|
|
1968
|
+
.cx-ag-shut { display: grid; place-items: center; }
|
|
1969
|
+
.cx-ag-shutpill { position: sticky; top: 40%; display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 99px; background: var(--card, var(--background)); box-shadow: 0 0 0 1px var(--border), 0 1px 2px color-mix(in oklab, var(--foreground) 6%, transparent); color: var(--muted-foreground); }
|
|
1970
|
+
.cx-ag-shutlist { display: grid; place-items: center; padding: 40px 12px; }
|
|
1971
|
+
.cx-ag-shutpill svg { width: 14px; height: 14px; }
|
|
1972
|
+
.cx-ag-shutpill .cx-text { color: inherit; font-weight: 500; }
|
|
1966
1973
|
.cx-ag-off[data-all] > .cx-text { display: inline-block; padding: 2px 10px; border-radius: 99px; background: var(--card, var(--background)); box-shadow: 0 0 0 1px var(--border); }
|
|
1967
1974
|
.cx-ag-ev { touch-action: pan-y; user-select: none; -webkit-user-select: none; cursor: grab; }
|
|
1968
1975
|
.cx-ag-ev[data-dragging] { opacity: .35; box-shadow: none; }
|
|
@@ -4792,8 +4799,9 @@ a.cx-bc-item:hover { color: var(--foreground); }
|
|
|
4792
4799
|
.cx-ag-count { display: flex; align-items: center; justify-content: center; height: 12px; font-size: 11px; font-weight: 500; line-height: 1; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
|
|
4793
4800
|
.cx-ag-day[aria-pressed="true"] .cx-ag-count { color: inherit; opacity: .8; }
|
|
4794
4801
|
.cx-ag-count .skel { width: 10px; height: 8px; }
|
|
4795
|
-
|
|
4796
|
-
.cx-ag-day[data-closed]
|
|
4802
|
+
/* a closed day in the strip: the grid's hatch and quieter ink, never struck through (it reads as an error) */
|
|
4803
|
+
.cx-ag-day[data-closed]:not([aria-pressed="true"]) { color: var(--muted-foreground); background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--muted) 90%, transparent) 0 5px, transparent 5px 10px); }
|
|
4804
|
+
.cx-ag-day[data-closed]:not([aria-pressed="true"]) b { font-weight: 500; }
|
|
4797
4805
|
.cx-ag-colhead[data-closed] { color: var(--muted-foreground); }
|
|
4798
4806
|
/* «Ahora»: the cita in progress, first thing on today: a plain row on the muted fill, no colour, no motion */
|
|
4799
4807
|
.cx-ag-nowcard { display: flex; align-items: center; gap: 12px; width: 100%; padding: 12px 14px; border: 1px solid var(--border); border-radius: calc(var(--radius) * 1.1); background: var(--muted); color: var(--foreground); text-align: left; cursor: pointer; font: inherit; }
|
|
@@ -4843,7 +4851,7 @@ a.cx-bc-item:hover { color: var(--foreground); }
|
|
|
4843
4851
|
.cx-ag-datefoot { display: flex; justify-content: flex-end; }
|
|
4844
4852
|
.cx-ag-datesheet { display: flex; flex-direction: column; gap: 12px; align-items: stretch; }
|
|
4845
4853
|
.cx-ag-datesheet .cx-ag-datecal, .cx-ag-pop .cx-ag-datecal { display: flex; justify-content: center; }
|
|
4846
|
-
.cx-cal-pick { display: inline-flex; align-items: center; gap: 4px; margin: 0 auto; padding: 4px 8px; border: 0; border-radius: calc(var(--radius) * .7); background: transparent; color: inherit; font: inherit; cursor: pointer; }
|
|
4854
|
+
.cx-cal-pick { display: inline-flex; align-items: center; justify-self: center; gap: 4px; margin: 0 auto; padding: 4px 8px; border: 0; border-radius: calc(var(--radius) * .7); background: transparent; color: inherit; font: inherit; cursor: pointer; }
|
|
4847
4855
|
.cx-cal-pick:hover { background: color-mix(in oklab, var(--foreground) 6%, transparent); }
|
|
4848
4856
|
.cx-cal-pick svg { width: 14px; height: 14px; opacity: .6; }
|
|
4849
4857
|
.cx-cal-mgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; padding-top: 8px; min-width: 252px; }
|
package/package.json
CHANGED
package/registry/agenda.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
// sends them back through `command`: "new", "block", "prev", "next", "today", "view:list", or an action id. The
|
|
22
22
|
// agenda runs a command when its value changes, so repeat one with a suffix after # ("new#1", "new#2"…)
|
|
23
23
|
// hour12 "yes" - times written 3:00 PM (the data stays "HH:MM") · timeZone "America/Bogota" - today and now are the shop's
|
|
24
|
+
// fit "yes" - the hours share the height the app gives the agenda (give it one: a flex parent), never under 56px
|
|
24
25
|
// phoneStrip "dots" - the phone's week strip marks each day with a dot per cita in its service's colour (default: the count)
|
|
25
26
|
// phoneView "list" - a phone opens on the list until a view is picked · blockScope "all" - blocks are for the whole
|
|
26
27
|
// business (no «Para» picker: a backend with no per-person blocks)
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
// create "sheet" - a showcase: «new» and a tap on a free hour open a short built-in form and the cita lands at once
|
|
34
35
|
// handlers: create(json {resource, day, start}) · open(json event) · block(json {resource, day, start, dur, reason})
|
|
35
36
|
// move(json {id, resource, day, start, dur}) · range(json {from, to, view}: view is day | week | list | range) · change(json [...]) · action(id)
|
|
36
|
-
//
|
|
37
|
+
// ranges "yes" - the month under the title also offers «Varios días» (a range listed day by day); off by default
|
|
38
|
+
// The title opens a month (floating; a sheet on a phone) to jump to a day.
|
|
37
39
|
// LIVE: new inputs repaint the same agenda (same day, view and person): an app sends its data again and it shows;
|
|
38
40
|
// lang, currency, locale and cents change live too.
|
|
39
41
|
export function mount(el, inputs, on) {
|
|
@@ -57,6 +59,7 @@ export function mount(el, inputs, on) {
|
|
|
57
59
|
blocks: list(i.blocks).map((b) => ({ ...b, day: plain(b.day) })),
|
|
58
60
|
queue: list(i.queue).map((q) => ({ ...q, day: plain(q.day) })), closed: list(i.closed).map(plain), loading: yes(i.loading),
|
|
59
61
|
placing: (() => { const g = list(i.placing)[0]; return g && g.start ? { ...g, day: dayOf(g.resource, g.day) } : null; })(),
|
|
62
|
+
from: Number(i.from) || 9, to: Number(i.to) || 19,
|
|
60
63
|
lang: i.lang === 'en' ? 'en' : 'es', overlap: ['never', 'allow'].includes(i.overlap) ? i.overlap : 'auto', currency: i.currency || '', locale: i.locale || '', cents: yes(i.cents),
|
|
61
64
|
};
|
|
62
65
|
};
|
|
@@ -69,7 +72,7 @@ export function mount(el, inputs, on) {
|
|
|
69
72
|
actions: list(inputs.actions).length ? list(inputs.actions) : null,
|
|
70
73
|
from: Number(inputs.from) || 9, to: Number(inputs.to) || 19, slot: Number(inputs.slot) || 30, view: inputs.view || 'day',
|
|
71
74
|
createSheet: inputs.create === 'sheet', services: list(inputs.services),
|
|
72
|
-
sheet: inputs.sheet === 'app' ? 'app' : 'built-in', drag: inputs.drag !== 'off', start: inputs.start ? at(inputs.start) : null, phone: inputs.phone === 'nav' ? 'nav' : '',
|
|
75
|
+
sheet: inputs.sheet === 'app' ? 'app' : 'built-in', drag: inputs.drag !== 'off', start: inputs.start ? at(inputs.start) : null, phone: inputs.phone === 'nav' ? 'nav' : '', fit: yes(inputs.fit), ranges: yes(inputs.ranges),
|
|
73
76
|
resize: inputs.resize === 'off' ? 'off' : 'on', moveAcross: inputs.moveAcross === 'off' ? 'off' : 'on', hour12: yes(inputs.hour12), timeZone: inputs.timeZone || '', phoneStrip: inputs.phoneStrip === 'dots' ? 'dots' : '', phoneView: ['list', 'day'].includes(inputs.phoneView) ? inputs.phoneView : '', blockScope: inputs.blockScope === 'all' ? 'all' : 'each',
|
|
74
77
|
onAction: (id) => send('action', id),
|
|
75
78
|
onCreate: (x) => send('create', day1(x)),
|
package/registry/agenda.muten
CHANGED
|
@@ -24,6 +24,8 @@ part AgendaDemo(resources: list, hours: list, events: list, blocks: list, action
|
|
|
24
24
|
# onOpen(json {id, …}) a cita tapped (open your sheet) · onCreate(json {resource, day, start}) a free hour tapped
|
|
25
25
|
# onBlock(json {resource, day, start, dur, reason}) · onMove(json {id, resource, day, start, dur}) dragged (answer with
|
|
26
26
|
# the data: it stays where it landed, or goes back if you send it unchanged) · onRange(json {from, to, view}) days shown
|
|
27
|
+
# fit "yes" the hours stretch to the height the app gives the agenda (a flex parent that grows), never under 56px
|
|
28
|
+
# ranges "yes" the date under the title also offers «Varios días» (a range, listed day by day); off by default
|
|
27
29
|
# actions [{id, label, icon, primary}] the toolbar's buttons (default: Bloquear and Nueva cita; [{id: "block", …}] alone
|
|
28
30
|
# leaves «Nueva cita» to the app's own button) · an id other than block or new fires onAction(id)
|
|
29
31
|
# phone "nav": on a phone its actions (Bloquear, Nueva cita, yours) and the arrows leave the toolbar for the app's
|
|
@@ -35,6 +37,6 @@ part AgendaDemo(resources: list, hours: list, events: list, blocks: list, action
|
|
|
35
37
|
# (the dashed cita while the app's new-cita sheet is open) · events take tags [words] and photo; resources role, hideEmpty
|
|
36
38
|
# AgendaLive(resources: @team, hours: @hours, events: @citas, blocks: @blocks, free: @free, queue: @queue, closed: @closed,
|
|
37
39
|
# lang: ui.lang, currency: "USD", onOpen: openCita, onCreate: newCita, onBlock: block, onMove: moveCita, onRange: loadDays)
|
|
38
|
-
part AgendaLive(resources: list, hours: list, events: list, blocks: list, actions: list, free: list, queue: list, closed: list, lang: text = "es", currency: text = "", locale: text = "", cents: text = "", from: number = 9, to: number = 19, slot: number = 30, view: text = "day", loading: text = "", start: text = "", drag: text = "", phone: text = "", command: text = "", overlap: text = "", hour12: text = "", timeZone: text = "", phoneView: text = "", phoneStrip: text = "", blockScope: text = "", resize: text = "", moveAcross: text = "", placing: list, onOpen: action, onCreate: action, onBlock: action, onMove: action, onRange: action, onAction: action) {
|
|
39
|
-
Custom Agenda inputs(resources: $resources, hours: $hours, events: $events, blocks: $blocks, actions: $actions, free: $free, queue: $queue, closed: $closed, lang: $lang, currency: $currency, locale: $locale, cents: $cents, from: $from, to: $to, slot: $slot, view: $view, loading: $loading, start: $start, drag: $drag, phone: $phone, command: $command, overlap: $overlap, hour12: $hour12, timeZone: $timeZone, phoneView: $phoneView, phoneStrip: $phoneStrip, blockScope: $blockScope, resize: $resize, moveAcross: $moveAcross, placing: $placing, sheet: "app") on(open: $onOpen, create: $onCreate, block: $onBlock, move: $onMove, range: $onRange, action: $onAction) class("cx-agenda")
|
|
40
|
+
part AgendaLive(resources: list, hours: list, events: list, blocks: list, actions: list, free: list, queue: list, closed: list, lang: text = "es", currency: text = "", locale: text = "", cents: text = "", from: number = 9, to: number = 19, slot: number = 30, view: text = "day", loading: text = "", start: text = "", drag: text = "", phone: text = "", command: text = "", overlap: text = "", hour12: text = "", timeZone: text = "", phoneView: text = "", phoneStrip: text = "", fit: text = "", ranges: text = "", blockScope: text = "", resize: text = "", moveAcross: text = "", placing: list, onOpen: action, onCreate: action, onBlock: action, onMove: action, onRange: action, onAction: action) {
|
|
41
|
+
Custom Agenda inputs(resources: $resources, hours: $hours, events: $events, blocks: $blocks, actions: $actions, free: $free, queue: $queue, closed: $closed, lang: $lang, currency: $currency, locale: $locale, cents: $cents, from: $from, to: $to, slot: $slot, view: $view, loading: $loading, start: $start, drag: $drag, phone: $phone, command: $command, overlap: $overlap, hour12: $hour12, timeZone: $timeZone, phoneView: $phoneView, phoneStrip: $phoneStrip, fit: $fit, ranges: $ranges, blockScope: $blockScope, resize: $resize, moveAcross: $moveAcross, placing: $placing, sheet: "app") on(open: $onOpen, create: $onCreate, block: $onBlock, move: $onMove, range: $onRange, action: $onAction) class("cx-agenda")
|
|
40
42
|
}
|
package/registry/kit/core.js
CHANGED
|
@@ -429,19 +429,21 @@ let wheelsNow = [];
|
|
|
429
429
|
const WD = weekdays || ['lu', 'ma', 'mi', 'ju', 'vi', 'sá', 'do'];
|
|
430
430
|
const inRange = (d) => mode === 'range' && sel?.[0] && (sel[1] || hover) && d > startOfDay(sel[0]) && d < startOfDay(sel[1] || hover) ;
|
|
431
431
|
const isEdge = (d) => mode === 'range' ? (sameDay(d, sel?.[0]) || sameDay(d, sel?.[1])) : sameDay(d, sel);
|
|
432
|
+
// the arrows sit in the title's own row, so they can never land on the month's name
|
|
433
|
+
const navBtn = (dir, icon, label, kind) => ui.button({ icon, variant: 'ghost', size: 'icon-sm', attrs: `data-cal-${kind}="${dir}" aria-label="${label}" title="${label}"` });
|
|
432
434
|
const month = (m) => {
|
|
433
435
|
const first = new Date(view.getFullYear(), view.getMonth() + m, 1), lead = (first.getDay() + 6) % 7, days = new Date(first.getFullYear(), first.getMonth() + 1, 0).getDate();
|
|
434
436
|
const cells = Array.from({ length: 42 }, (_, i) => addDays(first, i - lead)).slice(0, Math.ceil((lead + days) / 7) * 7);
|
|
435
|
-
return `<div class="cx-cal-month" role="grid" aria-label="${mon[first.getMonth()]} ${first.getFullYear()}"><button type="button" class="cx-cal-title cx-cal-pick" data-cal-pick aria-label="${pickLabel}: ${mon[first.getMonth()]} ${first.getFullYear()}">${ui.text(`${mon[first.getMonth()][0].toUpperCase()}${mon[first.getMonth()].slice(1)} ${first.getFullYear()}`, 'heading')}${ui.icon('chev')}</button><div class="cx-cal-grid" role="row">${WD.map((w) => `<span class="cx-cal-wd" role="columnheader">${ui.text(w, 'caption')}</span>`).join('')}</div><div class="cx-cal-grid">${cells.map((d) => {
|
|
437
|
+
return `<div class="cx-cal-month" role="grid" aria-label="${mon[first.getMonth()]} ${first.getFullYear()}"><div class="cx-cal-head">${m === 0 ? navBtn(-1, 'left', prevLabel, 'nav') : '<span></span>'}<button type="button" class="cx-cal-title cx-cal-pick" data-cal-pick aria-label="${pickLabel}: ${mon[first.getMonth()]} ${first.getFullYear()}">${ui.text(`${mon[first.getMonth()][0].toUpperCase()}${mon[first.getMonth()].slice(1)} ${first.getFullYear()}`, 'heading')}${ui.icon('chev')}</button>${m === months - 1 ? navBtn(1, 'right', nextLabel, 'nav') : '<span></span>'}</div><div class="cx-cal-grid" role="row">${WD.map((w) => `<span class="cx-cal-wd" role="columnheader">${ui.text(w, 'caption')}</span>`).join('')}</div><div class="cx-cal-grid">${cells.map((d) => {
|
|
436
438
|
const out = d.getMonth() !== first.getMonth(), off = min && d < startOfDay(min);
|
|
437
439
|
const st = isEdge(d) ? 'edge' : inRange(d) ? 'mid' : '';
|
|
438
440
|
return `<button class="cn-button cn-button-variant-ghost cn-button-size-icon cx-cal-day" role="gridcell" data-day="${+d}" ${out ? 'data-out' : ''} ${sameDay(d, today) ? 'data-today' : ''} ${st ? `data-range="${st}"` : ''} aria-pressed="${st === 'edge'}" aria-label="${DAYLONG[d.getDay()]} ${d.getDate()} de ${mon[d.getMonth()]}" tabindex="${sameDay(d, focusDay) && !out ? 0 : -1}" ${off ? 'disabled' : ''}>${d.getDate()}</button>`;
|
|
439
441
|
}).join('')}</div></div>`;
|
|
440
442
|
};
|
|
441
|
-
const picker = () => `<div class="cx-cal cx-cal-picker"><div class="cx-cal-
|
|
443
|
+
const picker = () => `<div class="cx-cal cx-cal-picker"><div class="cx-cal-head">${navBtn(-1, 'left', prevYear, 'year')}<div class="cx-cal-title">${ui.text(String(view.getFullYear()), 'heading')}</div>${navBtn(1, 'right', nextYear, 'year')}</div><div class="cx-cal-mgrid">${mon.map((m, k) => `<button type="button" class="cn-button cn-button-variant-ghost cn-button-size-sm cx-cal-m" data-cal-month="${k}" ${k === view.getMonth() ? 'aria-current="true"' : ''} ${today.getFullYear() === view.getFullYear() && today.getMonth() === k ? 'data-today' : ''}>${m[0].toUpperCase()}${m.slice(1, 3)}</button>`).join('')}</div></div>`;
|
|
442
444
|
const paint = (focus) => {
|
|
443
445
|
if (picking) { root.innerHTML = picker(); root.querySelector('[aria-current="true"]')?.focus(); return; }
|
|
444
|
-
root.innerHTML = `<div class="cx-cal" data-months="${months}"><div class="cx-cal-
|
|
446
|
+
root.innerHTML = `<div class="cx-cal" data-months="${months}"><div class="cx-cal-months">${Array.from({ length: months }, (_, m) => month(m)).join('')}</div></div>`;
|
|
445
447
|
if (focus) root.querySelector(`[data-day="${+focusDay}"]:not([data-out])`)?.focus();
|
|
446
448
|
};
|
|
447
449
|
const pick = (d) => {
|
|
@@ -540,7 +542,9 @@ let wheelsNow = [];
|
|
|
540
542
|
const sheetOf = (name) => { agOwner = me; openModal(K(name), 'auto'); };
|
|
541
543
|
let { resources, events, blocks = [], availability = () => [[9 * 60, 19 * 60]], free: freeList = null, queue = [], closed = [], loading = false,
|
|
542
544
|
lang = 'es', words = {}, currency = '', locale = '', cents = false, overlap = 'auto', placing = null } = opts; // all of these can change later through update()
|
|
543
|
-
|
|
545
|
+
// the first and last hour can change with the data (a shop's hours arrive after the first paint): update() takes them
|
|
546
|
+
let { from = 9, to = 19 } = opts;
|
|
547
|
+
const { slot = 30, hourPx: baseHour = 56, fit = false, ranges = false, onCreate = () => {}, onChange = () => {}, onAction = () => {}, onOpen = null, onBlock = null, onMove = null, onRange = () => {},
|
|
544
548
|
actions = null, createSheet = false, services = [], view: startView = 'day', sheet: sheetMode = 'built-in', drag: dragOn = true,
|
|
545
549
|
resize: resizeMode = 'on', moveAcross = 'on',
|
|
546
550
|
start: startDay = null, phone: phoneMode = '', hour12 = false, timeZone = '', phoneView = '', blockScope = 'each', phoneStrip = '' } = opts;
|
|
@@ -608,9 +612,12 @@ let wheelsNow = [];
|
|
|
608
612
|
const evHtml = (e, ln, showWho, rid = null, gut = false) => { const [l, n] = ln.get(e.id) || [0, 1]; const short = e.dur < 45; const said = clash.has(e.id) ? w.clashOne : SAY.includes(e.status) && w.status[e.status] ? w.status[e.status] : ''; const others = rid ? occ(e).filter((x) => x !== rid).map(resName).filter(Boolean).join(', ') : ''; const tg = tagsOf(e);
|
|
609
613
|
const span = gut ? '(100% - 22px)' : '100%'; /* an agenda with no limit keeps a strip on the right to book beside */ return `<button class="cx-ag-ev" data-ev="${e.id}" data-tone="${toneOf(e)}" ${e.status ? `data-status="${e.status}"` : ''}${paint1(e)}top:${y(toMin(e.start))}px;height:${(e.dur / 60) * hourPx - 3}px;left:calc(${span} * ${l / n} + 3px);width:calc(${span} / ${n} - 6px)"${clash.has(e.id) ? ' data-conflict' : ''} aria-label="${e.title}, ${clk(e.start)}, ${e.dur} ${w.minutes}${tg ? ', ' + tg : ''}${showWho ? `, ${w.with} ${resName(e.resource)}` : ''}${others ? `, ${w.with} ${others}` : ''}${e.status && w.status[e.status] ? `, ${w.status[e.status]}` : ''}${clash.has(e.id) ? `, ${w.clashOne}` : ''}">${short ? ui.text(`${e.title} · ${said || tg || clk(e.start)}`, 'label') : `${ui.text(e.title, 'label')}${ui.text(`${said ? said + ' · ' : ''}${tg ? tg + ' · ' : ''}${clk(e.start)} · ${e.subtitle || ''}${showWho ? ' · ' + resName(e.resource) : ''}${others ? ' · ' + others : ''}`, 'caption')}`}${e.price && !short ? `<span class="cx-ag-evprice">${fmtMoney(e.price)}</span>` : ''}${dragOn && resizeMode !== 'off' ? '<span class="cx-ag-grip" aria-hidden="true"></span>' : ''}</button>`; };
|
|
610
614
|
// the hours a person does not work: hatched. With server free hours, everything that is neither free nor booked.
|
|
615
|
+
// a day the business does not open: the column hatched, «Cerrado» in the middle (grid) or as the list's only line
|
|
616
|
+
const shutPill = () => `<span class="cx-ag-shutpill">${ui.icon('caloff')}${ui.text(w.closed, 'label')}</span>`;
|
|
617
|
+
const shutHtml = () => `<div class="cx-ag-off cx-ag-shut" style="top:0;height:100%">${shutPill()}</div>`;
|
|
611
618
|
const offHtml = (d, rid) => {
|
|
612
619
|
const av = freeList ? null : availability(rid, d);
|
|
613
|
-
if (av && !av.length) return `<div class="cx-ag-off" data-all style="top:0;height:100%">${ui.text(w.off, 'caption')}</div>`;
|
|
620
|
+
if (av && !av.length) return isClosed(d) ? shutHtml() : `<div class="cx-ag-off" data-all style="top:0;height:100%">${ui.text(w.off, 'caption')}</div>`;
|
|
614
621
|
if (!av) return '';
|
|
615
622
|
const out = []; let cur = from * 60; av.forEach(([a, b]) => { if (a > cur) out.push([cur, a]); cur = Math.max(cur, b); }); if (cur < to * 60) out.push([cur, to * 60]);
|
|
616
623
|
return out.map(([a, b]) => `<div class="cx-ag-off" style="top:${y(a)}px;height:${y(b) - y(a)}px"></div>`).join('');
|
|
@@ -657,7 +664,9 @@ let wheelsNow = [];
|
|
|
657
664
|
const skHead = (c) => { const r = resources.find((x) => x.id === c.rid); return v0() === 'week' ? `<span class="cx-ag-skhead" aria-hidden="true"><s class="skel" style="width:56px"></s></span>` : `<span class="cx-ag-skhead" aria-hidden="true"><s class="skel" style="width:24px;height:24px;border-radius:50%"></s><span class="cx-ag-colname"><b><s class="skel cx-ag-skline" style="width:64px"></s></b>${r && roleOf(r) ? `<small><s class="skel cx-ag-skline" style="width:48px"></s></small>` : ''}</span></span>`; };
|
|
658
665
|
const v0 = eff;
|
|
659
666
|
const grid = (cols) => { const hours = Array.from({ length: to - from }, (_, i) => from + i); const nowM = nowMin(); return `<div class="cx-ag-scroll cx-scroll"><div class="cx-ag" style="--cols:${cols.length};--h:${hourPx}px;grid-template-rows:auto auto"><div class="cx-ag-head"><span></span>${cols.map((c) => `<div class="cx-ag-colhead" ${c.today ? 'data-today' : ''} ${c.closed ? 'data-closed' : ''}>${loading ? skHead(c) : c.head}</div>`).join('')}</div><div class="cx-ag-body" style="height:${(to - from) * hourPx}px"><div class="cx-ag-times">${cols.some((k) => sameDay(k.day, today)) && nowM > from * 60 && nowM < to * 60 ? `<b class="cx-ag-nowtime" style="top:${y(nowM)}px">${clk(toHM(nowM))}</b>` : ''}${hours.map((h) => `<span style="top:${(h - from) * hourPx}px">${ui.text(hour12 ? `${((h + 11) % 12) + 1} ${h < 12 ? 'AM' : 'PM'}` : `${String(h).padStart(2, '0')}:00`, 'caption')}</span>`).join('')}</div>${cols.map((c, ci) => { const rid = c.rid === 'all' ? null : c.rid; const evs = evOn(c.day, rid); const cap = rid ? capOf(rid) : 1; const ln = lanes(evs); if (cap > 1) ln.forEach((v) => { v[1] = Math.max(v[1], Math.min(cap, 4)); }); const rids = rid ? [rid] : shown().map((r) => r.id); if (loading) return `<div class="cx-ag-col" data-day="${+c.day}" data-rid="${c.rid}" aria-hidden="true">${rid && !freeList ? offHtml(c.day, rid) : ''}${skelCol(ci)}</div>`;
|
|
660
|
-
|
|
667
|
+
const offRid = rid || (shown().length === 1 && !freeList ? shown()[0].id : null);
|
|
668
|
+
if (c.closed && !evs.length) return `<div class="cx-ag-col" data-day="${+c.day}" data-rid="${c.rid}" data-closed role="group" aria-label="${c.label}, ${w.closed}">${shutHtml()}</div>`;
|
|
669
|
+
return `<div class="cx-ag-col" data-day="${+c.day}" data-rid="${c.rid}" role="group" aria-label="${c.label}">${offRid ? offHtml(c.day, offRid) : ''}${!rids.some(bookable) ? '' : freeList ? rids.filter(bookable).map((r) => freeHtml(c.day, r, c.label)).join('') : Array.from({ length: ((to - from) * 60) / slot }, (_, i) => gone(c.day, from * 60 + i * slot) ? '' : `<button class="cx-ag-slot" data-slot-time="${from * 60 + i * slot}" style="top:${y(from * 60 + i * slot)}px;height:${(slot / 60) * hourPx}px" aria-label="${w.newItem}, ${c.label}, ${toHM(from * 60 + i * slot)}" tabindex="-1"></button>`).join('')}${blOn(c.day, rid).map(blockHtml).join('')}${evs.map((e) => evHtml(e, ln, c.rid === 'all' && resources.length > 1, rid, cap === 0)).join('')}${ghostHtml(c.day, rid)}${goneHtml(c.day)}${sameDay(c.day, today) && nowM > from * 60 && nowM < to * 60 ? `<div class="cx-ag-now" style="top:${y(nowM)}px" aria-hidden="true">${cols.findIndex((k) => sameDay(k.day, today)) === ci ? '<i></i>' : ''}</div>` : ''}</div>`; }).join('')}</div></div></div>`; };
|
|
661
670
|
// list = the phone's agenda: a solid row is sold, a dashed one is still free
|
|
662
671
|
// one cita as a row of the list (the day's list and the range's)
|
|
663
672
|
const evRow = (e) => `<button class="cx-ag-row" data-ev="${e.id}"${clash.has(e.id) ? ' data-conflict' : ''} data-tone="${toneOf(e)}" ${e.status ? `data-status="${e.status}"` : ''}${paint1(e)}"><span class="cx-ag-row-t">${ui.text(clk(e.start), 'strong')}${ui.text(`${e.dur} min`, 'caption')}</span>${e.photo ? `<span class="cn-avatar cx-ag-face cx-ag-rowface" style="background-image:url('${e.photo}')" aria-hidden="true"></span>` : ''}<span class="cx-vstack" style="gap:0;min-width:0">${ui.text(e.title, 'heading')}${ui.text(`${e.subtitle || ''}${resources.length > 1 ? ' · ' + resName(e.resource) : ''}${tagsOf(e) ? ' · ' + tagsOf(e) : ''}`, 'muted')}</span>${e.status && w.status[e.status] ? ui.badge(w.status[e.status], { tone: { info: 'info', live: 'wait', done: 'ok', bad: 'bad', ok: 'ok' }[toneOf(e)] || 'ok' }) : e.steps ? ui.badge(e.steps[e.at], { tone: e.tone === 'wait' ? 'wait' : e.tone === 'info' ? 'info' : 'ok' }) : ''}${e.price ? ui.text(fmtMoney(e.price), 'strong') : ''}</button>`;
|
|
@@ -679,7 +688,7 @@ let wheelsNow = [];
|
|
|
679
688
|
}
|
|
680
689
|
}
|
|
681
690
|
const html = rows.map((r) => (typeof r === 'string' ? r : `<button class="cx-ag-row" data-kind="free" data-free="${r.start}" data-rid="${r.free[0]}" aria-label="${w.freeFromTo(clk(toHM(r.start)), clk(toHM(r.end)))}"><span class="cx-ag-row-t">${ui.text(clk(toHM(r.start)), 'strong')}${r.end - r.start > slot ? ui.text(`${w.until} ${clk(toHM(r.end))}`, 'caption') : ''}</span>${ui.text(`${w.free}${rids.length > 1 ? ` · ${r.free.map(resName).join(', ')}` : ''}`, 'muted')}<span class="cx-grow"></span>${ui.icon('plus')}</button>`)).join('');
|
|
682
|
-
return `<div class="cx-ag-list" role="list">${html || `<div class="cn-empty" style="padding:32px 12px"><div class="cn-empty-header"><div class="cn-empty-title">${
|
|
691
|
+
return `<div class="cx-ag-list" role="list">${html || (isClosed(day) ? `<div class="cx-ag-shutlist">${shutPill()}</div>` : '') || `<div class="cn-empty" style="padding:32px 12px"><div class="cn-empty-header"><div class="cn-empty-title">${!rids.some((rid) => availability(rid, day).length) ? w.noHours : +day < +today && !sameDay(day, today) ? w.dayOver : sameDay(day, today) ? w.todayOver : w.noHours}</div></div></div>`}</div>`;
|
|
683
692
|
};
|
|
684
693
|
// LOADING keeps the very layout it is about to show: the same toolbar, strip, column heads, hours and height,
|
|
685
694
|
// with grey citas where the real ones will land. Nothing jumps when the data arrives, nothing shrinks while it loads.
|
|
@@ -708,6 +717,7 @@ let wheelsNow = [];
|
|
|
708
717
|
const weekHead = (d, rid) => { const n = evOn(d, rid).length, q = queue.filter((x) => sameDay(x.day, d)).length, shut = isClosed(d); const sub = [shut && !n ? w.closed : n ? w.countOf(n) : '', q ? w.waiting(q) : ''].filter(Boolean).join(' · '); return `<span class="cx-ag-colday">${w.dayS[d.getDay()]} ${d.getDate()}</span>${sub ? `<small class="cx-ag-colsub"${q ? ' data-queue' : ''}>${sub}</small>` : ''}`; };
|
|
709
718
|
const tellRange = () => { const v = eff(); const a = v === 'range' ? rangeSel[0] : v === 'week' ? weekDays()[0] : day, b = v === 'range' ? rangeSel[1] : v === 'week' ? weekDays()[6] : day; const key = `${v}|${+a}|${+b}`; if (key === lastRange) return; lastRange = key; onRange({ from: a, to: b, view: v }); };
|
|
710
719
|
const paint = () => {
|
|
720
|
+
refit();
|
|
711
721
|
if (typeof drag !== 'undefined' && drag) { const d = drag; drag = null; clearTimeout(d.timer); cancelAnimationFrame(scrollRaf); d.ghost?.remove(); }
|
|
712
722
|
const v = eff(); // the phone keeps the calendar: day grid or list, the week needs a wide screen
|
|
713
723
|
const cols = v === 'week' ? weekDays().map((d) => ({ day: d, rid: who === 'all' || who.startsWith('kind:') ? 'all' : who, head: weekHead(d, who === 'all' || who.startsWith('kind:') ? null : who), label: `${w.dayL[d.getDay()]} ${d.getDate()}`, today: sameDay(d, today), closed: isClosed(d) })) : dayCols().map((r) => ({ day, rid: r.id, head: `${r.photo ? `<span class="cn-avatar cx-ag-face" style="background-image:url('${r.photo}')"></span>` : ui.avatar(r.name, { size: 'sm' })}<span class="cx-ag-colname"><b>${r.name}</b>${roleOf(r) ? `<small>${roleOf(r)}</small>` : ''}</span>`, label: r.name }));
|
|
@@ -838,7 +848,7 @@ let wheelsNow = [];
|
|
|
838
848
|
$$('.cx-ag-now', root).forEach((x) => { x.style.top = y(m) + 'px'; }); const t = $('.cx-ag-nowtime', root); if (t) { t.style.top = y(m) + 'px'; t.textContent = clk(toHM(m)); }
|
|
839
849
|
$$('.cx-ag-col', root).forEach((c) => { if (!sameDay(new Date(+c.dataset.day), today)) return; const g = $('.cx-ag-gone', c); if (g) g.style.height = y(m) + 'px'; });
|
|
840
850
|
}, 30000);
|
|
841
|
-
new ResizeObserver(() => requestAnimationFrame(() => { const n = root.clientWidth < 700; if (n !== narrow) { narrow = n; paint(); } })).observe(root);
|
|
851
|
+
new ResizeObserver(() => requestAnimationFrame(() => { const n = root.clientWidth < 700; const h = fit ? root.querySelector('.cx-ag-scroll')?.clientHeight || 0 : 0; if (n !== narrow) { narrow = n; paint(); } else if (fit && h && h !== fitH) paint(); })).observe(root);
|
|
842
852
|
|
|
843
853
|
// ── the event sheet (showcase): detail · lifecycle · payment · move (second step) · cancel (second step).
|
|
844
854
|
// In app mode a tapped cita goes to onOpen and the app opens its own sheet. ──
|
|
@@ -926,11 +936,11 @@ let wheelsNow = [];
|
|
|
926
936
|
const seeClash = () => { const els = $$('.cx-ag-ev[data-conflict], .cx-ag-row[data-conflict]', root); if (!els.length) return; const el = els[clashAt++ % els.length]; const sc = $('.cx-ag-scroll', root);
|
|
927
937
|
if (sc && sc.contains(el)) { const a = sc.getBoundingClientRect(), b = el.getBoundingClientRect(); sc.scrollTo({ top: sc.scrollTop + b.top - a.top - 60, left: sc.scrollLeft + b.left - a.left - 60, behavior: 'smooth' }); } else el.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
928
938
|
el.animate([{ outline: '2px solid var(--destructive)', outlineOffset: '2px' }, { outline: '2px solid transparent', outlineOffset: '2px' }], { duration: 1400, delay: 250 }); };
|
|
929
|
-
let pop = null, dateMode = 'day';
|
|
939
|
+
let pop = null, dateMode = 'day'; // «range» only with the `ranges` option
|
|
930
940
|
const calWords = () => ({ mon: w.mon, weekdays: w.dayS.slice(1).concat(w.dayS[0]).map((x) => x.slice(0, 2).toLowerCase()), prevLabel: w.prevMonth, nextLabel: w.nextMonth, prevYear: w.prevYear, nextYear: w.nextYear, pickLabel: w.pickMonth });
|
|
931
941
|
const dateUI = (host, done) => {
|
|
932
942
|
const draw = () => {
|
|
933
|
-
host.innerHTML = `<div class="cx-tabs cx-ag-datemode" role="tablist" aria-label="${w.goTo}">${[['day', w.oneDay], ['range', w.someDays]].map(([k, l]) => `<button type="button" class="cn-button cn-button-variant-ghost cn-button-size-sm cx-tab" role="tab" aria-selected="${dateMode === k}" data-date-mode="${k}">${l}</button>`).join('')}</div
|
|
943
|
+
host.innerHTML = `${ranges ? `<div class="cx-tabs cx-ag-datemode" role="tablist" aria-label="${w.goTo}">${[['day', w.oneDay], ['range', w.someDays]].map(([k, l]) => `<button type="button" class="cn-button cn-button-variant-ghost cn-button-size-sm cx-tab" role="tab" aria-selected="${dateMode === k}" data-date-mode="${k}">${l}</button>`).join('')}</div>` : ''}<div class="cx-ag-datecal"></div>${dateMode === 'range' ? `<p class="cx-ag-datehint">${w.rangeHint}</p>` : ''}<div class="cx-ag-datefoot">${ui.button({ label: w.today, size: 'sm', variant: 'outline', attrs: 'data-date-today' })}</div>`;
|
|
934
944
|
$$('.cx-tabs', host).forEach(initTabs);
|
|
935
945
|
const wide = dateMode === 'range' && innerWidth > 720;
|
|
936
946
|
kit.calendar($('.cx-ag-datecal', host), { ...calWords(), mode: dateMode === 'range' ? 'range' : 'single', months: wide ? 2 : 1, value: dateMode === 'range' ? (rangeSel ? [new Date(rangeSel[0]), new Date(rangeSel[1])] : null) : new Date(day),
|
|
@@ -961,19 +971,38 @@ let wheelsNow = [];
|
|
|
961
971
|
body.innerHTML = '<div class="cx-ag-datesheet"></div>';
|
|
962
972
|
dateUI($('.cx-ag-datesheet', body), (r) => { closeModal(); applyDate(r); });
|
|
963
973
|
};
|
|
974
|
+
// fit: the hours share the height the app gives the agenda (never under baseHour), as a day view on a monitor should
|
|
975
|
+
let hourPx = baseHour, fitH = 0;
|
|
976
|
+
const refit = () => {
|
|
977
|
+
if (!fit) return;
|
|
978
|
+
const sc = root.querySelector('.cx-ag-scroll'), ag = sc ? sc.querySelector('.cx-ag') : null, hours = sc ? sc.querySelector('.cx-ag-body') : null;
|
|
979
|
+
if (!sc || !sc.clientHeight || !ag || !hours) return;
|
|
980
|
+
fitH = sc.clientHeight;
|
|
981
|
+
// everything in the scroll that is not the hours (the day heads, gaps, padding) keeps its height
|
|
982
|
+
const around = ag.offsetHeight - hours.offsetHeight;
|
|
983
|
+
hourPx = Math.max(baseHour, Math.floor((sc.clientHeight - around) / Math.max(1, to - from)));
|
|
984
|
+
};
|
|
985
|
+
let blockDay = null; // the one open day of the week, when the form had nothing to choose from
|
|
964
986
|
const openBlock = () => { blockOpen = true; sheetOf('agBlock'); };
|
|
965
987
|
CONTENT[K('agBlock')] = () => {
|
|
966
988
|
$('#mdl-title').textContent = w.blockTitle; $('#mdl-desc').textContent = w.blockDesc;
|
|
967
989
|
const sel = (id, label, v) => ui.field({ id, label, control: kit.timeButton(v, `id="${id}" data-blt="${label}" style="width:100%;justify-content:flex-start"`) });
|
|
968
|
-
|
|
990
|
+
// the week shows seven days, so the form says which one it blocks: only days that open, from today on, the shown day first
|
|
991
|
+
const open = eff() === 'week' ? weekDays().filter((d) => !isClosed(d)) : [];
|
|
992
|
+
const ahead = open.filter((d) => +d >= +today), days = ahead.length ? ahead : open;
|
|
993
|
+
const pickDay = days.find((d) => sameDay(d, day)) || days[0];
|
|
994
|
+
const dayField = days.length > 1 ? ui.field({ id: 'bl-d', label: w.day, control: `<button type="button" class="cn-button cn-button-variant-outline cn-button-size-default cx-sel-btn cx-fill" id="bl-d" data-v="${+pickDay}"></button>` }) : '';
|
|
995
|
+
body.innerHTML = `${dayField}${resources.length > 1 && blockScope !== 'all' ? ui.field({ id: 'bl-r', label: w.for, control: `<button type="button" class="cn-button cn-button-variant-outline cn-button-size-default cx-sel-btn cx-fill" id="bl-r" data-v="${who === 'all' ? 'all' : who}"></button>` }) : ''}<div class="cx-frange">${sel('bl-f', w.from, '13:00')}${sel('bl-t', w.to, '14:00')}</div>${ui.field({ id: 'bl-why', label: w.reasonLabel, control: ui.input({ id: 'bl-why', value: w.reasonDefault }), description: w.onlyYou })}`;
|
|
969
996
|
foot.innerHTML = ui.button({ label: w.cancel, size: 'default', attrs: 'data-dismiss', cls: 'cx-exit' }) + ui.button({ label: `${ui.icon('i-lock')}${w.block}`, variant: 'default', size: 'default', attrs: 'data-bl-go', cls: 'cx-go' });
|
|
997
|
+
const bd = $('#bl-d', body); if (bd) kit.selectMenu(bd, { label: w.day, value: bd.dataset.v, options: days.map((d) => ({ value: String(+d), label: `${w.dayL[d.getDay()]} ${d.getDate()}` })), onChange: (v) => { bd.dataset.v = v; } });
|
|
998
|
+
blockDay = days.length === 1 ? days[0] : null;
|
|
970
999
|
const br = $('#bl-r', body); if (br) kit.selectMenu(br, { label: w.for, value: br.dataset.v, options: [{ value: 'all', label: w.wholeShop }, ...resources.map((r) => ({ value: r.id, label: r.name }))], onChange: (v) => { br.dataset.v = v; } });
|
|
971
1000
|
};
|
|
972
1001
|
body.addEventListener('click', async (e) => { const b = e.target.closest('[data-blt]'); if (agOwner !== me || !b || !blockOpen) return; const v = await kit.pickTime({ value: b.querySelector('span').textContent, label: b.dataset.blt, from, to, step: 15 }); if (!v) return; b.querySelector('span').textContent = v; b.removeAttribute('aria-invalid'); const fb = $('#bl-f span', body), tb = $('#bl-t span', body); if (toMin(tb.textContent) <= toMin(fb.textContent)) tb.textContent = toHM(Math.min(toMin(fb.textContent) + 60, to * 60)); });
|
|
973
1002
|
document.addEventListener('click', (e) => {
|
|
974
1003
|
if (agOwner !== me || !e.target.closest('[data-bl-go]') || !blockOpen) return;
|
|
975
1004
|
const f = toMin($('#bl-f span', body).textContent), t2 = toMin($('#bl-t span', body).textContent); if (t2 <= f) { $('#bl-t', body).setAttribute('aria-invalid', 'true'); return; }
|
|
976
|
-
const b = { id: 'b' + Date.now(), resource: $('#bl-r', body)?.dataset.v || resources[0].id, day: new Date(day), start: toHM(f), dur: t2 - f, reason: $('#bl-why', body).value.trim() || w.blocked };
|
|
1005
|
+
const b = { id: 'b' + Date.now(), resource: $('#bl-r', body)?.dataset.v || resources[0].id, day: $('#bl-d', body) ? new Date(+$('#bl-d', body).dataset.v) : new Date(blockDay || day), start: toHM(f), dur: t2 - f, reason: $('#bl-why', body).value.trim() || w.blocked };
|
|
977
1006
|
blockOpen = false; closeModal();
|
|
978
1007
|
if (onBlock) { onBlock(b); return; }
|
|
979
1008
|
blocks.push(b); paint(); onChange(events); island('done', w.blockedOk, 1800);
|
|
@@ -985,6 +1014,7 @@ let wheelsNow = [];
|
|
|
985
1014
|
if ('resources' in next) resources = next.resources; if ('events' in next) events = next.events; if ('blocks' in next) blocks = next.blocks;
|
|
986
1015
|
if ('availability' in next) availability = next.availability; if ('free' in next) freeList = next.free; if ('queue' in next) queue = next.queue;
|
|
987
1016
|
if ('closed' in next) closed = next.closed; if ('loading' in next) loading = next.loading;
|
|
1017
|
+
if (Number(next.from) >= 0 && Number(next.to) > Number(next.from)) { from = Number(next.from); to = Number(next.to); }
|
|
988
1018
|
if ('overlap' in next) overlap = next.overlap; if ('placing' in next) placing = next.placing; if ('currency' in next) currency = next.currency; if ('locale' in next) locale = next.locale; if ('cents' in next) cents = next.cents;
|
|
989
1019
|
if ('lang' in next || 'words' in next) { lang = next.lang ?? lang; words = next.words ?? words; w = { ...(AG_WORDS[lang] || AG_WORDS.es), ...words }; }
|
|
990
1020
|
if (who !== 'all' && !(who.startsWith('kind:') ? resources.some((r) => kindOf(r) === who.slice(5)) : resources.some((r) => r.id === who))) who = 'all';
|