@muten/shadcn 0.2.4 → 0.2.6
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 +7 -1
- package/package.json +1 -1
- package/registry/carousel.muten +5 -3
- package/registry/kit/collections.js +12 -3
package/kit.css
CHANGED
|
@@ -1471,7 +1471,7 @@ p.cx-text { margin: 0; }
|
|
|
1471
1471
|
.cx-car-slide { scroll-snap-align: start; min-width: 0; }
|
|
1472
1472
|
/* a scrolling track clips both axes: 6 px of room above and below keeps a card's ring and shadow whole (it was cut at
|
|
1473
1473
|
the top), taken back with a negative margin so the carousel does not grow */
|
|
1474
|
-
.cx-car-track { padding-block: 6px; margin-block: -6px; }
|
|
1474
|
+
.cx-car-track { padding-block: var(--car-room, 6px); margin-block: calc(-1 * var(--car-room, 6px)); }
|
|
1475
1475
|
/* while the mouse drags the track it follows the hand; on release kit core settles it on the nearest page */
|
|
1476
1476
|
.cx-car-track.dragging { scroll-snap-type: none; scroll-behavior: auto; cursor: grabbing; user-select: none; }
|
|
1477
1477
|
/* centred like Embla's containScroll: the first slide sits at the start and the last at the end (no empty strip
|
|
@@ -1498,6 +1498,12 @@ p.cx-text { margin: 0; }
|
|
|
1498
1498
|
@media (max-width: 640px) { .cx-car { --per: var(--per-sm) !important; } }
|
|
1499
1499
|
/* only: "phone": swiped on a phone; on anything wider the same slides stand still in a grid of the wide perView, with
|
|
1500
1500
|
no dots and no arrows (data-only is on the root from the first paint, so a wide screen never sees a track) */
|
|
1501
|
+
/* bleed: "on": a scrolling track clips at its own edges, so on a phone the slide that peeks was cut at the text's margin.
|
|
1502
|
+
The track reaches out to the screen's edges by --car-bleed (the page's side gutter) and pads it back: the first slide
|
|
1503
|
+
still lines up with the text, the next one runs off the screen as it should */
|
|
1504
|
+
@media (max-width: 640px) {
|
|
1505
|
+
.cx-car[data-bleed="on"] .cx-car-track { margin-inline: calc(-1 * var(--car-bleed, 16px)); padding-inline: var(--car-bleed, 16px); scroll-padding-inline: var(--car-bleed, 16px); }
|
|
1506
|
+
}
|
|
1501
1507
|
@media (min-width: 641px) {
|
|
1502
1508
|
.cx-car[data-only="phone"] .cx-car-track { grid-auto-flow: row; grid-auto-columns: auto; grid-template-columns: repeat(var(--per, 3), minmax(0, 1fr)); overflow: visible; scroll-snap-type: none; padding-block: 0; margin-block: 0; }
|
|
1503
1509
|
.cx-car[data-only="phone"] .cx-car-bar, .cx-car[data-only="phone"] .cx-car-arrow { display: none; }
|
package/package.json
CHANGED
package/registry/carousel.muten
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
# perView "4" or "2,4" (phone, wider) · center "on" (the active one centred, neighbours peeking) · loop "on" ·
|
|
3
3
|
# autoplay ms (pauses on hover, focus, touch, hidden tab; off with reduced motion; its own pause button) ·
|
|
4
4
|
# indicator dots | progress | count | thumbs · arrows bar | overlay | none ·
|
|
5
|
-
# only "phone" (a carousel on a phone; on a wider screen the same slides sit still in a grid of perView's wide count)
|
|
5
|
+
# only "phone" (a carousel on a phone; on a wider screen the same slides sit still in a grid of perView's wide count) ·
|
|
6
|
+
# bleed "on" (on a phone the track reaches the screen's edges instead of stopping at the text's margin; the margin is
|
|
7
|
+
# --car-bleed, 16px by default) · --car-room on the carousel: the room above and below for shadows (6px)
|
|
6
8
|
# Carousel(label: "Products", perView: "2,4") { each products as p { CarouselSlide { … } } }
|
|
7
9
|
# shadcn-for-muten · part + Custom (CarouselBehavior, kit collections) · kit.css (.cx-car).
|
|
8
|
-
part Carousel(label: text, perView: text = "1", center: text = "", loop: text = "", autoplay: number = 0, indicator: text = "dots", arrows: text = "bar", only: text = "") {
|
|
9
|
-
Section class("cx-car") data(only: $only) aria(label: $label, roledescription: "carrusel") {
|
|
10
|
+
part Carousel(label: text, perView: text = "1", center: text = "", loop: text = "", autoplay: number = 0, indicator: text = "dots", arrows: text = "bar", only: text = "", bleed: text = "") {
|
|
11
|
+
Section class("cx-car") data(only: $only, bleed: $bleed) aria(label: $label, roledescription: "carrusel") {
|
|
10
12
|
Stack class("cx-car-view") { Stack class("cx-car-track") { slot } }
|
|
11
13
|
Custom Carousel inputs(perView: $perView, center: $center, loop: $loop, autoplay: $autoplay, indicator: $indicator, arrows: $arrows) class("cx-car-behavior")
|
|
12
14
|
}
|
|
@@ -134,8 +134,13 @@ export function carouselBehavior(root, i, core) {
|
|
|
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); } });
|
|
136
136
|
let drag = null;
|
|
137
|
-
track
|
|
138
|
-
addEventListener('
|
|
137
|
+
// a track with nothing to scroll (only: "phone" on a wide screen) is not dragged at all
|
|
138
|
+
track.addEventListener('pointerdown', (e) => { if (e.pointerType !== 'mouse' || e.button > 0 || track.scrollWidth <= track.clientWidth + 1) return; drag = { x: e.clientX, left: track.scrollLeft, moved: false }; track.classList.add('dragging'); });
|
|
139
|
+
// A slide that is a link or a picture starts the browser's own drag: it ends in pointercancel, never pointerup, and the
|
|
140
|
+
// track went on following the mouse with the button already up. So the browser's drag is refused inside the track,
|
|
141
|
+
// and a move with no button down (released outside the window, or a lost pointerup) ends the drag too.
|
|
142
|
+
track.addEventListener('dragstart', (e) => e.preventDefault());
|
|
143
|
+
addEventListener('pointermove', (e) => { if (!drag) return; if (!(e.buttons & 1)) { release(); return; } const dx = e.clientX - drag.x; if (Math.abs(dx) > 4) drag.moved = true; track.scrollLeft = drag.left - dx; });
|
|
139
144
|
// A drag lets go between two pages (snapping is off while dragging): settle on the nearest one, as a swipe does,
|
|
140
145
|
// or the track rests half-way and the dots and arrows disagree about where it is.
|
|
141
146
|
const nearest = () => {
|
|
@@ -143,7 +148,11 @@ export function carouselBehavior(root, i, core) {
|
|
|
143
148
|
if (track.scrollLeft >= track.scrollWidth - track.clientWidth - 4) return pages() - 1;
|
|
144
149
|
return Math.max(0, Math.min(pages() - 1, Math.round(track.scrollLeft / (step() * perPage()))));
|
|
145
150
|
};
|
|
146
|
-
|
|
151
|
+
function release() { if (!drag) return; const moved = drag.moved; drag = null; track.classList.remove('dragging'); if (moved) { go(nearest()); swallowClick(); } }
|
|
152
|
+
// the click that ends a drag is not a tap on the slide; only that one: a drag that ended without a click (pointercancel)
|
|
153
|
+
// must not leave the next real tap swallowed
|
|
154
|
+
function swallowClick() { const eat = (e) => { e.preventDefault(); e.stopPropagation(); }; track.addEventListener('click', eat, { capture: true, once: true }); setTimeout(() => track.removeEventListener('click', eat, { capture: true }), 0); }
|
|
155
|
+
['pointerup', 'pointercancel', 'blur'].forEach((type) => addEventListener(type, release));
|
|
147
156
|
let playing = !!autoplay && !matchMedia('(prefers-reduced-motion: reduce)').matches, hold = false;
|
|
148
157
|
if (autoplay) {
|
|
149
158
|
if (!playing) { const b = $('[data-car="play"]', root); b.innerHTML = ui.icon('play'); b.setAttribute('aria-label', T('Play')); }
|