gerillass 2.3.0 → 3.0.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 (55) hide show
  1. package/README.md +46 -7
  2. package/SKILL.md +37 -12
  3. package/gerillass.json +527 -145
  4. package/package.json +1 -1
  5. package/scss/internal/_color-problem.scss +99 -0
  6. package/scss/internal/_color-stops-problem.scss +94 -0
  7. package/scss/internal/_condition-width.scss +22 -0
  8. package/scss/internal/_custom-property-in.scss +25 -0
  9. package/scss/internal/_gradient.scss +425 -0
  10. package/scss/internal/_image-value.scss +68 -0
  11. package/scss/internal/_is-condition-value.scss +94 -0
  12. package/scss/internal/_is-css-function.scss +21 -0
  13. package/scss/internal/_is-size.scss +22 -0
  14. package/scss/internal/_keyword-value.scss +76 -0
  15. package/scss/internal/_length-problem.scss +127 -0
  16. package/scss/internal/_sibling-index.scss +51 -0
  17. package/scss/internal/_words.scss +35 -0
  18. package/scss/library/_adaptive.scss +17 -0
  19. package/scss/library/_after.scss +2 -12
  20. package/scss/library/_background-dots.scss +36 -43
  21. package/scss/library/_background-image.scss +40 -52
  22. package/scss/library/_background-stripes.scss +40 -54
  23. package/scss/library/_before.scss +2 -12
  24. package/scss/library/_border-radius.scss +83 -0
  25. package/scss/library/_brand-logo.scss +10 -24
  26. package/scss/library/_breakpoint.scss +19 -26
  27. package/scss/library/_circle.scss +7 -0
  28. package/scss/library/_columnizer.scss +62 -2
  29. package/scss/library/_container-query.scss +13 -29
  30. package/scss/library/_container.scss +2 -19
  31. package/scss/library/_counter.scss +3 -14
  32. package/scss/library/_ellipsis.scss +78 -1
  33. package/scss/library/_except.scss +15 -0
  34. package/scss/library/_focus-ring.scss +29 -0
  35. package/scss/library/_font-face.scss +117 -1
  36. package/scss/library/_gradient.scss +31 -0
  37. package/scss/library/_index.scss +1 -2
  38. package/scss/library/_line-clamp.scss +2 -13
  39. package/scss/library/_only.scss +16 -0
  40. package/scss/library/_position.scss +34 -3
  41. package/scss/library/_remove.scss +1 -1
  42. package/scss/library/_resizable.scss +29 -2
  43. package/scss/library/_screen-agent.scss +8 -22
  44. package/scss/library/_sizer.scss +9 -0
  45. package/scss/library/_sprite.scss +65 -0
  46. package/scss/library/_text-gradient.scss +36 -18
  47. package/scss/library/_text-image.scss +2 -23
  48. package/scss/library/_text-stroke.scss +30 -0
  49. package/scss/library/_triangle.scss +25 -56
  50. package/scss/utilities/_gradient-value.scss +24 -0
  51. package/scss/utilities/_index.scss +1 -0
  52. package/scss/utilities/_validate-breakpoint.scss +32 -6
  53. package/scss/utilities/_validate-scissors.scss +7 -0
  54. package/scss/library/_linear-gradient.scss +0 -26
  55. package/scss/library/_radial-gradient.scss +0 -30
@@ -0,0 +1,68 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:meta";
4
+ @use "sass:string";
5
+ @use "is-css-function" as *;
6
+
7
+ // Shared by the mixins and not public API; see _is-css-function.scss.
8
+
9
+ // An image path goes into url(). A var(), url(), image-set() or gradient is
10
+ // already an image, and wrapping it produced url(var(--x)), which loads nothing.
11
+ //
12
+ // $unquote is there because the copies this replaced differed:
13
+ // background-dots and background-stripes wrote `url(a.png)`, the other three
14
+ // `url("a.png")`. Both load the same file, and keeping each as it was means no
15
+ // stylesheet changes.
16
+ //
17
+ // What it refuses, measured in Chrome 152:
18
+ //
19
+ // - a list where one image is meant: `url(16 9)` was dropped;
20
+ // - a number, a colour or a boolean: `url(42)`, `url(#ff0000)` and `url(true)`
21
+ // are kept, but ask for a file named 42, a fragment of the page, or a file
22
+ // named true, which is never the image meant. background-dots and
23
+ // background-stripes refused these already.
24
+ //
25
+ // And an unquoted path is quoted when it has to be: `url(a b)`, `url(a(1).png)`
26
+ // and `url(a'b.png)` were dropped, while the same paths in quotes were kept and
27
+ // `url("a b")` resolved to `a%20b`. Every other path is written as before.
28
+ @function imageValue($value, $mixin, $argument, $unquote: false) {
29
+ // paint(), image() and -webkit-cross-fade() were kept as images in Chrome
30
+ // 152 and used to be wrapped into url(paint(...)), which loads nothing;
31
+ // cross-fade(), element() and -moz-element() are in the specification or
32
+ // other engines.
33
+ @if isCssFunction($value, "var" "url" "image-set" "linear-gradient" "radial-gradient" "conic-gradient" "repeating-linear-gradient" "repeating-radial-gradient" "repeating-conic-gradient" "paint" "image" "cross-fade" "-webkit-cross-fade" "element" "-moz-element") {
34
+ @return $value;
35
+ }
36
+ @if meta.type-of($value) == "list" or meta.type-of($value) == "arglist" or meta.type-of($value) == "map" {
37
+ @error "`#{meta.inspect($value)}` is not a valid #{$argument} for `#{$mixin}`: it is a list where one image is meant, and a browser drops `url()` with a list in it. Pass one path as a string, such as `\"/img/hero.png\"`.";
38
+ }
39
+ @if meta.type-of($value) != "string" {
40
+ @error "`#{meta.inspect($value)}` is not a valid #{$argument} for `#{$mixin}`: an image path is a string, and `url(#{meta.inspect($value)})` asks for a file of that name, which is never the image. Pass a path as a string, such as `\"/img/hero.png\"`, or var() or a gradient.";
41
+ }
42
+ @if not $unquote {
43
+ @return url($value);
44
+ }
45
+ $text: string.unquote($value);
46
+ @if not (string.index($text, " ") or string.index($text, "(") or string.index($text, ")") or string.index($text, "'") or string.index($text, "\"")) {
47
+ @return url(string.unquote($value));
48
+ }
49
+ @if string.index($text, "\"") {
50
+ @return string.unquote("url('#{-escaped-single-quotes($text)}')");
51
+ }
52
+ @return string.unquote("url(\"#{$text}\")");
53
+ }
54
+
55
+ // A path holding a double quote goes into single quotes, so any single quote in
56
+ // it is escaped.
57
+ @function -escaped-single-quotes($text) {
58
+ $result: "";
59
+ @for $i from 1 through string.length($text) {
60
+ $character: string.slice($text, $i, $i);
61
+ @if $character == "'" {
62
+ $result: $result + "\\'";
63
+ } @else {
64
+ $result: $result + $character;
65
+ }
66
+ }
67
+ @return $result;
68
+ }
@@ -0,0 +1,94 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:list";
4
+ @use "sass:math";
5
+ @use "sass:meta";
6
+ @use "sass:string";
7
+
8
+ // Shared by the mixins and not public API; see _is-css-function.scss.
9
+
10
+ // Every length unit a width condition kept in Chrome 152, in @media and
11
+ // @container alike and in any case: `(min-width: 10PX)` matched. `x`, `dppx`,
12
+ // `fr`, `s`, `%` and a made-up unit never matched, and neither did a unitless
13
+ // number other than 0.
14
+ $-length-units: "px" "cm" "mm" "q" "in" "pt" "pc" "em" "rem" "ex" "rex" "ch" "rch" "cap" "rcap" "ic" "ric" "lh" "rlh" "vw" "vh" "vi" "vb" "vmin" "vmax" "svw" "svh" "svi" "svb" "svmin" "svmax" "lvw" "lvh" "lvi" "lvb" "lvmin" "lvmax" "dvw" "dvh" "dvi" "dvb" "dvmin" "dvmax" "cqw" "cqh" "cqi" "cqb" "cqmin" "cqmax";
15
+
16
+ // The units a resolution condition kept, also in any case. A unitless number, a
17
+ // length and a negative resolution never matched; `0dppx` was kept.
18
+ $-resolution-units: "dpi" "dpcm" "dppx" "x";
19
+
20
+ // Angles, for an oblique `font-style`: `oblique 0.2rad` was kept in a @font-face
21
+ // rule and `oblique 10`, unitless, was dropped.
22
+ $-angle-units: "deg" "grad" "rad" "turn";
23
+
24
+ // True for a value a @media or @container condition can match: a number in one
25
+ // of the units above, a calculation, or either written as a quoted string, which
26
+ // Sass writes into a condition without its quotes, so `breakpoint(min, "600px")`
27
+ // has always worked. A calculation is taken on trust: `calc(30em + 1px)`,
28
+ // `clamp()`, `min()` and `max()` were all kept, and Sass reduces `calc(10deg)`
29
+ // to a number before it gets here, so that one is checked like any other.
30
+ //
31
+ // $kind is "length" or "resolution" for a condition. A negative length was
32
+ // kept, since it is valid and simply matches every width or none; a negative
33
+ // resolution was not. Two more kinds serve values outside a condition, where a
34
+ // caller rules out a negative itself if the property does: "length-percentage",
35
+ // a length or a percentage, and "angle".
36
+ @function isConditionValue($value, $kind: "length") {
37
+ $units: $-length-units;
38
+ @if $kind == "resolution" {
39
+ $units: $-resolution-units;
40
+ } @else if $kind == "length-percentage" {
41
+ $units: list.append($-length-units, "%");
42
+ } @else if $kind == "angle" {
43
+ $units: $-angle-units;
44
+ }
45
+
46
+ @if meta.type-of($value) == "calculation" {
47
+ @return true;
48
+ }
49
+ @if meta.type-of($value) == "number" {
50
+ @if math.is-unitless($value) {
51
+ @return ($kind == "length" or $kind == "length-percentage") and $value == 0;
52
+ }
53
+ @if $kind == "resolution" and $value < 0 {
54
+ @return false;
55
+ }
56
+ @return list.index($units, string.to-lower-case(math.unit($value))) != null;
57
+ }
58
+ @if meta.type-of($value) != "string" {
59
+ @return false;
60
+ }
61
+
62
+ $text: string.to-lower-case(string.unquote($value));
63
+ @each $function in "calc(" "clamp(" "min(" "max(" {
64
+ @if string.index($text, $function) == 1 {
65
+ @return true;
66
+ }
67
+ }
68
+
69
+ // A quoted length, such as "600px": the number runs up to the first
70
+ // character that cannot be part of one, and the rest is the unit.
71
+ $unit-start: null;
72
+ $digits: 0;
73
+ @for $i from 1 through string.length($text) {
74
+ $character: string.slice($text, $i, $i);
75
+ @if $unit-start == null {
76
+ @if string.index("0123456789", $character) {
77
+ $digits: $digits + 1;
78
+ } @else if not string.index(".+-", $character) {
79
+ $unit-start: $i;
80
+ }
81
+ }
82
+ }
83
+ @if $digits == 0 {
84
+ @return false;
85
+ }
86
+ @if $unit-start == null {
87
+ // A quoted unitless number: only zero, and only for a length.
88
+ @return ($kind == "length" or $kind == "length-percentage") and not string.index($text, "1") and not string.index($text, "2") and not string.index($text, "3") and not string.index($text, "4") and not string.index($text, "5") and not string.index($text, "6") and not string.index($text, "7") and not string.index($text, "8") and not string.index($text, "9");
89
+ }
90
+ @if $kind == "resolution" and string.index(string.slice($text, 1, $unit-start - 1), "-") {
91
+ @return false;
92
+ }
93
+ @return list.index($units, string.slice($text, $unit-start)) != null;
94
+ }
@@ -0,0 +1,21 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:list";
4
+ @use "sass:meta";
5
+ @use "sass:string";
6
+
7
+ // scss/internal/ holds checks the mixins share. No _index.scss forwards it, so
8
+ // a user cannot reach these functions and they are not public API: through
9
+ // `@use "gerillass" as *` or `@import` a call to one renders as literal CSS,
10
+ // and through a namespace it is an undefined function. Their names must not
11
+ // start with `-` or `_`, which would make them private to their own file.
12
+
13
+ // True for an unquoted call to one of $names, such as var(--x). A quoted string
14
+ // stays text, so `"var(--x)"` is not a call.
15
+ @function isCssFunction($value, $names) {
16
+ @if meta.type-of($value) != "string" or string.slice(meta.inspect($value), 1, 1) == "\"" {
17
+ @return false;
18
+ }
19
+ $open: string.index($value, "(");
20
+ @return $open != null and list.index($names, string.to-lower-case(string.slice($value, 1, $open - 1))) != null;
21
+ }
@@ -0,0 +1,22 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:list";
4
+ @use "sass:meta";
5
+ @use "sass:string";
6
+
7
+ // Shared by the mixins and not public API; see _is-css-function.scss.
8
+
9
+ // A size Sass can use inside calc(): a number, a calculation such as calc(),
10
+ // min() or clamp(), or var() and env(), which only the browser can resolve.
11
+ // Anything else would pass silently through calc(): `calc(nonsense * 5)`
12
+ // compiles.
13
+ @function isSize($value) {
14
+ @if meta.type-of($value) == "number" or meta.type-of($value) == "calculation" {
15
+ @return true;
16
+ }
17
+ @if meta.type-of($value) == "string" and string.slice(meta.inspect($value), 1, 1) != "\"" {
18
+ $open: string.index($value, "(");
19
+ @return $open != null and list.index("var" "env", string.to-lower-case(string.slice($value, 1, $open - 1))) != null;
20
+ }
21
+ @return false;
22
+ }
@@ -0,0 +1,76 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:list";
4
+ @use "sass:meta";
5
+ @use "sass:string";
6
+ @use "words" as *;
7
+
8
+ // Shared by the mixins and not public API; see _is-css-function.scss.
9
+
10
+ $-css-wide: "inherit" "initial" "unset" "revert" "revert-layer";
11
+
12
+ // Returns what is wrong with $value as a keyword value, or null when it is fine.
13
+ // It checks the kind of each word, not whether the words make sense together;
14
+ // a mixin whose property has a grammar, such as display, checks that itself.
15
+ //
16
+ // - null is fine: the declaration is left out, as it always was.
17
+ // - Any word holding a parenthesis makes the whole value fine when $functions is
18
+ // true. var(), env() and calc() are resolved by the browser, and a value such
19
+ // as `var(--x) auto` cannot be judged here. In a @font-face descriptor, which
20
+ // drops var(), the caller passes false.
21
+ // - Up to $max-words words, each one of $keywords in any case; `STICKY` and
22
+ // `BOTH` were kept in Chrome 152.
23
+ // - A CSS-wide keyword only on its own: `overflow: inherit auto` was dropped.
24
+ // - A vendor-prefixed word when $vendor is true. Chrome drops `-webkit-sticky`
25
+ // and `-moz-box`, but other engines use them, so refusing one could break a
26
+ // stylesheet written for Safari.
27
+ @function keywordProblem($value, $keywords, $max-words: 1, $functions: true, $vendor: false) {
28
+ @if $value == null {
29
+ @return null;
30
+ }
31
+ @if meta.type-of($value) == "list" and list.separator($value) == "comma" {
32
+ @return "it takes a space-separated value, not a comma-separated list";
33
+ }
34
+ $words: wordsOf($value);
35
+ @if list.length($words) == 0 {
36
+ @return "it is empty";
37
+ }
38
+ @each $word in $words {
39
+ @if meta.type-of($word) == "string" and string.index($word, "(") {
40
+ @if $functions {
41
+ @return null;
42
+ }
43
+ @return "a function cannot supply it here";
44
+ }
45
+ }
46
+ @if list.length($words) > $max-words {
47
+ @if $max-words == 1 {
48
+ @return "it takes one keyword, and got #{list.length($words)}";
49
+ }
50
+ @return "it takes at most #{$max-words} keywords, and got #{list.length($words)}";
51
+ }
52
+ $expected: list.join($keywords, (), $separator: comma);
53
+ @each $word in $words {
54
+ @if meta.type-of($word) != "string" {
55
+ @if list.length($words) == 1 {
56
+ @return "it takes a keyword, one of: #{$expected}";
57
+ }
58
+ @return "`#{meta.inspect($word)}` is not a keyword; pass words from: #{$expected}";
59
+ }
60
+ $lower: string.to-lower-case(string.unquote($word));
61
+ @if list.index($-css-wide, $lower) {
62
+ @if list.length($words) > 1 {
63
+ @return "`#{$lower}` only works as the whole value";
64
+ }
65
+ } @else if not list.index($keywords, $lower) {
66
+ $prefixed: string.index($lower, "-webkit-") == 1 or string.index($lower, "-moz-") == 1 or string.index($lower, "-ms-") == 1 or string.index($lower, "-o-") == 1;
67
+ @if not ($vendor and $prefixed) {
68
+ @if list.length($words) == 1 {
69
+ @return "it is not one of: #{$expected}";
70
+ }
71
+ @return "`#{$lower}` is not one of: #{$expected}";
72
+ }
73
+ }
74
+ }
75
+ @return null;
76
+ }
@@ -0,0 +1,127 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:list";
4
+ @use "sass:math";
5
+ @use "sass:meta";
6
+ @use "sass:string";
7
+ @use "is-condition-value" as *;
8
+
9
+ // Shared by the mixins and not public API; see _is-css-function.scss.
10
+
11
+ // Maths functions a browser resolves to a length, and the substitutions whose
12
+ // value only the browser knows. Sass reduces most maths on plain numbers before
13
+ // they get here (`round(10.5px, 1px)` arrives as 11px), so these are the forms
14
+ // it leaves as text.
15
+ $-functions: "var" "env" "attr" "calc" "clamp" "min" "max" "round" "abs" "sign" "mod" "rem" "hypot" "sqrt" "pow" "exp" "log";
16
+ $-css-wide: "inherit" "initial" "unset" "revert" "revert-layer";
17
+
18
+ // Returns what is wrong with $value as one length for a property, or null when
19
+ // a browser keeps it. Each caller passes what its property takes, measured in
20
+ // Chrome 152 (see todos/silent-values-plan.md, S7):
21
+ //
22
+ // - $percent: whether a percentage works (width yes, outline-width no);
23
+ // - $negative: whether a negative length works (outline-offset yes, width no);
24
+ // - $keywords: the words the property takes, such as auto or thin;
25
+ // - $functions: sizing functions beyond maths, such as anchor-size;
26
+ // - $css-wide: whether a CSS-wide keyword works, which is only as a whole
27
+ // declaration;
28
+ // - $quoted: whether a quoted length works, which is only where the mixin
29
+ // interpolates it; written raw, `width: "10px"` is dropped.
30
+ //
31
+ // A unitless 0 is accepted; a caller whose output breaks on it handles it.
32
+ // A calculation is taken on trust, since Sass keeps it only when it cannot
33
+ // reduce it.
34
+ @function lengthProblem($value, $percent: true, $negative: true, $keywords: (), $functions: (), $css-wide: false, $quoted: false) {
35
+ @if meta.type-of($value) == "calculation" {
36
+ @return null;
37
+ }
38
+ @if meta.type-of($value) == "list" and list.length($value) > 1 {
39
+ @return "it is several values where one length is meant";
40
+ }
41
+ @if meta.type-of($value) == "number" {
42
+ @if math.is-unitless($value) {
43
+ @if $value == 0 {
44
+ @return null;
45
+ }
46
+ @return "a unitless number is not a length; add a unit, such as `#{$value}px`";
47
+ }
48
+ @if math.unit($value) == "%" and not $percent {
49
+ @return "a percentage does not work here";
50
+ }
51
+ @if not isConditionValue($value, "length-percentage") {
52
+ @return "`#{math.unit($value)}` is not a length unit";
53
+ }
54
+ @if not $negative and $value < 0 {
55
+ @return "a length below zero does not work here";
56
+ }
57
+ @return null;
58
+ }
59
+ @if meta.type-of($value) != "string" {
60
+ @return "it is not a length";
61
+ }
62
+ $kind: "length";
63
+ @if $percent {
64
+ $kind: "length-percentage";
65
+ }
66
+ @if string.slice(meta.inspect($value), 1, 1) == "\"" {
67
+ @if $quoted and isConditionValue($value, $kind) {
68
+ @return null;
69
+ }
70
+ @return "a length in quotes is text here; write it without quotes";
71
+ }
72
+ $lower: string.to-lower-case($value);
73
+ $open: string.index($lower, "(");
74
+ @if $open {
75
+ $name: string.slice($lower, 1, $open - 1);
76
+ @if list.index($-functions, $name) or list.index($functions, $name) {
77
+ @return null;
78
+ }
79
+ @return "`#{$name}()` does not give a length here";
80
+ }
81
+ @if string.index($lower, " ") {
82
+ @return "it is several values where one length is meant";
83
+ }
84
+ // Interpolation, such as `#{$n}px`, gives an unquoted string rather than a
85
+ // number, and it is written into the CSS as it reads, so its content decides.
86
+ @if isConditionValue($value, $kind) {
87
+ @if not $negative and string.index($lower, "-") == 1 {
88
+ @return "a length below zero does not work here";
89
+ }
90
+ @return null;
91
+ }
92
+ @if string.index($lower, "%") and isConditionValue($value, "length-percentage") {
93
+ @return "a percentage does not work here";
94
+ }
95
+ @if list.index($keywords, $lower) {
96
+ @return null;
97
+ }
98
+ @if list.index($-css-wide, $lower) {
99
+ @if $css-wide {
100
+ @return null;
101
+ }
102
+ @return "a CSS-wide keyword only works as a whole declaration, not here";
103
+ }
104
+ @if list.length($keywords) > 0 {
105
+ @return "it is not a length or one of: #{list.join($keywords, (), $separator: comma)}";
106
+ }
107
+ @return "it is not a length";
108
+ }
109
+
110
+ // lengthProblem for a width or height, and for max-width with $max. Measured in
111
+ // Chrome 152: width and height keep the intrinsic keywords, stretch,
112
+ // -webkit-fill-available, anchor-size(), calc-size() and a CSS-wide keyword;
113
+ // max-width keeps none where width keeps auto, and drops calc-size(). Both drop
114
+ // a negative length. -moz-available is Firefox's spelling of stretch; Chrome
115
+ // drops it, and it is let through like the other vendor keywords.
116
+ //
117
+ // null is accepted, since Sass leaves the declaration out, as it always has.
118
+ @function sizeProblem($value, $max: false) {
119
+ @if $value == null {
120
+ @return null;
121
+ }
122
+ $keywords: "min-content" "max-content" "fit-content" "stretch" "-webkit-fill-available" "-moz-available";
123
+ @if $max {
124
+ @return lengthProblem($value, $negative: false, $keywords: list.append($keywords, "none"), $functions: ("anchor-size",), $css-wide: true);
125
+ }
126
+ @return lengthProblem($value, $negative: false, $keywords: list.append($keywords, "auto"), $functions: "anchor-size" "calc-size", $css-wide: true);
127
+ }
@@ -0,0 +1,51 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:math";
4
+ @use "sass:meta";
5
+
6
+ // Shared by the mixins and not public API; see _is-css-function.scss.
7
+
8
+ // Returns what is wrong with $value as a sibling position for `only` or
9
+ // `except`, or null when it is a whole number other than 0. Measured in Chrome
10
+ // 152 on six siblings:
11
+ //
12
+ // - a number with any unit or a fraction, `:nth-of-type(10deg)`,
13
+ // `:nth-of-type(10px)`, `:nth-of-type(1.5)`, is an invalid selector, and the
14
+ // browser drops the whole rule. In a selector list that takes every other
15
+ // selector with it: `.a:nth-of-type(1), .a:nth-of-type(10deg)` was dropped.
16
+ // - `:nth-of-type(0)` is valid and matched none of the six, and
17
+ // `:not(:nth-of-type(0))` matched all six, since positions count from 1.
18
+ // - a negative number, `+3` and `99999` were kept; `only` and `except` turn a
19
+ // negative position into :nth-last-of-type.
20
+ //
21
+ // $several is true when the value is one of several arguments, where only
22
+ // positions are taken; alone, a word is a keyword or a selector.
23
+ @function siblingIndexProblem($value, $mixin, $several: false) {
24
+ @if meta.type-of($value) != "number" {
25
+ @if $several {
26
+ @return "several arguments are all positions, whole numbers such as `1` or `-2`. Pass a keyword such as `first`, or a selector such as `.foo`, on its own";
27
+ }
28
+ @return null;
29
+ }
30
+ // A multiple of n, such as `3n`, reaches here as a number whose unit is n.
31
+ // Measured in Chrome 152, :nth-of-type(3n) is valid and matched two of six
32
+ // siblings, and main compiled only(3n) to it, so it is accepted. Its
33
+ // coefficient is checked like a position: `1.5n` was dropped and `0n`
34
+ // matched nothing.
35
+ $coefficient: $value;
36
+ @if math.unit($value) == "n" {
37
+ $coefficient: math.div($value, 1n);
38
+ } @else if not math.is-unitless($value) {
39
+ @return "a position is a whole number such as `2`, or a multiple of n such as `3n`, and `:nth-of-type(#{math.abs($value)})` is an invalid selector, so the browser would drop the rule. Pass a number such as `2`, or `-2` to count from the end";
40
+ }
41
+ @if math.round($coefficient) != $coefficient {
42
+ @return "a position is a whole number, and `:nth-of-type(#{math.abs($value)})` is an invalid selector, so the browser would drop the rule. Pass a number such as `2`, or `-2` to count from the end";
43
+ }
44
+ @if $coefficient == 0 {
45
+ @if $mixin == "except" {
46
+ @return "positions count from 1, so `:not(:nth-of-type(0))` excludes nothing. Pass `1` for the first, or `-1` for the last";
47
+ }
48
+ @return "positions count from 1, so `:nth-of-type(0)` matches nothing. Pass `1` for the first, or `-1` for the last";
49
+ }
50
+ @return null;
51
+ }
@@ -0,0 +1,35 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "sass:list";
4
+ @use "sass:meta";
5
+ @use "sass:string";
6
+
7
+ // Shared by the mixins and not public API; see _is-css-function.scss.
8
+
9
+ // The words of a value, as a space-separated list. A quoted string such as
10
+ // "block flow-root" is split on its spaces, the way the browser reads it once
11
+ // the quotes are gone; a space-separated list gives its items; anything else is
12
+ // one word. Empty words from repeated spaces are skipped.
13
+ @function wordsOf($value) {
14
+ @if meta.type-of($value) == "list" and list.separator($value) != "comma" {
15
+ @return $value;
16
+ }
17
+ @if meta.type-of($value) != "string" {
18
+ @return ($value,);
19
+ }
20
+ $words: ();
21
+ $rest: $value;
22
+ $at: string.index($rest, " ");
23
+ @while $at {
24
+ $word: string.slice($rest, 1, $at - 1);
25
+ @if string.length($word) > 0 {
26
+ $words: list.append($words, $word);
27
+ }
28
+ $rest: string.slice($rest, $at + 1);
29
+ $at: string.index($rest, " ");
30
+ }
31
+ @if string.length($rest) > 0 {
32
+ $words: list.append($words, $rest);
33
+ }
34
+ @return $words;
35
+ }
@@ -1,9 +1,26 @@
1
1
  @charset "UTF-8";
2
2
 
3
3
  @use "sass:map";
4
+ @use "sass:math";
5
+ @use "sass:meta";
6
+ @use "sass:string";
4
7
  @use "../maps/map-for-breakpoints" as *;
8
+ @use "../internal/length-problem" as *;
5
9
 
6
10
  @mixin adaptive($gutter: 30px) {
11
+ // The gutter is subtracted twice inside calc(), which in Chrome 152 kept a
12
+ // length, a percentage, a negative length and var(), and dropped the
13
+ // max-width for a word, a CSS-wide keyword, a unitless number and a unit that
14
+ // is not a length. A quoted length works, since it is interpolated.
15
+ $problem: lengthProblem($gutter, $functions: ("anchor-size",), $quoted: true);
16
+ @if $problem {
17
+ @error "`#{meta.inspect($gutter)}` is not a valid $gutter for `adaptive`: #{$problem}. A browser drops the max-width at every breakpoint.";
18
+ }
19
+ // A unitless 0 made `calc(576px - 0 * 2)`, a length minus a number, which was
20
+ // dropped; 0px is kept.
21
+ @if (meta.type-of($gutter) == "number" and math.is-unitless($gutter)) or (meta.type-of($gutter) == "string" and string.unquote($gutter) == string.unquote("0")) {
22
+ $gutter: 0px;
23
+ }
7
24
  $actual-breakpoints: map.remove($map-for-breakpoints, "xsmall");
8
25
  margin: 0 auto;
9
26
  @each $item, $value in $actual-breakpoints {
@@ -1,18 +1,8 @@
1
1
  @charset "UTF-8";
2
2
 
3
- @use "sass:list";
4
3
  @use "sass:meta";
5
4
  @use "sass:string";
6
-
7
- // True for an unquoted call to one of $names, such as var(--x). A quoted string
8
- // stays text, so `"var(--x)"` is not a call.
9
- @function -is-css-function($value, $names) {
10
- @if meta.type-of($value) != "string" or string.slice(meta.inspect($value), 1, 1) == "\"" {
11
- @return false;
12
- }
13
- $open: string.index($value, "(");
14
- @return $open != null and list.index($names, string.to-lower-case(string.slice($value, 1, $open - 1))) != null;
15
- }
5
+ @use "../internal/is-css-function" as *;
16
6
 
17
7
  @mixin after($content: null) {
18
8
  &::after {
@@ -20,7 +10,7 @@
20
10
  @content;
21
11
  } @else if meta.type-of($content) != "string" {
22
12
  @error "`#{$content}` is not a valid $content for `after`. Pass a string to use as the content value, a data- attribute name such as `\"data-label\"` to render it with attr(), or no argument at all and write the content yourself.";
23
- } @else if -is-css-function($content, "var" "attr" "counter" "counters") {
13
+ } @else if isCssFunction($content, "var" "attr" "counter" "counters") {
24
14
  // Quoting would turn the call into text: content: "var(--label)".
25
15
  content: $content;
26
16
  @content;