@greese/ui 1.1.0 → 1.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.
Files changed (194) hide show
  1. package/README.md +19 -14
  2. package/dist/_maps.scss +1 -1
  3. package/dist/_tokens-base.scss +30 -6
  4. package/dist/_tokens.scss +31 -2
  5. package/dist/_utilities-table.scss +1615 -0
  6. package/dist/clipboard.d.ts +18 -0
  7. package/dist/clipboard.js +62 -0
  8. package/dist/css/Button.css +2 -2
  9. package/dist/css/Carousel.css +145 -47
  10. package/dist/css/Checkbox.css +2 -2
  11. package/dist/css/Chip.css +38 -8
  12. package/dist/css/ChoiceGroup.css +42 -1
  13. package/dist/css/Collapsible.css +1 -0
  14. package/dist/css/ColorPicker.css +84 -0
  15. package/dist/css/Combobox.css +2 -2
  16. package/dist/css/Dialog.css +24 -22
  17. package/dist/css/EditableField.css +219 -0
  18. package/dist/css/Input.css +11 -2
  19. package/dist/css/Menu.css +9 -0
  20. package/dist/css/NumberField.css +53 -8
  21. package/dist/css/NumberStepper.css +9 -0
  22. package/dist/css/OtpField.css +1 -1
  23. package/dist/css/Radio.css +2 -2
  24. package/dist/css/ScrollCarousel.css +225 -0
  25. package/dist/css/SegmentedControl.css +2 -2
  26. package/dist/css/Select.css +3 -2
  27. package/dist/css/Slider.css +45 -5
  28. package/dist/css/Switch.css +6 -6
  29. package/dist/css/Text.css +14 -14
  30. package/dist/css/Textarea.css +1 -1
  31. package/dist/css/core.css +36 -6
  32. package/dist/css/resets.css +162 -0
  33. package/dist/css.d.ts +2 -0
  34. package/dist/css.js +2 -0
  35. package/dist/greese.css +978 -138
  36. package/dist/index.d.ts +2 -1
  37. package/dist/index.js +2 -1
  38. package/dist/scss/components/_Accordion.scss +2 -0
  39. package/dist/scss/components/_ActionSelect.scss +2 -0
  40. package/dist/scss/components/_Alert.scss +2 -0
  41. package/dist/scss/components/_AppBar.scss +2 -0
  42. package/dist/scss/components/_AspectRatio.scss +2 -0
  43. package/dist/scss/components/_AutoLayout.scss +2 -0
  44. package/dist/scss/components/_Avatar.scss +2 -0
  45. package/dist/scss/components/_Badge.scss +2 -0
  46. package/dist/scss/components/_Box.scss +2 -0
  47. package/dist/scss/components/_Breadcrumbs.scss +2 -0
  48. package/dist/scss/components/_Button.scss +2 -0
  49. package/dist/scss/components/_ButtonGroup.scss +2 -0
  50. package/dist/scss/components/_Card.scss +2 -0
  51. package/dist/scss/components/_Carousel.scss +2 -0
  52. package/dist/scss/components/_Center.scss +2 -0
  53. package/dist/scss/components/_ChatBubble.scss +2 -0
  54. package/dist/scss/components/_Checkbox.scss +2 -0
  55. package/dist/scss/components/_Chip.scss +2 -0
  56. package/dist/scss/components/_ChoiceGroup.scss +2 -0
  57. package/dist/scss/components/_Code.scss +2 -0
  58. package/dist/scss/components/_Collapsible.scss +2 -0
  59. package/dist/scss/components/_ColorPicker.scss +2 -0
  60. package/dist/scss/components/_Combobox.scss +2 -0
  61. package/dist/scss/components/_Container.scss +2 -0
  62. package/dist/scss/components/_Countdown.scss +2 -0
  63. package/dist/scss/components/_DateField.scss +2 -0
  64. package/dist/scss/components/_Dialog.scss +2 -0
  65. package/dist/scss/components/_Diff.scss +2 -0
  66. package/dist/scss/components/_Divider.scss +2 -0
  67. package/dist/scss/components/_Dock.scss +2 -0
  68. package/dist/scss/components/_EditableField.scss +2 -0
  69. package/dist/scss/components/_Field.scss +2 -0
  70. package/dist/scss/components/_Fieldset.scss +2 -0
  71. package/dist/scss/components/_FileInput.scss +2 -0
  72. package/dist/scss/components/_Footer.scss +2 -0
  73. package/dist/scss/components/_Grid.scss +2 -0
  74. package/dist/scss/components/_Header.scss +2 -0
  75. package/dist/scss/components/_Heading.scss +2 -0
  76. package/dist/scss/components/_Icon.scss +2 -0
  77. package/dist/scss/components/_IconButton.scss +2 -0
  78. package/dist/scss/components/_Image.scss +2 -0
  79. package/dist/scss/components/_Indicator.scss +2 -0
  80. package/dist/scss/components/_Input.scss +2 -0
  81. package/dist/scss/components/_Key.scss +2 -0
  82. package/dist/scss/components/_Link.scss +2 -0
  83. package/dist/scss/components/_List.scss +2 -0
  84. package/dist/scss/components/_Media.scss +2 -0
  85. package/dist/scss/components/_Menu.scss +2 -0
  86. package/dist/scss/components/_Motion.scss +2 -0
  87. package/dist/scss/components/_NumberField.scss +2 -0
  88. package/dist/scss/components/_NumberStepper.scss +2 -0
  89. package/dist/scss/components/_OtpField.scss +2 -0
  90. package/dist/scss/components/_PageTransition.scss +2 -0
  91. package/dist/scss/components/_Pagination.scss +2 -0
  92. package/dist/scss/components/_Popover.scss +2 -0
  93. package/dist/scss/components/_Progress.scss +2 -0
  94. package/dist/scss/components/_RadialProgress.scss +2 -0
  95. package/dist/scss/components/_Radio.scss +2 -0
  96. package/dist/scss/components/_Rating.scss +2 -0
  97. package/dist/scss/components/_ScrollCarousel.scss +2 -0
  98. package/dist/scss/components/_ScrollSpy.scss +2 -0
  99. package/dist/scss/components/_SegmentedControl.scss +2 -0
  100. package/dist/scss/components/_Select.scss +2 -0
  101. package/dist/scss/components/_Sidebar.scss +2 -0
  102. package/dist/scss/components/_Skeleton.scss +2 -0
  103. package/dist/scss/components/_Slider.scss +2 -0
  104. package/dist/scss/components/_Spacer.scss +2 -0
  105. package/dist/scss/components/_SpeedDial.scss +2 -0
  106. package/dist/scss/components/_Spinner.scss +2 -0
  107. package/dist/scss/components/_Stack.scss +2 -0
  108. package/dist/scss/components/_Stat.scss +2 -0
  109. package/dist/scss/components/_Status.scss +2 -0
  110. package/dist/scss/components/_Steps.scss +2 -0
  111. package/dist/scss/components/_Swap.scss +2 -0
  112. package/dist/scss/components/_Switch.scss +2 -0
  113. package/dist/scss/components/_Table.scss +2 -0
  114. package/dist/scss/components/_Tabs.scss +2 -0
  115. package/dist/scss/components/_Text.scss +2 -0
  116. package/dist/scss/components/_TextRotate.scss +2 -0
  117. package/dist/scss/components/_Textarea.scss +2 -0
  118. package/dist/scss/components/_ThemeProvider.scss +2 -0
  119. package/dist/scss/components/_Tile.scss +2 -0
  120. package/dist/scss/components/_Toast.scss +2 -0
  121. package/dist/scss/components/_ToggleButton.scss +2 -0
  122. package/dist/scss/components/_Tooltip.scss +2 -0
  123. package/dist/scss/components/_Type.scss +2 -0
  124. package/dist/scss/components/_Visibility.scss +2 -0
  125. package/dist/scss/components/_VisuallyHidden.scss +2 -0
  126. package/dist/shapes.d.ts +12 -1
  127. package/dist/shapes.js +11 -10
  128. package/dist/style-manifest.json +18830 -132
  129. package/dist/theme.js +6 -1
  130. package/dist/themeFiles/index.d.ts +9 -0
  131. package/dist/themeFiles/index.js +9 -0
  132. package/dist/themeFiles/scssModule.d.ts +100 -0
  133. package/dist/themeFiles/scssModule.js +384 -0
  134. package/dist/themeFiles/spec.d.ts +198 -0
  135. package/dist/themeFiles/spec.js +877 -0
  136. package/dist/tokens.css +30 -1
  137. package/dist/typography.js +1 -1
  138. package/dist/utilities.css +435 -0
  139. package/dist/utilities.d.ts +8 -0
  140. package/dist/utilities.js +75 -0
  141. package/package.json +20 -1
  142. package/src/components/AutoLayout/AutoLayout.scss +2 -26
  143. package/src/components/Carousel/Carousel.scss +122 -126
  144. package/src/components/Checkbox/Checkbox.scss +2 -2
  145. package/src/components/Chip/Chip.scss +63 -8
  146. package/src/components/ChoiceGroup/ChoiceGroup.scss +19 -1
  147. package/src/components/Collapsible/Collapsible.scss +3 -0
  148. package/src/components/ColorPicker/ColorPicker.scss +95 -0
  149. package/src/components/Combobox/Combobox.scss +4 -2
  150. package/src/components/Dialog/Dialog.scss +24 -7
  151. package/src/components/EditableField/EditableField.scss +275 -0
  152. package/src/components/Grid/Grid.scss +2 -70
  153. package/src/components/Input/Input.scss +29 -0
  154. package/src/components/Menu/Menu.scss +18 -0
  155. package/src/components/NumberField/NumberField.scss +56 -9
  156. package/src/components/NumberStepper/NumberStepper.scss +13 -0
  157. package/src/components/Radio/Radio.scss +2 -2
  158. package/src/components/ScrollCarousel/ScrollCarousel.scss +142 -0
  159. package/src/components/Select/Select.scss +4 -0
  160. package/src/components/Slider/Slider.scss +51 -6
  161. package/src/components/Stack/Stack.scss +2 -48
  162. package/src/components/Switch/Switch.scss +9 -6
  163. package/src/components/Text/Text.scss +5 -2
  164. package/src/styles/_breakpoint-vars.scss +17 -11
  165. package/src/styles/_controls.scss +2 -2
  166. package/src/styles/_core.scss +1 -0
  167. package/src/styles/_kit.scss +483 -56
  168. package/src/styles/_layout-props.scss +197 -0
  169. package/src/styles/_manifest.scss +53 -23
  170. package/src/styles/_mixins.scss +53 -15
  171. package/src/styles/_slides.scss +112 -0
  172. package/src/styles/_utilities.scss +104 -0
  173. package/src/styles/breakpoints.test.ts +21 -1
  174. package/src/styles/entryPoints.test.ts +25 -2
  175. package/src/styles/index.scss +3 -1
  176. package/src/styles/kit.test.ts +171 -1
  177. package/src/styles/public/_breakpoints.scss +43 -0
  178. package/src/styles/public/_slides.scss +3 -0
  179. package/src/styles/public/_utilities.scss +3 -0
  180. package/src/styles/resets.scss +7 -0
  181. package/src/styles/utilities.test.ts +39 -0
  182. package/themes/bevel.css +187 -187
  183. package/themes/bevel.scss +960 -958
  184. package/themes/extrude.css +42 -42
  185. package/themes/folio.css +200 -200
  186. package/themes/folio.scss +880 -878
  187. package/themes/outline.css +18 -18
  188. package/themes/pebble.css +8 -8
  189. package/themes/ridge.css +188 -188
  190. package/themes/ridge.scss +820 -818
  191. package/themes/soft.css +11 -11
  192. package/themes/ticket.css +180 -180
  193. package/themes/ticket.scss +881 -879
  194. package/themes/tilt.css +25 -25
@@ -38,6 +38,10 @@ $_theme: null;
38
38
  // Tokens declared with `tokens(…, $extend: …)`, by group, so `token()` and
39
39
  // `component()` variables accept them from then on.
40
40
  $_extended: ();
41
+
42
+ // What `tokens()` has declared, by theme name ('' for the page): the merged
43
+ // overrides and extensions, so a later call adds to them.
44
+ $_token-calls: ();
41
45
  $_block: null;
42
46
  $_part: null;
43
47
  $_scheme-attr: 'data-greese-scheme';
@@ -145,11 +149,14 @@ $_eases: ('linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'in', 'out', '
145
149
 
146
150
  @function _block-name($name) {
147
151
  $name: '#{$name}';
148
- $known: map.has-key(manifest.$blocks, $name);
149
- $checked: _check(
150
- $known,
151
- 'no component `#{$name}`. Components are PascalCase, as in the docs: #{_join(map.keys(manifest.$blocks))}.'
152
- );
152
+
153
+ // The message lists every component, so it's only built for an unknown one.
154
+ @if not map.has-key(manifest.$blocks, $name) {
155
+ $checked: _check(
156
+ false,
157
+ 'no component `#{$name}`. Components are PascalCase, as in the docs: #{_join(map.keys(manifest.$blocks))}.'
158
+ );
159
+ }
153
160
 
154
161
  @return string.unquote($name);
155
162
  }
@@ -367,23 +374,60 @@ $_eases: ('linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'in', 'out', '
367
374
 
368
375
  /// Styles for one component, in `@layer greese.overrides`. Nest `part()`,
369
376
  /// `modifier()`, `vars()`, `motion()` and plain declarations inside.
370
- @mixin component($name) {
377
+ ///
378
+ /// The styles belong to one of a theme's layers, `$layer`: `shape` (the
379
+ /// default: a component's own look, as shape themes write it), `color` or
380
+ /// `motion` (`motion()` inside is always motion). `.greese-reset-<layer>`
381
+ /// stops them, and `reset()` leaves them out of the build.
382
+ @mixin component($name, $layer: shape) {
371
383
  $block: _block-name($name);
384
+ $layer: list.nth(_check-layers(($layer)), 1);
372
385
  $outer-block: $_block;
373
386
  $outer-part: $_part;
387
+ $outer-layer: $_layer;
374
388
  $_block: $block !global;
375
389
  $_part: null !global;
390
+ $_layer: $layer !global;
391
+
392
+ // Once `reset()` has taken the layer, the rule matches nothing (`:not(*)`),
393
+ // but `motion()` inside still writes its own, which is another layer's.
394
+ $dead: '';
395
+
396
+ @if _is-reset($block, $layer) {
397
+ $dead: ':not(*)';
398
+ }
376
399
 
377
400
  @include _layer-order;
378
401
 
379
402
  @layer greese.overrides {
380
- #{_scoped('.greese-#{$block}')} {
403
+ #{_scoped('.greese-#{$block}')}#{reset-guard($layer)}#{$dead} {
381
404
  @content;
382
405
  }
383
406
  }
384
407
 
408
+ // After the content, so a `reset()` inside doesn't count its own rule.
409
+ @if $dead == '' {
410
+ $_themed: _add($_themed, $block, $layer) !global;
411
+ }
412
+
385
413
  $_block: $outer-block !global;
386
414
  $_part: $outer-part !global;
415
+ $_layer: $outer-layer !global;
416
+ }
417
+
418
+ /// `$text` with every `$find` replaced.
419
+ @function _replace($text, $find, $with) {
420
+ $text: '#{$text}';
421
+ $at: string.index($text, $find);
422
+
423
+ @while $at {
424
+ $before: string.slice($text, 1, $at - 1);
425
+ $after: string.slice($text, $at + string.length($find));
426
+ $text: $before + $with + $after;
427
+ $at: string.index($text, $find);
428
+ }
429
+
430
+ @return $text;
387
431
  }
388
432
 
389
433
  @function _require-component($mixin) {
@@ -478,6 +522,25 @@ $_eases: ('linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'in', 'out', '
478
522
  /// `delay` and `ease` tune it.
479
523
  @mixin motion($options) {
480
524
  $checked: _require-component('motion');
525
+
526
+ // Motion is its own theme layer: the rule is the component's, guarded for
527
+ // motion rather than the component's layer, and absent once reset.
528
+ @if not _is-reset($_block, motion) {
529
+ $_themed: _add($_themed, $_block, motion) !global;
530
+ $guard: reset-guard($_layer);
531
+ $selector: _replace(
532
+ _replace(&, '#{$guard}:not(*)', $guard),
533
+ $guard,
534
+ reset-guard(motion)
535
+ );
536
+
537
+ @at-root #{$selector} {
538
+ @include _motion($options);
539
+ }
540
+ }
541
+ }
542
+
543
+ @mixin _motion($options) {
481
544
  $known: (enter, keyframes, exit, duration, delay, ease, distance, durations, easings);
482
545
 
483
546
  @each $key, $unused in $options {
@@ -654,42 +717,9 @@ $_eases: ('linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'in', 'out', '
654
717
  }
655
718
  }
656
719
 
657
- /// Tunes every `greese` prop entrance on the page (or in the theme), and
658
- /// assigns the theme's motion roles (ADR 0025):
659
- ///
660
- /// duration, delay, ease, distance, stagger every `greese` entrance's pacing
661
- /// theme the entrance `greese="theme"` plays (base: fade-up)
662
- /// content a tab panel's, an opening accordion's children (base: a short rise)
663
- /// rows a table's rows, one after another (base: none)
664
- /// toast, backdrop their entrances
665
- /// overlay every overlay's entrance but a drawer's; `dialog`, `popover`
666
- /// (menus and speed-dial actions too), `tooltip` and `drawer`
667
- /// (the sidebar too) for one kind
668
- /// overlay-exit, dialog-exit, popover-exit, tooltip-exit, drawer-exit
669
- /// how they leave: an exit by name
670
- /// content-duration, rows-duration, overlay-duration, toast-duration,
671
- /// backdrop-duration, exit-duration a duration token (`fast`,
672
- /// `normal`, `slow`) or a time
673
- /// enter-ease, exit-ease an easing token (`standard`,
674
- /// `enter`, `exit`), an ease name or a curve
675
- /// page, page-back a page transition's entrance,
676
- /// going forward and back (ADR 0029; base: fade-in)
677
- /// page-exit, page-back-exit how the old page leaves: an exit
678
- /// by name (base: fade-out)
679
- /// page-duration, share-duration a duration token or a time:
680
- /// the page's, and a shared element's morph
681
- /// page-ease, share-ease an easing, as above
682
- /// page-stagger seconds between a live exit's
683
- /// members (base: the entrance stagger)
684
- /// page-exit-order `reverse` (base), `document` or
685
- /// `together`: the order a live exit's members leave in
686
- /// page-mode `auto` (base), `snapshot` or
687
- /// `live`: how a page change plays
688
- ///
689
- /// `motion-defaults((content: wipe-open-x, dialog: iris-in, dialog-exit: iris-out,
690
- /// content-duration: slow))`. A role points at an effect or a token: the
691
- /// effects keep their keyframes and the tokens their values.
692
- @mixin motion-defaults($options) {
720
+ /// The motion roles `motion-defaults()` assigns, by kind, and `known`: every
721
+ /// option. A reset sets the variables of all of them back.
722
+ @function _motion-lists() {
693
723
  $pacing: (duration, delay, ease, distance, stagger);
694
724
  $entrances: (
695
725
  theme,
@@ -701,9 +731,19 @@ $_eases: ('linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'in', 'out', '
701
731
  dialog,
702
732
  popover,
703
733
  tooltip,
704
- drawer
734
+ drawer,
735
+ carousel,
736
+ carousel-back
737
+ );
738
+ $exits: (
739
+ overlay-exit,
740
+ dialog-exit,
741
+ popover-exit,
742
+ tooltip-exit,
743
+ drawer-exit,
744
+ carousel-exit,
745
+ carousel-back-exit
705
746
  );
706
- $exits: (overlay-exit, dialog-exit, popover-exit, tooltip-exit, drawer-exit);
707
747
 
708
748
  // A page transition's roles (ADR 0029): its entrances and exits are
709
749
  // `@keyframes` names, played on the view transition's snapshots.
@@ -728,6 +768,7 @@ $_eases: ('linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'in', 'out', '
728
768
  toast-duration,
729
769
  backdrop-duration,
730
770
  exit-duration,
771
+ carousel-duration,
731
772
  page-duration,
732
773
  share-duration
733
774
  );
@@ -743,15 +784,8 @@ $_eases: ('linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'in', 'out', '
743
784
  list.join((page-stagger), map.keys($page-keywords))
744
785
  )
745
786
  );
746
- $root: ':root';
747
787
 
748
- @include _layer-order;
749
-
750
- @if $_theme {
751
- $root: "[#{$_theme-attr}='#{$_theme}']";
752
- }
753
-
754
- $lists: (
788
+ @return (
755
789
  known: $known,
756
790
  entrances: $entrances,
757
791
  exits: $exits,
@@ -759,8 +793,68 @@ $_eases: ('linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'in', 'out', '
759
793
  page-exits: $page-exits,
760
794
  page-keywords: $page-keywords,
761
795
  durations: $durations,
762
- eases: $eases,
796
+ eases: $eases
763
797
  );
798
+ }
799
+
800
+ /// Tunes every `greese` prop entrance on the page (or in the theme), and
801
+ /// assigns the theme's motion roles (ADR 0025):
802
+ ///
803
+ /// duration, delay, ease, distance, stagger every `greese` entrance's pacing
804
+ /// theme the entrance `greese="theme"` plays (base: fade-up)
805
+ /// content a tab panel's, an opening accordion's children (base: a short rise)
806
+ /// rows a table's rows, one after another (base: none)
807
+ /// toast, backdrop their entrances
808
+ /// overlay every overlay's entrance but a drawer's; `dialog`, `popover`
809
+ /// (menus and speed-dial actions too), `tooltip` and `drawer`
810
+ /// (the sidebar too) for one kind
811
+ /// overlay-exit, dialog-exit, popover-exit, tooltip-exit, drawer-exit
812
+ /// how they leave: an exit by name
813
+ /// carousel, carousel-back a Carousel's arriving slide,
814
+ /// going forward and back (base: a slide the way it moves)
815
+ /// carousel-exit, carousel-back-exit its leaving slide: an exit by
816
+ /// name (base: a slide out the way it moves)
817
+ /// content-duration, rows-duration, overlay-duration, toast-duration,
818
+ /// backdrop-duration, exit-duration, carousel-duration a duration token (`fast`,
819
+ /// `normal`, `slow`) or a time
820
+ /// enter-ease, exit-ease an easing token (`standard`,
821
+ /// `enter`, `exit`), an ease name or a curve
822
+ /// page, page-back a page transition's entrance,
823
+ /// going forward and back (ADR 0029; base: fade-in)
824
+ /// page-exit, page-back-exit how the old page leaves: an exit
825
+ /// by name (base: fade-out)
826
+ /// page-duration, share-duration a duration token or a time:
827
+ /// the page's, and a shared element's morph
828
+ /// page-ease, share-ease an easing, as above
829
+ /// page-stagger seconds between a live exit's
830
+ /// members (base: the entrance stagger)
831
+ /// page-exit-order `reverse` (base), `document` or
832
+ /// `together`: the order a live exit's members leave in
833
+ /// page-mode `auto` (base), `snapshot` or
834
+ /// `live`: how a page change plays
835
+ ///
836
+ /// `motion-defaults((content: wipe-open-x, dialog: iris-in, dialog-exit: iris-out,
837
+ /// content-duration: slow))`. A role points at an effect or a token: the
838
+ /// effects keep their keyframes and the tokens their values.
839
+ @mixin motion-defaults($options) {
840
+ // An empty map writes nothing (a theme layout's untouched files).
841
+ @if list.length($options) > 0 {
842
+ @include _motion-defaults($options);
843
+ }
844
+ }
845
+
846
+ @mixin _motion-defaults($options) {
847
+ $lists: _motion-lists();
848
+ $entrances: map.get($lists, entrances);
849
+ $page-entrances: map.get($lists, page-entrances);
850
+ $page-exits: map.get($lists, page-exits);
851
+ $root: ':root';
852
+
853
+ @include _layer-order;
854
+
855
+ @if $_theme {
856
+ $root: "[#{$_theme-attr}='#{$_theme}']";
857
+ }
764
858
 
765
859
  @layer greese.overrides {
766
860
  #{$root} {
@@ -831,6 +925,13 @@ $_eases: ('linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'in', 'out', '
831
925
  /// they move with the page. Every named element is a snapshot of its own:
832
926
  /// name sections, not every item of a long list.
833
927
  @mixin page-transitions($rules) {
928
+ // An empty map writes nothing (a theme layout's untouched files).
929
+ @if list.length($rules) > 0 {
930
+ @include _page-transitions($rules);
931
+ }
932
+ }
933
+
934
+ @mixin _page-transitions($rules) {
834
935
  $known: (enter, exit, duration, delay, ease);
835
936
  $during: ':root:active-view-transition-type(greese-page)';
836
937
  $prefix: base;
@@ -1209,6 +1310,46 @@ $_dark-ink: #11171c;
1209
1310
  @return $known;
1210
1311
  }
1211
1312
 
1313
+ /// A token map with kebab-case group and key names, so two calls' maps line up.
1314
+ @function _token-map($map) {
1315
+ $out: ();
1316
+
1317
+ @each $group, $values in $map {
1318
+ $group: '#{_kebab($group)}';
1319
+ $keys: map.get($out, $group) or ();
1320
+
1321
+ @each $key, $value in $values {
1322
+ $keys: map.set($keys, '#{_kebab($key)}', $value);
1323
+ }
1324
+
1325
+ $out: map.set($out, $group, $keys);
1326
+ }
1327
+
1328
+ @return $out;
1329
+ }
1330
+
1331
+ /// `$b` over `$a`, a key at a time: a color pair replaces the pair before it.
1332
+ @function _merge-tokens($a, $b) {
1333
+ @each $group, $values in $b {
1334
+ $a: map.set($a, $group, map.merge(map.get($a, $group) or (), $values));
1335
+ }
1336
+
1337
+ @return $a;
1338
+ }
1339
+
1340
+ /// The declarations in `$now` that `$before` lacks or sets otherwise.
1341
+ @function _changed($now, $before) {
1342
+ $out: ();
1343
+
1344
+ @each $property, $value in $now {
1345
+ @if map.get($before, $property) != $value {
1346
+ $out: map.set($out, $property, $value);
1347
+ }
1348
+ }
1349
+
1350
+ @return $out;
1351
+ }
1352
+
1212
1353
  @function _token-decls($overrides, $complete, $extend: ()) {
1213
1354
  $base: ();
1214
1355
  $light: ();
@@ -1355,6 +1496,195 @@ $_dark-ink: #11171c;
1355
1496
  }
1356
1497
  }
1357
1498
 
1499
+ // --- resets ----------------------------------------------------------------
1500
+
1501
+ // A theme's three layers (ADR 0025), each of which can be taken back from
1502
+ // part of a page (`.greese-reset-<layer>`, `.greese-reset` for all three) or
1503
+ // from a component (`reset()`).
1504
+ $_reset-layers: (color, shape, motion);
1505
+
1506
+ // The layer the rules of the enclosing `component()` belong to.
1507
+ $_layer: shape;
1508
+
1509
+ // Layers `reset()` took from a component, and those its rules have used so far.
1510
+ $_resets: ();
1511
+ $_themed: ();
1512
+
1513
+ @function _check-layers($layers) {
1514
+ @each $layer in $layers {
1515
+ @if not _in($_reset-layers, $layer) {
1516
+ $checked: _check(
1517
+ false,
1518
+ 'no theme layer `#{$layer}`. Layers: #{_join($_reset-layers)}.'
1519
+ );
1520
+ }
1521
+ }
1522
+
1523
+ @return $layers;
1524
+ }
1525
+
1526
+ @function _add($map, $block, $layer) {
1527
+ $list: map.get($map, '#{$block}') or ();
1528
+
1529
+ @if _in($list, $layer) {
1530
+ @return $map;
1531
+ }
1532
+
1533
+ @return map.set($map, '#{$block}', list.append($list, $layer, comma));
1534
+ }
1535
+
1536
+ @function _is-reset($block, $layer) {
1537
+ @return _in(map.get($_resets, '#{$block}') or (), $layer);
1538
+ }
1539
+
1540
+ /// What every theme rule of `$layer` adds to its selector, so the rule stops
1541
+ /// at `.greese-reset-<layer>` and `.greese-reset` and everything inside them.
1542
+ /// No specificity. The kit adds it to `component()` and `motion()`; add it to
1543
+ /// rules of your own that belong to a theme layer:
1544
+ /// `.greese-Card#{greese.reset-guard(shape)} { … }`.
1545
+ @function reset-guard($layer: shape) {
1546
+ $checked: _check-layers(($layer));
1547
+ $r: '.greese-reset-#{$layer}';
1548
+
1549
+ @return ':where(:not(#{$r}, #{$r} *, .greese-reset, .greese-reset *))';
1550
+ }
1551
+
1552
+ /// A selector of a theme's, for rules written as plain CSS (shapes'): inside
1553
+ /// `theme()`, scoped to it with no specificity; for a component (`$name`),
1554
+ /// guarded for `$layer`, and matching nothing once `reset()` has taken that
1555
+ /// layer from it. `#{greese.themed('.greese-Card', Card)} { … }`
1556
+ @function themed($selector, $name: null, $layer: shape) {
1557
+ $scope: '';
1558
+
1559
+ @if $_theme {
1560
+ $scope: ":where([#{$_theme-attr}='#{$_theme}']) ";
1561
+ }
1562
+
1563
+ @if not $name {
1564
+ @return '#{$scope}#{$selector}';
1565
+ }
1566
+
1567
+ $block: _block-name($name);
1568
+ $guard: reset-guard($layer);
1569
+
1570
+ @if _is-reset($block, $layer) {
1571
+ @return '#{$scope}#{$selector}#{$guard}:not(*)';
1572
+ }
1573
+
1574
+ $_themed: _add($_themed, $block, $layer) !global;
1575
+
1576
+ @return '#{$scope}#{$selector}#{$guard}';
1577
+ }
1578
+
1579
+ /// Every variable `motion-defaults()` can set: each role's, and each exit
1580
+ /// role's per property any exit sets.
1581
+ @function _motion-variables() {
1582
+ $lists: _motion-lists();
1583
+ $properties: ();
1584
+
1585
+ @each $name, $plan in _effects() {
1586
+ @each $property, $unused in map.get($plan, exit) or () {
1587
+ @if not _in($properties, $property) {
1588
+ $properties: list.append($properties, $property, comma);
1589
+ }
1590
+ }
1591
+ }
1592
+
1593
+ $out: ();
1594
+
1595
+ @each $key in map.get($lists, known) {
1596
+ @if _in(map.get($lists, exits), $key) {
1597
+ @each $property in $properties {
1598
+ $out: list.append($out, '--greese-motion-#{$key}-#{$property}', comma);
1599
+ }
1600
+ } @else {
1601
+ $out: list.append($out, '--greese-motion-#{$key}', comma);
1602
+ }
1603
+ }
1604
+
1605
+ @return $out;
1606
+ }
1607
+
1608
+ /// The default tokens of each layer, declared here, so the element and what
1609
+ /// it contains take them whatever theme is around: colors (as `light-dark()`,
1610
+ /// following the color scheme), the radius scale and roles, and no motion
1611
+ /// roles at all (each falls back to the base theme's).
1612
+ @mixin _reset-declarations($layers) {
1613
+ @if _in($layers, color) {
1614
+ @each $role, $pair in defaults.$color {
1615
+ $light: _css(map.get($pair, light));
1616
+ $dark: _css(map.get($pair, dark));
1617
+ $name: 'greese-color-#{$role}';
1618
+
1619
+ --#{$name}: light-dark(#{$light}, #{$dark});
1620
+ }
1621
+ }
1622
+
1623
+ @if _in($layers, shape) {
1624
+ @each $group in (radius, corner) {
1625
+ @each $key, $value in map.get(defaults.$scalars, $group) {
1626
+ $name: 'greese-#{$group}-#{$key}';
1627
+
1628
+ --#{$name}: #{_css($value)};
1629
+ }
1630
+ }
1631
+ }
1632
+
1633
+ @if _in($layers, motion) {
1634
+ @each $variable in _motion-variables() {
1635
+ #{$variable}: initial;
1636
+ }
1637
+ }
1638
+ }
1639
+
1640
+ /// Takes a theme's layers back: `reset(shape)`, `reset(color, motion)`, or
1641
+ /// `reset()` for all three. In any rule, it declares the layers' default
1642
+ /// tokens there, for that element and everything inside it:
1643
+ ///
1644
+ /// .legal-notice { @include greese.reset(color); }
1645
+ ///
1646
+ /// Inside `component(Name)` it also leaves that component out of the layers'
1647
+ /// theme rules for the rest of the build, its own and its parts': a shape
1648
+ /// theme's clip, a motion theme's entrance. Only rules compiled after it,
1649
+ /// in the same build, so call it first (in the theme layout, from
1650
+ /// `_resets.scss`); it warns about rules that came before. A prebuilt `.css`
1651
+ /// theme is outside the build: use the classes there.
1652
+ @mixin reset($layers...) {
1653
+ @if list.length($layers) == 0 {
1654
+ $layers: $_reset-layers;
1655
+ }
1656
+
1657
+ $layers: _check-layers($layers);
1658
+
1659
+ @if $_block {
1660
+ @each $layer in $layers {
1661
+ @if _in(map.get($_themed, '#{$_block}') or (), $layer) {
1662
+ @warn 'greese ui kit: reset(#{$layer}) for #{$_block} comes after #{$_block}\'s #{$layer} rules, which stay. Reset it before them.';
1663
+ }
1664
+
1665
+ $_resets: _add($_resets, $_block, $layer) !global;
1666
+ }
1667
+ }
1668
+
1669
+ @include _reset-declarations($layers);
1670
+ }
1671
+
1672
+ /// The reset classes (`.greese-reset-color`, `-shape`, `-motion`, and
1673
+ /// `.greese-reset` for all three), with their layers' default tokens.
1674
+ /// `@greese/ui`'s stylesheet has them; a theme built alone can include them.
1675
+ @mixin reset-classes {
1676
+ @include _layer-order;
1677
+
1678
+ @layer greese.overrides {
1679
+ @each $layer in $_reset-layers {
1680
+ .greese-reset-#{$layer},
1681
+ .greese-reset {
1682
+ @include _reset-declarations(($layer));
1683
+ }
1684
+ }
1685
+ }
1686
+ }
1687
+
1358
1688
  /// Token overrides, compiled: the build-time `createTheme()`. Takes the same
1359
1689
  /// groups as `createTheme` in kebab-case, and a color takes one value or a
1360
1690
  /// `(light: …, dark: …)` pair:
@@ -1368,6 +1698,10 @@ $_dark-ink: #11171c;
1368
1698
  /// pass. Inside `theme(name)` it declares a complete theme, as `createTheme`
1369
1699
  /// does, for `data-greese-theme="name"`.
1370
1700
  ///
1701
+ /// Calls add up: a second call for the same theme (or the page) writes only
1702
+ /// what it changes, so token groups can live in files of their own and the
1703
+ /// result is the same as one call with everything merged.
1704
+ ///
1371
1705
  /// To add tokens the system doesn't have, name them in `$extend` (same shape):
1372
1706
  ///
1373
1707
  /// @include greese.tokens((), $extend: (
@@ -1379,9 +1713,67 @@ $_dark-ink: #11171c;
1379
1713
  /// component overrides accept it from then on. Names that already exist, or
1380
1714
  /// groups that don't, still stop the build. (A new `space` key is a variable
1381
1715
  /// only: the responsive classes come with the stylesheet's build.)
1716
+ ///
1717
+ /// A call that sets nothing (no groups, or only empty ones, as a theme layout's
1718
+ /// untouched group files are) writes nothing, and in a named theme doesn't
1719
+ /// declare it either: the first call that sets something does.
1382
1720
  @mixin tokens($overrides, $extend: ()) {
1721
+ @if not(_sets-nothing($overrides) and _sets-nothing($extend)) {
1722
+ @include _tokens($overrides, $extend);
1723
+ }
1724
+ }
1725
+
1726
+ /// Whether a `tokens()` map sets nothing: no groups, or only empty ones.
1727
+ @function _sets-nothing($tokens) {
1728
+ @each $_group, $values in $tokens {
1729
+ @if list.length($values) > 0 {
1730
+ @return false;
1731
+ }
1732
+ }
1733
+
1734
+ @return true;
1735
+ }
1736
+
1737
+ @mixin _tokens($overrides, $extend) {
1383
1738
  $_extended: _register-extended($extend) !global;
1384
- $d: _token-decls($overrides, $_theme != null, $extend);
1739
+ $scope: $_theme or '';
1740
+ $before: map.get($_token-calls, $scope);
1741
+ $call: (
1742
+ overrides: _token-map($overrides),
1743
+ extend: _token-map($extend),
1744
+ );
1745
+
1746
+ @if $before {
1747
+ $call: (
1748
+ overrides: _merge-tokens(map.get($before, overrides), map.get($call, overrides)),
1749
+ extend: _merge-tokens(map.get($before, extend), map.get($call, extend)),
1750
+ );
1751
+ }
1752
+
1753
+ $_token-calls: map.set($_token-calls, $scope, $call) !global;
1754
+ $d: _token-decls(map.get($call, overrides), $_theme != null, map.get($call, extend));
1755
+
1756
+ // A repeat call writes what the merged tokens change since the last one,
1757
+ // under the same selectors, so it wins without restating the theme.
1758
+ @if $before {
1759
+ $was: _token-decls(
1760
+ map.get($before, overrides),
1761
+ $_theme != null,
1762
+ map.get($before, extend)
1763
+ );
1764
+ $changed: ();
1765
+
1766
+ @each $kind in (base, light, dark, durations) {
1767
+ $changed: map.set(
1768
+ $changed,
1769
+ $kind,
1770
+ _changed(map.get($d, $kind), map.get($was, $kind))
1771
+ );
1772
+ }
1773
+
1774
+ $d: $changed;
1775
+ }
1776
+
1385
1777
  $base: map.get($d, base);
1386
1778
  $light: map.get($d, light);
1387
1779
  $dark: map.get($d, dark);
@@ -1410,7 +1802,11 @@ $_dark-ink: #11171c;
1410
1802
  @include _declare($dark);
1411
1803
  }
1412
1804
 
1413
- @include type-emit.emit-media(config.$typography, $t);
1805
+ // The type system's breakpoints are the same whatever the tokens, so
1806
+ // a repeat call has none to write.
1807
+ @if not $before {
1808
+ @include type-emit.emit-media(config.$typography, $t);
1809
+ }
1414
1810
 
1415
1811
  :root#{_scheme(light)} #{$t}:not(#{$s}) {
1416
1812
  @include _declare($light);
@@ -1474,6 +1870,13 @@ $_dark-ink: #11171c;
1474
1870
  /// word-spacing, font-variant-numeric, color. Variants: p1-p3,
1475
1871
  /// h1-h6, s1, s2, caption, button. Names that don't exist stop the build.
1476
1872
  @mixin typography($changes) {
1873
+ // An empty map writes nothing (a theme layout's untouched files).
1874
+ @if list.length($changes) > 0 {
1875
+ @include _typography($changes);
1876
+ }
1877
+ }
1878
+
1879
+ @mixin _typography($changes) {
1477
1880
  $merged: ();
1478
1881
 
1479
1882
  @each $variant, $spec in $changes {
@@ -1546,3 +1949,27 @@ $_dark-ink: #11171c;
1546
1949
 
1547
1950
  $_theme: $outer !global;
1548
1951
  }
1952
+
1953
+ /// `theme($name)` when there is a name, the page when there isn't (`null`,
1954
+ /// `false` or `''`), so one file serves either. A theme's files share the
1955
+ /// name from one place:
1956
+ ///
1957
+ /// // _settings.scss
1958
+ /// $theme: brand; // or null, for the page's defaults
1959
+ ///
1960
+ /// // components/_card.scss
1961
+ /// @include greese.scope(settings.$theme) {
1962
+ /// @include greese.component(Card) { border-radius: 0; }
1963
+ /// }
1964
+ ///
1965
+ /// (Set the name in that file rather than with `@use … with ($theme: null)`:
1966
+ /// Sass reads a null in `with` as "not configured" and keeps the default.)
1967
+ @mixin scope($name) {
1968
+ @if $name and $name != '' {
1969
+ @include theme($name) {
1970
+ @content;
1971
+ }
1972
+ } @else {
1973
+ @content;
1974
+ }
1975
+ }