@waveso/ui 0.17.0 → 0.18.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/dist/styles.css CHANGED
@@ -247,21 +247,21 @@
247
247
  * `--contrast`…).
248
248
  * ──────────────────────────────────────────────────────────────────────── */
249
249
 
250
- /* Graphite — the mid-tone grey (default). Built around its card colour, the
251
- * 900 (#2F3036): the page (950) and menus (800) one even contrast step
252
- * either side, the steps above evenly spaced up to the text colour at the
253
- * 50. One hue, 278.2; chroma arcs to 0.014 through the middle. */
254
- --graphite-50: oklch(0.964 0.001 278.2); /* #F3F3F4 */
255
- --graphite-100: oklch(0.911 0.004 278.2); /* #E1E1E4 */
256
- --graphite-200: oklch(0.840 0.007 278.2); /* #C9CACF */
257
- --graphite-300: oklch(0.757 0.009 278.2); /* #AEB0B6 */
258
- --graphite-400: oklch(0.678 0.011 278.2); /* #96979F */
259
- --graphite-500: oklch(0.589 0.013 278.2); /* #7B7C85 */
260
- --graphite-600: oklch(0.498 0.014 278.2); /* #61626B */
261
- --graphite-700: oklch(0.415 0.013 278.2); /* #4A4B53 */
262
- --graphite-800: oklch(0.348 0.012 278.2); /* #383940 */
263
- --graphite-900: oklch(0.311 0.011 278.2); /* #2F3036 */
264
- --graphite-950: oklch(0.268 0.009 278.2); /* #25262A */
250
+ /* Graphite — the mid-tone grey (default). Built around its original card
251
+ * colour, the 900 (#323339): the page (950) and menus (800) one 1.15
252
+ * contrast step either side, the steps above evenly spaced up to the text
253
+ * colour at the 50. One hue, 278.3; chroma arcs to 0.014 through the middle. */
254
+ --graphite-50: oklch(0.964 0.001 278.3); /* #F3F3F4 */
255
+ --graphite-100: oklch(0.912 0.003 278.3); /* #E1E2E4 */
256
+ --graphite-200: oklch(0.842 0.007 278.3); /* #CACBD0 */
257
+ --graphite-300: oklch(0.761 0.009 278.3); /* #B0B1B7 */
258
+ --graphite-400: oklch(0.683 0.011 278.3); /* #9799A0 */
259
+ --graphite-500: oklch(0.595 0.013 278.3); /* #7D7E87 */
260
+ --graphite-600: oklch(0.506 0.014 278.3); /* #63646D */
261
+ --graphite-700: oklch(0.424 0.014 278.3); /* #4C4E56 */
262
+ --graphite-800: oklch(0.359 0.012 278.3); /* #3B3C43 */
263
+ --graphite-900: oklch(0.322 0.011 278.3); /* #323339 */
264
+ --graphite-950: oklch(0.283 0.009 278.3); /* #28292E */
265
265
 
266
266
  /* Ink — deep navy surfaces, neutral-cool structure (page #020812, surface #0B1627).
267
267
  * Graphite's even lightness steps between Ink's 50 and its page. Chroma fades
@@ -318,6 +318,11 @@
318
318
  /* Rings = brand themed */
319
319
  --focus: var(--primary);
320
320
 
321
+ /* Selected — chosen, current or on: the current tab, the sidebar's current
322
+ * page, a pressed toggle, a checked box, a selected row. Primary by default,
323
+ * so it looks as it always has; set it to restyle every chosen state at once. */
324
+ --selected: var(--primary);
325
+
321
326
  /* Scrim — the dim behind modal overlays (dialog / drawer / alert backdrop). */
322
327
  --scrim: oklch(0 0 0 / 0.1);
323
328
 
@@ -498,17 +503,17 @@
498
503
  --wave-teal-900: oklch(0.251 0.049 219.9);
499
504
  --wave-teal-950: oklch(0.197 0.037 222.5);
500
505
  /* Graphite */
501
- --graphite-50: oklch(0.964 0.001 278.2);
502
- --graphite-100: oklch(0.911 0.004 278.2);
503
- --graphite-200: oklch(0.840 0.007 278.2);
504
- --graphite-300: oklch(0.757 0.009 278.2);
505
- --graphite-400: oklch(0.678 0.011 278.2);
506
- --graphite-500: oklch(0.589 0.013 278.2);
507
- --graphite-600: oklch(0.498 0.014 278.2);
508
- --graphite-700: oklch(0.415 0.013 278.2);
509
- --graphite-800: oklch(0.348 0.012 278.2);
510
- --graphite-900: oklch(0.311 0.011 278.2);
511
- --graphite-950: oklch(0.268 0.009 278.2);
506
+ --graphite-50: oklch(0.964 0.001 278.3);
507
+ --graphite-100: oklch(0.912 0.003 278.3);
508
+ --graphite-200: oklch(0.842 0.007 278.3);
509
+ --graphite-300: oklch(0.761 0.009 278.3);
510
+ --graphite-400: oklch(0.683 0.011 278.3);
511
+ --graphite-500: oklch(0.595 0.013 278.3);
512
+ --graphite-600: oklch(0.506 0.014 278.3);
513
+ --graphite-700: oklch(0.424 0.014 278.3);
514
+ --graphite-800: oklch(0.359 0.012 278.3);
515
+ --graphite-900: oklch(0.322 0.011 278.3);
516
+ --graphite-950: oklch(0.283 0.009 278.3);
512
517
  /* Ink */
513
518
  --ink-50: oklch(0.966 0.003 248);
514
519
  --ink-100: oklch(0.906 0.007 265);
@@ -546,8 +551,8 @@
546
551
  * dark system as on a light one. A pinned theme class still beats both.
547
552
  *
548
553
  * The page and popups are white; cards and the sidebar sit on the 50, a faint
549
- * warm tint, so the contour does most of the separating. The marks are its
550
- * text colour, the 950, made translucent, as in every theme.
554
+ * warm tint, so their edge does most of the separating. Lines and fills are
555
+ * its text colour, the 950, made translucent, as in every theme.
551
556
  * ------------------------------------------------------------------------- */
552
557
  :where(:root),
553
558
  .paper {
@@ -556,11 +561,13 @@
556
561
  --elevated: white;
557
562
  --contrast: var(--paper-950);
558
563
  --muted: var(--paper-600);
559
- --soft: var(--paper-500);
564
+ --subtle: var(--paper-500);
560
565
  --secondary: var(--paper-100);
561
- --contour: color-mix(in srgb, var(--paper-950) 5%, transparent);
562
- --highlight: color-mix(in srgb, var(--paper-950) 10%, transparent);
563
- --shade: color-mix(in srgb, var(--paper-950) 15%, transparent);
566
+ --line: color-mix(in srgb, var(--paper-950) 5%, transparent);
567
+ --edge: color-mix(in srgb, var(--paper-950) 10%, transparent);
568
+ --contour: color-mix(in srgb, var(--paper-950) 15%, transparent);
569
+ --highlighted: color-mix(in srgb, var(--paper-950) 10%, transparent);
570
+ --neutral: color-mix(in srgb, var(--paper-950) 15%, transparent);
564
571
  --shadow-strength: 0.055;
565
572
  --danger: var(--wave-red-500);
566
573
  --danger-text: var(--wave-red-600);
@@ -589,20 +596,22 @@
589
596
  --elevated: var(--graphite-800);
590
597
  --contrast: var(--graphite-50);
591
598
  --muted: var(--graphite-300);
592
- --soft: var(--graphite-500);
599
+ --subtle: var(--graphite-500);
593
600
  --secondary: var(--graphite-700);
594
- --contour: color-mix(in srgb, var(--graphite-50) 5%, transparent);
595
- --highlight: color-mix(in srgb, var(--graphite-50) 10%, transparent);
596
- --shade: color-mix(in srgb, var(--graphite-50) 15%, transparent);
601
+ --line: color-mix(in srgb, var(--graphite-50) 5%, transparent);
602
+ --edge: color-mix(in srgb, var(--graphite-50) 10%, transparent);
603
+ --contour: color-mix(in srgb, var(--graphite-50) 15%, transparent);
604
+ --highlighted: color-mix(in srgb, var(--graphite-50) 10%, transparent);
605
+ --neutral: color-mix(in srgb, var(--graphite-50) 15%, transparent);
597
606
  --shadow-strength: 0.045;
598
607
  --danger: var(--wave-red-500);
599
- --danger-text: oklch(0.738 0.158 16.8); /* #FE7C86, see below */
608
+ --danger-text: oklch(0.749 0.152 16.8); /* #FF828B, see below */
600
609
  --success: var(--wave-green-500);
601
- --success-text: oklch(0.700 0.155 160); /* #14BB7C, see below */
610
+ --success-text: oklch(0.712 0.161 160); /* #00C07E, see below */
602
611
  --warning: var(--wave-orange-500);
603
- --warning-text: oklch(0.732 0.173 57.3); /* #F78711, see below */
612
+ --warning-text: oklch(0.745 0.179 57.3); /* #FE8A04, see below */
604
613
  --info: var(--wave-blue-500);
605
- --info-text: oklch(0.717 0.150 250.4); /* #52A8FD, see below */
614
+ --info-text: oklch(0.730 0.145 250.4); /* #5AADFF, see below */
606
615
  }
607
616
  }
608
617
 
@@ -612,20 +621,22 @@
612
621
  --elevated: var(--graphite-800);
613
622
  --contrast: var(--graphite-50);
614
623
  --muted: var(--graphite-300);
615
- --soft: var(--graphite-500);
624
+ --subtle: var(--graphite-500);
616
625
  --secondary: var(--graphite-700);
617
- --contour: color-mix(in srgb, var(--graphite-50) 5%, transparent);
618
- --highlight: color-mix(in srgb, var(--graphite-50) 10%, transparent);
619
- --shade: color-mix(in srgb, var(--graphite-50) 15%, transparent);
626
+ --line: color-mix(in srgb, var(--graphite-50) 5%, transparent);
627
+ --edge: color-mix(in srgb, var(--graphite-50) 10%, transparent);
628
+ --contour: color-mix(in srgb, var(--graphite-50) 15%, transparent);
629
+ --highlighted: color-mix(in srgb, var(--graphite-50) 10%, transparent);
630
+ --neutral: color-mix(in srgb, var(--graphite-50) 15%, transparent);
620
631
  --shadow-strength: 0.045;
621
632
  --danger: var(--wave-red-500);
622
- --danger-text: oklch(0.738 0.158 16.8); /* #FE7C86, see below */
633
+ --danger-text: oklch(0.749 0.152 16.8); /* #FF828B, see below */
623
634
  --success: var(--wave-green-500);
624
- --success-text: oklch(0.700 0.155 160); /* #14BB7C, see below */
635
+ --success-text: oklch(0.712 0.161 160); /* #00C07E, see below */
625
636
  --warning: var(--wave-orange-500);
626
- --warning-text: oklch(0.732 0.173 57.3); /* #F78711, see below */
637
+ --warning-text: oklch(0.745 0.179 57.3); /* #FE8A04, see below */
627
638
  --info: var(--wave-blue-500);
628
- --info-text: oklch(0.717 0.150 250.4); /* #52A8FD, see below */
639
+ --info-text: oklch(0.730 0.145 250.4); /* #5AADFF, see below */
629
640
  }
630
641
 
631
642
  /* Graphite's status text. Its mid-tone menus need each status a touch lighter
@@ -636,19 +647,19 @@
636
647
  * palette steps. */
637
648
  @media (color-gamut: p3) {
638
649
  .graphite {
639
- --danger-text: oklch(0.743 0.198 16.8);
640
- --success-text: oklch(0.693 0.212 160);
641
- --warning-text: oklch(0.734 0.198 57.3);
642
- --info-text: oklch(0.717 0.162 250.4);
650
+ --danger-text: oklch(0.755 0.190 16.8);
651
+ --success-text: oklch(0.705 0.219 160);
652
+ --warning-text: oklch(0.748 0.205 57.3);
653
+ --info-text: oklch(0.730 0.157 250.4);
643
654
  }
644
655
  }
645
656
 
646
657
  @media (prefers-color-scheme: dark) and (color-gamut: p3) {
647
658
  :where(:root:not(.paper):not(.graphite):not(.ink)) {
648
- --danger-text: oklch(0.743 0.198 16.8);
649
- --success-text: oklch(0.693 0.212 160);
650
- --warning-text: oklch(0.734 0.198 57.3);
651
- --info-text: oklch(0.717 0.162 250.4);
659
+ --danger-text: oklch(0.755 0.190 16.8);
660
+ --success-text: oklch(0.705 0.219 160);
661
+ --warning-text: oklch(0.748 0.205 57.3);
662
+ --info-text: oklch(0.730 0.157 250.4);
652
663
  }
653
664
  }
654
665
 
@@ -664,11 +675,13 @@
664
675
  --elevated: var(--ink-800);
665
676
  --contrast: var(--ink-50);
666
677
  --muted: var(--ink-400);
667
- --soft: var(--ink-600);
678
+ --subtle: var(--ink-600);
668
679
  --secondary: var(--ink-700);
669
- --contour: color-mix(in srgb, var(--ink-50) 5%, transparent);
670
- --highlight: color-mix(in srgb, var(--ink-50) 10%, transparent);
671
- --shade: color-mix(in srgb, var(--ink-50) 15%, transparent);
680
+ --line: color-mix(in srgb, var(--ink-50) 5%, transparent);
681
+ --edge: color-mix(in srgb, var(--ink-50) 10%, transparent);
682
+ --contour: color-mix(in srgb, var(--ink-50) 15%, transparent);
683
+ --highlighted: color-mix(in srgb, var(--ink-50) 10%, transparent);
684
+ --neutral: color-mix(in srgb, var(--ink-50) 15%, transparent);
672
685
  --shadow-strength: 0.045;
673
686
  --danger: var(--wave-red-500);
674
687
  --danger-text: var(--wave-red-400);
@@ -683,7 +696,7 @@
683
696
  /* ---------------------------------------------------------------------------
684
697
  * Tailwind v4 theme mapping — maps CSS variables to Tailwind color tokens.
685
698
  * Consumers MUST include this for Tailwind classes like `bg-primary`,
686
- * `text-muted`, `border-contour` etc. to work.
699
+ * `text-muted`, `border-line` etc. to work.
687
700
  * ------------------------------------------------------------------------- */
688
701
 
689
702
  @theme inline {
@@ -692,10 +705,10 @@
692
705
  --color-surface: var(--surface);
693
706
  --color-elevated: var(--elevated);
694
707
 
695
- /* Contrast / text — `contrast` (highest emphasis) → `muted` → `soft`. */
708
+ /* Contrast / text, strongest to faintest — `contrast` → `muted` → `subtle`. */
696
709
  --color-contrast: var(--contrast);
697
710
  --color-muted: var(--muted);
698
- --color-soft: var(--soft);
711
+ --color-subtle: var(--subtle);
699
712
 
700
713
  /* Inverse — maps the `:root` inverse aliases to utilities. */
701
714
  --color-surface-inverse: var(--surface-inverse);
@@ -706,20 +719,27 @@
706
719
  --color-primary: var(--primary);
707
720
  --color-secondary: var(--secondary);
708
721
  --color-accent: var(--accent);
709
-
710
- /* Form — the theme's foreground (its text colour) made translucent, at
711
- * three strengths, the way a form is drawn: `contour` marks where a shape
712
- * ends: hairline borders and separators. `highlight` marks where attention
713
- * lands: hovered, keyboard-highlighted and open items. `shade` gives a
714
- * control its body: field and track fills and outline-control borders.
715
- * Every theme uses the same strengths: contour 5%, highlight 10%, shade
716
- * 15% of its text colour (Graphite's and Ink's 50, Paper's 950).
717
- * All three are translucent, so each reads the same on any surface beneath
718
- * it, and `shade` doubles as the intense border (layout regions, control
719
- * outlines). `focus` is the brand-themed ring (= primary). */
722
+ --color-selected: var(--selected);
723
+
724
+ /* Lines and fills — the theme's text colour made translucent (Graphite's
725
+ * and Ink's 50, Paper's 950), so each reads the same on any surface beneath
726
+ * it. Every theme uses the same strengths.
727
+ * Lines, one job each: `line` (5%) divides: separators, table rows,
728
+ * hairlines. `edge` (10%) bounds a surface: cards, dialogs, popups, toasts,
729
+ * always as a border (rings are kept for focus and invalid halos).
730
+ * `contour` (15%) outlines a control or a region: outline buttons, badges,
731
+ * toggles, the slider thumb, menubars.
732
+ * Fills, one job each: `highlighted` (10%) marks where attention lands:
733
+ * hovered, keyboard-highlighted and open items. `neutral` (15%) gives a
734
+ * control its body, its look before anything is chosen: field and track
735
+ * fills, unchecked boxes. (Tailwind's own `neutral-50` … `neutral-950`
736
+ * greys are a separate palette Wave doesn't use.) `focus` is the
737
+ * brand-themed ring (= primary). */
738
+ --color-line: var(--line);
739
+ --color-edge: var(--edge);
720
740
  --color-contour: var(--contour);
721
- --color-highlight: var(--highlight);
722
- --color-shade: var(--shade);
741
+ --color-highlighted: var(--highlighted);
742
+ --color-neutral: var(--neutral);
723
743
  --color-focus: var(--focus);
724
744
  --color-scrim: var(--scrim);
725
745
 
@@ -813,6 +833,81 @@
813
833
  }
814
834
  }
815
835
 
836
+ /* `scroll-fade` — fades the edges of anything that scrolls, where there is
837
+ * more to scroll. It's a mask, so the content itself turns transparent and
838
+ * whatever is behind shows through: no colour to match, in any theme. Each
839
+ * fade grows with the first 2.5rem of scroll and shrinks over the last, so it
840
+ * never pops in. Two things drive it, so it works on any scroller:
841
+ * - the element's own scroll position, through scroll-driven animations
842
+ * (Chrome, Edge, Safari 26+): a Combobox list, a plain `overflow-auto` box;
843
+ * - ScrollArea, which reports how far each edge is (`--scroll-area-overflow-*`),
844
+ * so its viewport fades in every browser, Firefox included.
845
+ * A plain scroller in a browser without scroll-driven animations (Firefox,
846
+ * for now) simply doesn't fade. `--scroll-fade-size` sets the length, up to a
847
+ * quarter of the box; the horizontal fade follows the text direction. */
848
+ @property --scroll-fade-top { syntax: "<length>"; inherits: false; initial-value: 0px; }
849
+ @property --scroll-fade-bottom { syntax: "<length>"; inherits: false; initial-value: 0px; }
850
+ @property --scroll-fade-start { syntax: "<length>"; inherits: false; initial-value: 0px; }
851
+ @property --scroll-fade-end { syntax: "<length>"; inherits: false; initial-value: 0px; }
852
+ @utility scroll-fade {
853
+ --scroll-fade-x: to right;
854
+ --scroll-fade-top: min(var(--scroll-fade-size, 2.5rem), var(--scroll-area-overflow-y-start, 0px));
855
+ --scroll-fade-bottom: min(var(--scroll-fade-size, 2.5rem), var(--scroll-area-overflow-y-end, 0px));
856
+ --scroll-fade-start: min(var(--scroll-fade-size, 2.5rem), var(--scroll-area-overflow-x-start, 0px));
857
+ --scroll-fade-end: min(var(--scroll-fade-size, 2.5rem), var(--scroll-area-overflow-x-end, 0px));
858
+ /* Controls laid over an edge (Select's scroll arrows) set
859
+ * `--scroll-fade-inset` to their height: the fade then starts where they
860
+ * end, only while that edge is fading. */
861
+ --scroll-fade-inset-top: min(var(--scroll-fade-inset, 0px), var(--scroll-fade-top) * 1000);
862
+ --scroll-fade-inset-bottom: min(var(--scroll-fade-inset, 0px), var(--scroll-fade-bottom) * 1000);
863
+ /* Each fade at most a quarter of the box, so a short list still keeps a
864
+ * row clear between its two fades. */
865
+ --scroll-fade-top-size: min(var(--scroll-fade-top), 25%);
866
+ --scroll-fade-bottom-size: min(var(--scroll-fade-bottom), 25%);
867
+ --scroll-fade-start-size: min(var(--scroll-fade-start), 25%);
868
+ --scroll-fade-end-size: min(var(--scroll-fade-end), 25%);
869
+ mask-image:
870
+ linear-gradient(to bottom, transparent var(--scroll-fade-inset-top), #000 calc(var(--scroll-fade-inset-top) + var(--scroll-fade-top-size)), #000 calc(100% - var(--scroll-fade-inset-bottom) - var(--scroll-fade-bottom-size)), transparent calc(100% - var(--scroll-fade-inset-bottom))),
871
+ linear-gradient(var(--scroll-fade-x), transparent, #000 var(--scroll-fade-start-size), #000 calc(100% - var(--scroll-fade-end-size)), transparent);
872
+ mask-composite: intersect;
873
+ /* What's scrolled into view (an option picked with the arrow keys) stops
874
+ * clear of whatever is faded at that moment, and nowhere is padded where
875
+ * nothing fades (Firefox's plain lists, an edge scrolled to its end). */
876
+ scroll-padding-block:
877
+ calc(var(--scroll-fade-inset-top) + var(--scroll-fade-top-size))
878
+ calc(var(--scroll-fade-inset-bottom) + var(--scroll-fade-bottom-size));
879
+ @variant rtl {
880
+ --scroll-fade-x: to left;
881
+ }
882
+ @supports (animation-timeline: scroll()) {
883
+ animation-name: scroll-fade-top, scroll-fade-bottom, scroll-fade-start, scroll-fade-end;
884
+ animation-timing-function: linear;
885
+ animation-fill-mode: both;
886
+ animation-timeline: scroll(self block), scroll(self block), scroll(self inline), scroll(self inline);
887
+ animation-range:
888
+ 0 var(--scroll-fade-size, 2.5rem),
889
+ calc(100% - var(--scroll-fade-size, 2.5rem)) 100%,
890
+ 0 var(--scroll-fade-size, 2.5rem),
891
+ calc(100% - var(--scroll-fade-size, 2.5rem)) 100%;
892
+ }
893
+ }
894
+ @keyframes scroll-fade-top {
895
+ from { --scroll-fade-top: 0px; }
896
+ to { --scroll-fade-top: var(--scroll-fade-size, 2.5rem); }
897
+ }
898
+ @keyframes scroll-fade-bottom {
899
+ from { --scroll-fade-bottom: var(--scroll-fade-size, 2.5rem); }
900
+ to { --scroll-fade-bottom: 0px; }
901
+ }
902
+ @keyframes scroll-fade-start {
903
+ from { --scroll-fade-start: 0px; }
904
+ to { --scroll-fade-start: var(--scroll-fade-size, 2.5rem); }
905
+ }
906
+ @keyframes scroll-fade-end {
907
+ from { --scroll-fade-end: var(--scroll-fade-size, 2.5rem); }
908
+ to { --scroll-fade-end: 0px; }
909
+ }
910
+
816
911
  /* `motion-color` — interaction-state transition for controls: smoothly
817
912
  * animates color / background / border / ring (box-shadow) / backdrop blur on hover, focus,
818
913
  * pressed, checked, etc. Runs at the `sm` tier (controls are small + fast). A
@@ -1160,3 +1255,22 @@
1160
1255
  0%, 70%, 100% { opacity: 1; }
1161
1256
  20%, 50% { opacity: 0; }
1162
1257
  }
1258
+
1259
+ /* ---------------------------------------------------------------------------
1260
+ * SwipeDeck — the numbers each card works out its place from change every
1261
+ * frame of a drag (the cards behind lift toward the front). Unregistered,
1262
+ * custom properties inherit, so each change would restyle everything inside
1263
+ * the cards in sight; registered as not inheriting, it stops at the card.
1264
+ * Measured with heavy cards: about 2.5x less style work in Chromium, and
1265
+ * WebKit back to 60 fps from about 30. The card's public hooks
1266
+ * (`--swipe-deck-progress`, `-movement-x`/`-y`, `-place`, `-index`) still
1267
+ * inherit, so what's inside a card can read them. */
1268
+ @property --swipe-deck-lift { syntax: "*"; inherits: false; }
1269
+ @property --swipe-deck-slot { syntax: "*"; inherits: false; }
1270
+ @property --swipe-deck-layout-slot { syntax: "*"; inherits: false; }
1271
+ @property --swipe-deck-layout-x { syntax: "*"; inherits: false; }
1272
+ @property --swipe-deck-layout-y { syntax: "*"; inherits: false; }
1273
+ @property --swipe-deck-layout-rotate { syntax: "*"; inherits: false; }
1274
+ @property --swipe-deck-layout-scale { syntax: "*"; inherits: false; }
1275
+ @property --swipe-deck-enter-scale { syntax: "*"; inherits: false; }
1276
+ @property --swipe-deck-pose { syntax: "*"; inherits: false; }
@@ -1,28 +1,66 @@
1
1
  import { ButtonProps } from "./button.js";
2
2
  import * as React from "react";
3
3
  //#region src/swipe-deck.d.ts
4
- type SwipeDirection = "left" | "right" | "up" | "down";
4
+ type SwipeDeckDirection = "left" | "right" | "up" | "down";
5
5
  type SwipeDeckLayout = "stack" | "fan" | "cascade" | "pile";
6
6
  interface SwipeDeckSwipeDetails {
7
- /** The `value` of the card that left. */
7
+ /** The `value` of the card. */
8
8
  value: string;
9
- /** The direction it left in. */
10
- direction: SwipeDirection;
9
+ /** The direction it leaves in. */
10
+ direction: SwipeDeckDirection;
11
11
  }
12
+ /** What sent a card off: a drag or throw, an arrow key, or a `SwipeDeckAction`. */
13
+ type SwipeDeckSwipeEventReason = SwipeDeckSwipeCause["reason"];
14
+ type SwipeDeckSwipeCause = {
15
+ reason: "swipe";
16
+ event: PointerEvent;
17
+ } | {
18
+ reason: "keyboard";
19
+ event: KeyboardEvent;
20
+ } | {
21
+ reason: "action-press";
22
+ event: MouseEvent;
23
+ };
24
+ /**
25
+ * `onSwipe`'s second argument: the reason and native event behind a swipe, and
26
+ * a way to cancel it. The parts of Base UI's own event details that apply.
27
+ */
28
+ type SwipeDeckSwipeEventDetails = SwipeDeckSwipeCause & {
29
+ /**
30
+ * Keeps the card in front: a dragged card springs back, and a key or
31
+ * action does nothing.
32
+ */
33
+ cancel: () => void;
34
+ /** Whether `cancel` has been called. */
35
+ readonly isCanceled: boolean;
36
+ };
12
37
  type SwipeDeckProps = React.ComponentProps<"div"> & {
13
38
  /**
14
- * Called once a card has left the deck. Remove it from your list to discard
15
- * it, or move it to the end of your list to cycle it to the back.
39
+ * Called the moment a card is sent off, before it leaves: count it here, or
40
+ * call `eventDetails.cancel()` to keep it in front. To cycle the deck, move
41
+ * the card to the end of your list here, and it slides back under the pile.
42
+ * Don't remove it here: it would vanish mid-exit, and `onSwipeComplete`
43
+ * would never come.
16
44
  */
17
- onSwipe?: (details: SwipeDeckSwipeDetails) => void;
45
+ onSwipe?: (details: SwipeDeckSwipeDetails, eventDetails: SwipeDeckSwipeEventDetails) => void;
46
+ /**
47
+ * Called once a card is done leaving: faded out, or (moved to the back in
48
+ * `onSwipe`) on its way under the pile. Remove it from your list here; a
49
+ * card your list keeps comes back, in front or wherever you moved it.
50
+ * Return a promise, such as a save, to keep a faded card out until it
51
+ * settles; a card sliding under the pile is back already.
52
+ */
53
+ onSwipeComplete?: (details: SwipeDeckSwipeDetails) => void | Promise<unknown>;
18
54
  /** The directions a card can leave in. Defaults to left and right. */
19
- directions?: readonly SwipeDirection[];
55
+ directions?: readonly SwipeDeckDirection[];
20
56
  /** Stops cards leaving by pointer, keyboard or action. */
21
57
  disabled?: boolean;
22
58
  /**
23
59
  * How the cards behind the front one sit: `stack` peeks them above it, `fan`
24
60
  * turns them out like a hand of cards, `cascade` steps them up and to the
25
- * side, and `pile` scatters them. Defaults to `stack`.
61
+ * side, and `pile` scatters them. Defaults to `fan`. `fan` and `cascade`
62
+ * open toward the end of the line: right in left-to-right text, left in
63
+ * right-to-left.
26
64
  */
27
65
  layout?: SwipeDeckLayout;
28
66
  /** How far the cards behind spread out, from 0 (not at all) to 2. Defaults to 0.5. */
@@ -33,49 +71,67 @@ type SwipeDeckProps = React.ComponentProps<"div"> & {
33
71
  visible?: number;
34
72
  };
35
73
  type SwipeDeckItemProps = React.ComponentProps<"div"> & {
36
- /** Names the card in `onSwipe`. Use the same id as its `key`. */
74
+ /**
75
+ * Names the card in `onSwipe`. Use the same id as its `key`, unique among
76
+ * the deck's cards, those still leaving included: a card dealt again is a
77
+ * new card and needs a new value.
78
+ */
37
79
  value: string;
38
80
  };
39
81
  type SwipeDeckEmptyProps = React.ComponentProps<"div">;
40
82
  type SwipeDeckActionProps = ButtonProps & {
41
83
  /** The direction the front card leaves in when pressed. */
42
- direction: SwipeDirection;
84
+ direction: SwipeDeckDirection;
43
85
  };
44
86
  /**
45
87
  * A pile of cards people swipe away: drag or throw the front card, press an
46
88
  * arrow key while the deck is focused, or use a `SwipeDeckAction`. A let-go
47
- * card whose position and throw carry it past a quarter of its width leaves:
48
- * it slides out the way it is heading until clear of the stack, held by a
89
+ * card whose position and throw carry it past a quarter of its width (or of
90
+ * its height, up and down) leaves:
91
+ * it slides out the way it is heading until clear of the pile, held by a
49
92
  * spring that a hard throw runs out against, then fades. Short of that, it
50
- * springs back. Your list stays the source of truth — `onSwipe` tells you
51
- * which card left and which way, and you remove it or move it to the back.
93
+ * springs back. Pressed, a card lifts under the pointer; dragged, it tilts
94
+ * with the throw.
95
+ *
96
+ * Your list stays the source of truth. `onSwipe` comes the moment a card is
97
+ * sent off: count it there, or cancel to keep it. `onSwipeComplete` comes once
98
+ * it has gone: remove it from your list then, not in `onSwipe`, where removing
99
+ * it would cut its exit short. To cycle the deck instead, move the card to
100
+ * the end of your list in `onSwipe`, and it slides back under the pile. To
101
+ * undo a swipe, put the card back at the start of your list.
102
+ *
103
+ * The cards are the deck's own children, in your list's order. A long list
104
+ * needn't be rendered whole: the deck shows `visible` cards, so the next few
105
+ * dozen are plenty.
52
106
  *
53
107
  * End the deck with a `SwipeDeckEmpty`: it lies under the last card, so
54
- * dragging that card shows what comes next. The deck carries `data-empty`
55
- * once no card is in front (the last one leaving, or none left), for styles
56
- * of your own.
108
+ * dragging that card shows what comes next.
109
+ *
110
+ * Styling hooks: `data-layout`, `data-disabled`, and `data-empty` once no
111
+ * card is in front (the last one leaving, or none left). CSS variables:
112
+ * `--swipe-deck-card-width` / `-height`, the size of the largest card.
57
113
  *
58
114
  * Give the deck an `aria-label`. Screen readers hear the next card after each
59
115
  * swipe: its `aria-label` when it has one, otherwise all of its text.
60
116
  */
61
- declare function SwipeDeck({ onSwipe, directions, disabled, layout, spread, depth, visible, className, style, onKeyDown, children, ref, ...props }: SwipeDeckProps): React.JSX.Element;
117
+ declare function SwipeDeck({ onSwipe, onSwipeComplete, directions, disabled, layout, spread, depth, visible, className, style, onKeyDown, children, ref, ...props }: SwipeDeckProps): React.JSX.Element;
62
118
  /**
63
- * One card in the deck. It sits in the deck's first grid cell with every other
64
- * card, so the deck takes the size of its largest card.
119
+ * One card in the deck, a direct child of it. It sits in the deck's first grid
120
+ * cell with every other card, so the deck takes the size of its largest card.
65
121
  *
66
122
  * Styling hooks: `data-front` on the top card, `data-swiping` while it is
67
- * dragged, `data-leaving` from the moment it leaves, `data-ending-style` once
68
- * it is clear of the stack and fading (the `motion-fade-lg` recipe), and
69
- * `data-swipe-direction` for the way it leans or leaves. CSS variables:
70
- * `--swipe-deck-index` (its place in the pile, 0 at the front),
71
- * `--swipe-deck-place` (that place as it springs from one to the next, less
72
- * the lift while the front card is dragged out, as `--swipe-deck-slot`),
123
+ * dragged, `data-leaving` from the moment it leaves, `data-starting-style`
124
+ * while it comes in and `data-ending-style` once it is clear of the pile and
125
+ * fading (the `motion-fade-lg` recipe), and `data-swipe-direction` for the
126
+ * way it leans or leaves. CSS variables, which what's inside the card can
127
+ * read too: `--swipe-deck-index` (its place in the pile, 0 at the front),
128
+ * `--swipe-deck-place` (that place as it springs from one to the next),
73
129
  * `--swipe-deck-movement-x` / `-y` in px, and `--swipe-deck-progress` from 0
74
130
  * at rest to 1 where letting go makes the card leave.
75
131
  *
76
- * The card owns its translate, rotate and scale, so its motion recipe can only
77
- * be a `motion-fade-*` one: a recipe that transitions those would lag every
78
- * drag.
132
+ * The card owns its translate, rotate, scale and transform, so its motion
133
+ * recipe can only be a `motion-fade-*` one: a recipe that transitions those
134
+ * would lag every drag.
79
135
  */
80
136
  declare function SwipeDeckItem({ value, className, style, ref, onPointerDown, onClickCapture, onDragStart, ...props }: SwipeDeckItemProps): React.JSX.Element;
81
137
  /**
@@ -84,15 +140,15 @@ declare function SwipeDeckItem({ value, className, style, ref, onPointerDown, on
84
140
  * their size, inside a dashed outline of their shape (`data-slot`
85
141
  * `swipe-deck-empty-outline`, to restyle or hide). It is drawn as soon as one
86
142
  * card is left and has come to rest, under that card, so dragging it shows
87
- * what comes next; it
88
- * can be reached (Tab, clicks) and screen readers hear it (its `aria-label`,
89
- * or its text) once that last card has gone too.
143
+ * what comes next; it can be reached (Tab, clicks) and screen readers hear it
144
+ * (its `aria-label`, or its text) once that last card has gone too.
90
145
  */
91
146
  declare function SwipeDeckEmpty({ className, children, ref, ...props }: SwipeDeckEmptyProps): React.JSX.Element;
92
147
  /**
93
148
  * A button that sends the front card off in `direction`: the way to swipe
94
- * without dragging. Disabled when the deck is empty or can't go that way.
149
+ * without dragging. Disabled (and still focusable) when the deck is disabled,
150
+ * has no card in front, or can't go that way.
95
151
  */
96
- declare function SwipeDeckAction({ direction, disabled, onClick, ...props }: SwipeDeckActionProps): React.JSX.Element;
152
+ declare function SwipeDeckAction({ direction, disabled, onClick, onKeyDown, ...props }: SwipeDeckActionProps): React.JSX.Element;
97
153
  //#endregion
98
- export { SwipeDeck, SwipeDeckAction, type SwipeDeckActionProps, SwipeDeckEmpty, type SwipeDeckEmptyProps, SwipeDeckItem, type SwipeDeckItemProps, type SwipeDeckLayout, type SwipeDeckProps, type SwipeDeckSwipeDetails, type SwipeDirection };
154
+ export { SwipeDeck, SwipeDeckAction, type SwipeDeckActionProps, type SwipeDeckDirection, SwipeDeckEmpty, type SwipeDeckEmptyProps, SwipeDeckItem, type SwipeDeckItemProps, type SwipeDeckLayout, type SwipeDeckProps, type SwipeDeckSwipeDetails, type SwipeDeckSwipeEventDetails, type SwipeDeckSwipeEventReason };