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
@@ -1,6 +1,13 @@
1
1
  @charset "UTF-8";
2
2
 
3
+ @use "sass:meta";
4
+ @use "../internal/length-problem" as *;
5
+
3
6
  @mixin circle($size) {
7
+ $problem: sizeProblem($size);
8
+ @if $problem {
9
+ @error "`#{meta.inspect($size)}` is not a valid $size for `circle`: #{$problem}. A browser drops the width and height.";
10
+ }
4
11
  width: $size;
5
12
  height: $size;
6
13
  display: inline-block;
@@ -1,15 +1,75 @@
1
1
  @charset "UTF-8";
2
2
 
3
3
  @use "sass:list";
4
+ @use "sass:math";
4
5
  @use "sass:meta";
6
+ @use "sass:string";
5
7
  @use "../library/border-box" as *;
6
8
  @use "../utilities/is-gutter" as *;
9
+ @use "../internal/length-problem" as *;
10
+
11
+ // The count divides 100% in calc(), which in Chrome 152 kept a unitless number
12
+ // and a function that resolves to one, var(), env() or attr(), or maths with
13
+ // one of those inside. It dropped a count with a unit, a word and maths on
14
+ // lengths alone, such as calc(100% - 10px): 100% divided by a length is not a
15
+ // length.
16
+ @function -count-problem($columns) {
17
+ @if meta.type-of($columns) == "number" {
18
+ @if math.is-unitless($columns) {
19
+ @return null;
20
+ }
21
+ @return "a column count has no unit";
22
+ }
23
+ // Interpolation, as in `#{$cols}`, gives the number as an unquoted string,
24
+ // which is written into calc() as it reads.
25
+ @if meta.type-of($columns) == "string" and string.slice(meta.inspect($columns), 1, 1) != "\"" and not string.index($columns, "(") {
26
+ $digits: 0;
27
+ @for $i from 1 through string.length($columns) {
28
+ $character: string.slice($columns, $i, $i);
29
+ @if string.index("0123456789", $character) {
30
+ $digits: $digits + 1;
31
+ } @else if not string.index(".+-", $character) {
32
+ @return "it is not a column count";
33
+ }
34
+ }
35
+ @if $digits > 0 {
36
+ @return null;
37
+ }
38
+ }
39
+ @if meta.type-of($columns) == "calculation" or (meta.type-of($columns) == "string" and string.index($columns, "(")) {
40
+ $text: string.to-lower-case(meta.inspect($columns));
41
+ @if string.index($text, "var(") or string.index($text, "env(") or string.index($text, "attr(") {
42
+ @return null;
43
+ }
44
+ @return "maths on lengths does not give a count; pass a number, or var(), env() or attr()";
45
+ }
46
+ @return "it is not a column count";
47
+ }
48
+
49
+ // The gutter is multiplied in calc() and written as a margin. Chrome 152 kept a
50
+ // length, a percentage and a negative length, and dropped the flex-basis for a
51
+ // unitless number, a unit that is not a length and a word. A unitless 0 made
52
+ // `(3 - 1) * 0`, a number rather than a length, so it is written as 0px.
53
+ @function -gutter($gutter) {
54
+ $problem: lengthProblem($gutter);
55
+ @if $problem {
56
+ @error "`#{meta.inspect($gutter)}` is not a valid gutter for `columnizer`: #{$problem}. A browser drops the column width.";
57
+ }
58
+ @if (meta.type-of($gutter) == "number" and math.is-unitless($gutter)) or (meta.type-of($gutter) == "string" and string.unquote($gutter) == string.unquote("0")) {
59
+ @return 0px;
60
+ }
61
+ @return $gutter;
62
+ }
7
63
 
8
64
  @mixin columnizer($params...) {
9
65
  @if list.length($params) < 1 or list.length($params) > 3 {
10
66
  @error "`columnizer` takes 1 to 3 arguments; you passed #{list.length($params)}. Pass the column count, optionally a gutter length, and optionally a boolean telling the last row whether to fill.";
11
67
  }
12
68
  $columns: list.nth($params, 1);
69
+ $count-problem: -count-problem($columns);
70
+ @if $count-problem {
71
+ @error "`#{meta.inspect($columns)}` is not a valid column count for `columnizer`: #{$count-problem}. A browser drops the column width.";
72
+ }
13
73
  $gutter: null;
14
74
  $fill: false;
15
75
  display: flex;
@@ -33,7 +93,7 @@
33
93
  margin-right: 0;
34
94
  }
35
95
  } @else if isGutter(list.nth($params, 2)) {
36
- $gutter: list.nth($params, list.length($params));
96
+ $gutter: -gutter(list.nth($params, list.length($params)));
37
97
  flex-grow: 0;
38
98
  flex-shrink: 0;
39
99
  flex-basis: calc((100% - (#{$columns} - 1) * #{$gutter}) / #{$columns});
@@ -48,7 +108,7 @@
48
108
  @error "`#{list.nth($params, 2)}` is not a valid second argument for `columnizer`. Pass a gutter length such as `20px` or `var(--gap)`, or a boolean telling the last row whether to fill.";
49
109
  }
50
110
  } @else if list.length($params) == 3 {
51
- $gutter: list.nth($params, 2);
111
+ $gutter: -gutter(list.nth($params, 2));
52
112
  $fill: list.nth($params, list.length($params));
53
113
  flex-grow: if($fill == true, 1, 0);
54
114
  flex-shrink: 0;
@@ -5,26 +5,8 @@
5
5
  @use "sass:meta";
6
6
  @use "sass:string";
7
7
  @use "../maps/map-for-breakpoints" as *;
8
- @use "../utilities/validate-breakpoint" as *;
9
-
10
- // Returns the first value in $value that holds var(), looking inside lists and
11
- // calculations, or null. Custom properties are not evaluated in a @media or
12
- // @container condition, so a rule built from one never applies, and nothing
13
- // warns. Measured in Chrome 152: `(min-width: var(--wide))` did not match with
14
- // `--wide: 200px`, while `(min-width: 200px)` did.
15
- @function -custom-property-in($value) {
16
- @each $item in $value {
17
- @if meta.type-of($item) == "list" or meta.type-of($item) == "arglist" {
18
- $found: -custom-property-in($item);
19
- @if $found {
20
- @return $found;
21
- }
22
- } @else if string.index(meta.inspect($item), "var(") {
23
- @return $item;
24
- }
25
- }
26
- @return null;
27
- }
8
+ @use "../internal/custom-property-in" as *;
9
+ @use "../internal/condition-width" as *;
28
10
 
29
11
  // Characters and names a browser will not keep as the name in an @container
30
12
  // rule. Measured in Chrome 152, the rule was dropped for `var(--n)`, `none`,
@@ -88,7 +70,7 @@ $-forbidden-in-name: "." "," "/" "#" ":" ";" "(" ")" "[" "]" "{" "}" "!" "@" "$"
88
70
  }
89
71
  }
90
72
 
91
- $custom-property: -custom-property-in($params);
73
+ $custom-property: customPropertyIn($params);
92
74
  @if $custom-property {
93
75
  @error "`#{$custom-property}` cannot be used in a container query: custom properties are not evaluated in a @container condition, so the rule would never apply. Pass a length or a key from $map-for-breakpoints.";
94
76
  }
@@ -96,27 +78,29 @@ $-forbidden-in-name: "." "," "/" "#" ":" ";" "(" ")" "[" "]" "{" "}" "!" "@" "$"
96
78
 
97
79
  @if $count == 1 {
98
80
  $value: list.nth($params, 1);
99
- $width: validateBreakpoint($value);
81
+ // Checked before the warning, as in `breakpoint`. conditionWidth returns
82
+ // what validateBreakpoint did, so a working call writes the same condition.
83
+ $width: conditionWidth($value, "container-query", "@container");
100
84
  $condition: "(width: #{$width})";
101
- // A single pixel, as in `breakpoint`; still emitted until 3.0.0 refuses it.
102
- @warn "`container-query(#{$value})` matches only a container exactly #{$width} wide, `(width: #{$width})`. Write `container-query(only, #{$value})` for that width, or `container-query(min, #{$value})` from it upwards. The one-argument form will be refused in 3.0.0.";
85
+ // A single pixel, as in `breakpoint`; still emitted, with a warning.
86
+ @warn "`container-query(#{$value})` matches only a container exactly #{$width} wide, `(width: #{$width})`. Write `container-query(only, #{$value})` for that width, or `container-query(min, #{$value})` from it upwards.";
103
87
  } @else if $count == 2 {
104
88
  $mode: list.nth($params, 1);
105
89
  @if not list.index("only" "min" "max" "between", $mode) {
106
90
  // Two bare sizes mean a range, as they do in `breakpoint`.
107
- $condition: "(min-width: #{validateBreakpoint($mode)}) and (max-width: #{validateBreakpoint(list.nth($params, 2))})";
91
+ $condition: "(min-width: #{conditionWidth($mode, "container-query", "@container")}) and (max-width: #{conditionWidth(list.nth($params, 2), "container-query", "@container")})";
108
92
  } @else {
109
93
  $value: list.nth($params, 2);
110
94
  @if $mode == "only" {
111
- $condition: "(width: #{validateBreakpoint($value)})";
95
+ $condition: "(width: #{conditionWidth($value, "container-query", "@container")})";
112
96
  } @else if $mode == "min" {
113
- $condition: "(min-width: #{validateBreakpoint($value)})";
97
+ $condition: "(min-width: #{conditionWidth($value, "container-query", "@container")})";
114
98
  } @else if $mode == "max" {
115
- $condition: "(max-width: #{validateBreakpoint($value)})";
99
+ $condition: "(max-width: #{conditionWidth($value, "container-query", "@container")})";
116
100
  } @else {
117
101
  $start: list.nth($value, 1);
118
102
  $end: list.nth($value, 2);
119
- $condition: "(min-width: #{validateBreakpoint($start)}) and (max-width: #{validateBreakpoint($end)})";
103
+ $condition: "(min-width: #{conditionWidth($start, "container-query", "@container")}) and (max-width: #{conditionWidth($end, "container-query", "@container")})";
120
104
  }
121
105
  }
122
106
  } @else {
@@ -3,6 +3,7 @@
3
3
  @use "sass:list";
4
4
  @use "sass:meta";
5
5
  @use "sass:string";
6
+ @use "../internal/words" as *;
6
7
 
7
8
  // What a browser will not keep in `container-name`. Measured in Chrome 152, the
8
9
  // declaration was dropped for `and`, `or`, `not` and `default` in any case, a
@@ -16,30 +17,12 @@ $-reserved-names: "and" "or" "not" "default";
16
17
  $-alone-names: "none" "inherit" "initial" "unset" "revert" "revert-layer";
17
18
  $-forbidden-in-name: "." "," "/" "#" ":" ";" "(" ")" "[" "]" "{" "}" "!" "@" "$" "%" "^" "&" "*" "+" "=" "<" ">" "?" "\\" "|" "~" "`" "'" "\"";
18
19
 
19
- @function -words($text) {
20
- $words: ();
21
- $rest: $text;
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
- }
36
-
37
20
  @function -name-problem($name) {
38
21
  $text: "#{$name}";
39
22
  @if string.index($text, "var(") {
40
23
  @return null;
41
24
  }
42
- $words: -words($text);
25
+ $words: wordsOf($text);
43
26
  @each $word in $words {
44
27
  $lower: string.to-lower-case($word);
45
28
  @if list.index($-alone-names, $lower) and list.length($words) > 1 {
@@ -1,19 +1,8 @@
1
1
  @charset "UTF-8";
2
2
 
3
3
  @use "sass:list";
4
- @use "sass:meta";
5
- @use "sass:string";
6
4
  @use "../lists/list-of-counter-styles" as *;
7
-
8
- // True for an unquoted call to one of $names, such as var(--x). A quoted string
9
- // stays text, so `"var(--x)"` is not a call.
10
- @function -is-css-function($value, $names) {
11
- @if meta.type-of($value) != "string" or string.slice(meta.inspect($value), 1, 1) == "\"" {
12
- @return false;
13
- }
14
- $open: string.index($value, "(");
15
- @return $open != null and list.index($names, string.to-lower-case(string.slice($value, 1, $open - 1))) != null;
16
- }
5
+ @use "../internal/is-css-function" as *;
17
6
 
18
7
  @mixin counter($params...) {
19
8
  $counter: null;
@@ -49,10 +38,10 @@
49
38
  // quoting would turn into the literal characters.
50
39
  $quote-before: '"';
51
40
  $quote-after: '"';
52
- @if -is-css-function($counter-before, "var" "attr" "counter" "counters") {
41
+ @if isCssFunction($counter-before, "var" "attr" "counter" "counters") {
53
42
  $quote-before: "";
54
43
  }
55
- @if -is-css-function($counter-after, "var" "attr" "counter" "counters") {
44
+ @if isCssFunction($counter-after, "var" "attr" "counter" "counters") {
56
45
  $quote-after: "";
57
46
  }
58
47
  &.counter-start {
@@ -1,7 +1,84 @@
1
1
  @charset "UTF-8";
2
2
 
3
+ @use "sass:list";
4
+ @use "sass:meta";
5
+ @use "sass:string";
6
+ @use "../internal/keyword-value" as *;
7
+ @use "../internal/length-problem" as *;
8
+ @use "../internal/words" as *;
9
+
10
+ // The display keywords, sorted by the part of the value each one fills.
11
+ // Measured in Chrome 152, one keyword, or two or three in any order, were kept
12
+ // when they named at most one outer type, one inner type and one list-item:
13
+ // `inline flex`, `flex block`, `block flow list-item`, `list-item inline flow`.
14
+ // These were dropped: two of a kind (`block inline`, `flex grid`,
15
+ // `block block`), `list-item` beside an inner type other than flow or
16
+ // flow-root (`list-item flex`), and a compound keyword such as `inline-block`
17
+ // beside anything. Chrome also dropped `run-in`, the ruby-base family and the
18
+ // -moz- and -ms- boxes; they are in the specification or used by other
19
+ // engines, so they are let through.
20
+ $-outer: "block" "inline" "run-in";
21
+ $-inner: "flow" "flow-root" "table" "flex" "grid" "ruby" "math";
22
+ $-alone: "none" "contents" "inline-block" "inline-table" "inline-flex" "inline-grid" "table-row-group" "table-header-group" "table-footer-group" "table-row" "table-cell" "table-column-group" "table-column" "table-caption" "ruby-base" "ruby-text" "ruby-base-container" "ruby-text-container";
23
+
24
+ @function -display-problem($display) {
25
+ $keywords: list.join(list.join(list.join($-outer, $-inner), $-alone), ("list-item",));
26
+ $problem: keywordProblem($display, $keywords, 3, $vendor: true);
27
+ @if $problem or $display == null {
28
+ @return $problem;
29
+ }
30
+ $words: wordsOf($display);
31
+ $outer: 0;
32
+ $inner: null;
33
+ $inners: 0;
34
+ $list-items: 0;
35
+ @each $word in $words {
36
+ @if string.index($word, "(") {
37
+ @return null;
38
+ }
39
+ $lower: string.to-lower-case(string.unquote($word));
40
+ @if list.length($words) > 1 and not list.index($-outer, $lower) and not list.index($-inner, $lower) and $lower != "list-item" {
41
+ @return "`#{$lower}` only works as the whole value";
42
+ }
43
+ @if list.index($-outer, $lower) {
44
+ $outer: $outer + 1;
45
+ } @else if list.index($-inner, $lower) {
46
+ $inners: $inners + 1;
47
+ $inner: $lower;
48
+ } @else if $lower == "list-item" {
49
+ $list-items: $list-items + 1;
50
+ }
51
+ }
52
+ @if $outer > 1 {
53
+ @return "it names two outer display types; pass one of block, inline or run-in";
54
+ }
55
+ @if $inners > 1 {
56
+ @return "it names two inner display types; pass one of flow, flow-root, table, flex, grid, ruby or math";
57
+ }
58
+ @if $list-items > 1 {
59
+ @return "it names list-item twice";
60
+ }
61
+ @if $list-items == 1 and $inner and not list.index("flow" "flow-root", $inner) {
62
+ @return "`list-item` only goes with flow or flow-root, not `#{$inner}`";
63
+ }
64
+ @return null;
65
+ }
66
+
3
67
  @mixin ellipsis($width: 100%, $display: inline-block) {
4
- display: $display;
68
+ $problem: -display-problem($display);
69
+ @if $problem {
70
+ @error "`#{meta.inspect($display)}` is not a valid $display for `ellipsis`: #{$problem}. A browser drops the declaration.";
71
+ }
72
+ $problem: sizeProblem($width, $max: true);
73
+ @if $problem {
74
+ @error "`#{meta.inspect($width)}` is not a valid $width for `ellipsis`, which sets max-width: #{$problem}. A browser drops the declaration.";
75
+ }
76
+ // Written without quotes, since `display: "block"` was dropped.
77
+ @if meta.type-of($display) == "string" {
78
+ display: string.unquote($display);
79
+ } @else {
80
+ display: $display;
81
+ }
5
82
  max-width: $width;
6
83
  text-overflow: ellipsis;
7
84
  white-space: nowrap;
@@ -3,6 +3,7 @@
3
3
  @use "sass:list";
4
4
  @use "sass:meta";
5
5
  @use "sass:string";
6
+ @use "../internal/sibling-index" as *;
6
7
 
7
8
  @mixin except($params...) {
8
9
  @if list.length($params) == 0 {
@@ -10,6 +11,12 @@
10
11
  } @else if list.length($params) == 1 {
11
12
  $value: list.nth($params, 1);
12
13
  @if meta.type-of($value) == "number" {
14
+ // A unit or a fraction makes a selector the browser drops, and 0 excludes
15
+ // nothing; see scss/internal/_sibling-index.scss.
16
+ $problem: siblingIndexProblem($value, "except");
17
+ @if $problem {
18
+ @error "`#{meta.inspect($value)}` is not a valid position for `except`: #{$problem}.";
19
+ }
13
20
  @if $value < 0 {
14
21
  &:not(:nth-last-of-type(#{$value * -1})) {
15
22
  @content;
@@ -45,6 +52,14 @@
45
52
  @error "`#{$value}` is not a valid argument for `except`. Pass a positive or negative number, one of `first`, `last`, `odd`, `even`, or a selector such as `.foo`.";
46
53
  }
47
54
  } @else if list.length($params) > 1 {
55
+ // Every item is checked first. A word used to fail with Sass's own
56
+ // `Undefined operation`, and one bad position dropped the whole rule.
57
+ @each $item in $params {
58
+ $problem: siblingIndexProblem($item, "except", true);
59
+ @if $problem {
60
+ @error "`#{meta.inspect($item)}` is not a valid position for `except`: #{$problem}.";
61
+ }
62
+ }
48
63
  $items: "";
49
64
  @for $i from 1 through list.length($params) {
50
65
  $items: $items + if(list.nth($params, $i) < 0, string.unquote(":not(:nth-last-of-type(#{list.nth($params, $i) * -1}))"), string.unquote(":not(:nth-of-type(#{list.nth($params, $i)}))"));
@@ -1,5 +1,9 @@
1
1
  @charset "UTF-8";
2
2
 
3
+ @use "sass:meta";
4
+ @use "../internal/color-problem" as *;
5
+ @use "../internal/length-problem" as *;
6
+
3
7
  // Draws a focus ring for keyboard users. Two things are usually got wrong by
4
8
  // hand, and this writes the version that avoids both.
5
9
  //
@@ -26,6 +30,31 @@
26
30
  @if not & {
27
31
  @error "`focus-ring` styles the element it is called in, so call it inside a selector, such as `.button { @include focus-ring; }`.";
28
32
  }
33
+ // The colour goes into the outline shorthand, where Chrome 152 dropped a word
34
+ // that is not a colour, a quoted colour, a length and a CSS-wide keyword, so
35
+ // the ring was not drawn at all. null leaves the colour to the browser.
36
+ @if $color != null {
37
+ $problem: colorProblem($color);
38
+ @if $problem {
39
+ @error "`#{meta.inspect($color)}` is not a valid $color for `focus-ring`: #{$problem}. A browser drops the outline, so no ring is drawn. Pass a colour such as `#1a73e8`, `currentColor`, or a function such as `var(--focus)`.";
40
+ }
41
+ }
42
+ // The width goes into the same shorthand, which in Chrome 152 kept a length,
43
+ // 0 and thin, medium or thick, and dropped a percentage, a negative width and
44
+ // a CSS-wide keyword. The offset is a declaration of its own: it kept a
45
+ // negative length and a CSS-wide keyword, and dropped a percentage and thin.
46
+ @if $width != null {
47
+ $problem: lengthProblem($width, $percent: false, $negative: false, $keywords: "thin" "medium" "thick");
48
+ @if $problem {
49
+ @error "`#{meta.inspect($width)}` is not a valid $width for `focus-ring`: #{$problem}. A browser drops the outline, so no ring is drawn.";
50
+ }
51
+ }
52
+ @if $offset != null {
53
+ $problem: lengthProblem($offset, $percent: false, $css-wide: true);
54
+ @if $problem {
55
+ @error "`#{meta.inspect($offset)}` is not a valid $offset for `focus-ring`: #{$problem}. A browser drops the declaration.";
56
+ }
57
+ }
29
58
 
30
59
  &:focus-visible {
31
60
  outline: $width solid $color;
@@ -2,10 +2,113 @@
2
2
 
3
3
  @use "sass:list";
4
4
  @use "sass:map";
5
+ @use "sass:math";
5
6
  @use "sass:meta";
6
7
  @use "sass:string";
7
8
  @use "../maps/map-for-font-formats" as *;
8
9
  @use "../utilities/font-source" as *;
10
+ @use "../internal/is-condition-value" as *;
11
+ @use "../internal/words" as *;
12
+
13
+ // What a @font-face rule keeps in `font-style` and `font-weight`, measured in
14
+ // Chrome 152 by inserting the rule and reading the descriptor back, in any
15
+ // case. font-style kept normal, italic, auto, oblique, and oblique with one or
16
+ // two angles from -90deg to 90deg (`oblique 10deg 20deg`, `oblique 0.2rad`),
17
+ // and dropped a CSS-wide keyword, `oblique 100deg`, an angle without a unit
18
+ // and a weight such as `400`. font-weight kept normal, bold, auto, a number
19
+ // from 1 to 1000 (`450`, `400.5`, `calc(400 + 100)`) and two numbers as a range
20
+ // in either order, and dropped bolder and lighter, a CSS-wide keyword, 0, 1001,
21
+ // three numbers, and a keyword beside a number or another keyword.
22
+ //
23
+ // A quoted value is split on its spaces and checked word by word, but Sass
24
+ // cannot turn the text of a number into one, so a quoted number is checked for
25
+ // its shape and not its range: "1001" and "oblique 100deg" are let through.
26
+
27
+ // A quoted or unquoted number, without a unit, for a weight.
28
+ @function -is-weight-number($word) {
29
+ @if meta.type-of($word) == "number" {
30
+ @return math.is-unitless($word) and $word >= 1 and $word <= 1000;
31
+ }
32
+ @if meta.type-of($word) != "string" {
33
+ @return false;
34
+ }
35
+ $text: string.unquote($word);
36
+ $nonzero: false;
37
+ @for $i from 1 through string.length($text) {
38
+ $character: string.slice($text, $i, $i);
39
+ @if not string.index("0123456789.", $character) {
40
+ @return false;
41
+ }
42
+ @if string.index("123456789", $character) {
43
+ $nonzero: true;
44
+ }
45
+ }
46
+ @return $nonzero;
47
+ }
48
+
49
+ @function -weight-problem($weight) {
50
+ @if $weight == null or meta.type-of($weight) == "calculation" {
51
+ @return null;
52
+ }
53
+ $words: wordsOf($weight);
54
+ @if list.length($words) > 2 {
55
+ @return "a weight is one value, or two numbers for a range such as `100 900`";
56
+ }
57
+ @if list.length($words) == 1 {
58
+ $word: list.nth($words, 1);
59
+ @if meta.type-of($word) == "string" and list.index("normal" "bold" "auto", string.to-lower-case(string.unquote($word))) {
60
+ @return null;
61
+ }
62
+ @if -is-weight-number($word) {
63
+ @return null;
64
+ }
65
+ @return "a weight is normal, bold, auto or a number from 1 to 1000; bolder, lighter and the CSS-wide keywords are dropped";
66
+ }
67
+ @each $word in $words {
68
+ @if not -is-weight-number($word) {
69
+ @return "a range is two numbers from 1 to 1000, such as `100 900`";
70
+ }
71
+ }
72
+ @return null;
73
+ }
74
+
75
+ @function -style-problem($style) {
76
+ @if $style == null {
77
+ @return null;
78
+ }
79
+ $words: wordsOf($style);
80
+ $first: list.nth($words, 1);
81
+ $keyword: null;
82
+ @if meta.type-of($first) == "string" {
83
+ $keyword: string.to-lower-case(string.unquote($first));
84
+ }
85
+ @if list.index("normal" "italic" "auto", $keyword) {
86
+ @if list.length($words) > 1 {
87
+ @return "`#{$keyword}` takes nothing after it; only oblique takes angles";
88
+ }
89
+ @return null;
90
+ }
91
+ @if $keyword != "oblique" {
92
+ @return "a style is normal, italic, oblique or auto; the CSS-wide keywords are dropped";
93
+ }
94
+ @if list.length($words) > 3 {
95
+ @return "oblique takes at most two angles";
96
+ }
97
+ @for $i from 2 through list.length($words) {
98
+ $angle: list.nth($words, $i);
99
+ @if meta.type-of($angle) == "number" {
100
+ @if not isConditionValue($angle, "angle") {
101
+ @return "`#{$angle}` is not an angle; pass one such as `10deg`";
102
+ }
103
+ @if $angle < -90deg or $angle > 90deg {
104
+ @return "an oblique angle is between -90deg and 90deg";
105
+ }
106
+ } @else if not isConditionValue($angle, "angle") {
107
+ @return "`#{$angle}` is not an angle; pass one such as `10deg`";
108
+ }
109
+ }
110
+ @return null;
111
+ }
9
112
 
10
113
  @mixin font-face(
11
114
  $font-family,
@@ -51,7 +154,10 @@
51
154
  @error "`#{meta.inspect($font-weight)}` is not a valid $font-weight for `font-face`. A custom property cannot supply it, because a @font-face rule drops var() in a descriptor. Pass the weight itself, such as `700`, or a range such as `160 700`.";
52
155
  }
53
156
 
54
- @if list.index(100 200 300 400 500 600 700 800 900, $font-style) {
157
+ // A number in $font-style is a weight. Only the hundreds used to be read
158
+ // that way, so `font-face("I", "/f/i", 450)` wrote `font-style: 450`, which
159
+ // a @font-face rule drops; any weight a rule keeps, 1 to 1000, is read so.
160
+ @if meta.type-of($font-style) == "number" and math.is-unitless($font-style) and $font-style >= 1 and $font-style <= 1000 {
55
161
  $font-weight: $font-style;
56
162
  $font-style: normal;
57
163
  } @else if not
@@ -71,6 +177,16 @@
71
177
  }
72
178
  }
73
179
 
180
+ // Checked after the reinterpretation above, on the values that are written.
181
+ $problem: -style-problem($font-style);
182
+ @if $problem {
183
+ @error "`#{meta.inspect($font-style)}` is not a valid $font-style for `font-face`: #{$problem}. A @font-face rule drops the descriptor.";
184
+ }
185
+ $problem: -weight-problem($font-weight);
186
+ @if $problem {
187
+ @error "`#{meta.inspect($font-weight)}` is not a valid $font-weight for `font-face`: #{$problem}. A @font-face rule drops the descriptor.";
188
+ }
189
+
74
190
  // $font-display comes last so the reinterpretation of $font-style above is
75
191
  // left alone, and it is written only when given: the browser's default is
76
192
  // what a call without it has always had. Measured in Chrome 152, a
@@ -0,0 +1,31 @@
1
+ @charset "UTF-8";
2
+
3
+ @use "../internal/gradient" as *;
4
+
5
+ // One mixin for linear, radial and conic gradients, plain or repeating, with an
6
+ // optional colour interpolation space. It replaced linear-gradient and
7
+ // radial-gradient in 3.0.0; MIGRATION.md has each old call's new form.
8
+ //
9
+ // It writes background-image, not the background shorthand: measured in Chrome
10
+ // 152, the shorthand reset a background-color set before it to transparent.
11
+ // What each argument takes, and what was measured, is in
12
+ // scss/internal/_gradient.scss.
13
+ @mixin gradient(
14
+ $colors,
15
+ $type: linear,
16
+ $direction: null,
17
+ $shape: null,
18
+ $position: null,
19
+ $from: null,
20
+ $in: null,
21
+ $repeating: false
22
+ ) {
23
+ $value: gradientOf($colors, $type, $direction, $shape, $position, $from, $in, $repeating);
24
+ // Written without the space first. A browser that does not know `in` drops
25
+ // the second declaration and keeps this one; measured in Chrome by giving it
26
+ // a space name it does not know.
27
+ @if $in != null {
28
+ background-image: gradientOf($colors, $type, $direction, $shape, $position, $from, null, $repeating, $warn: false);
29
+ }
30
+ background-image: $value;
31
+ }
@@ -27,16 +27,15 @@
27
27
  @forward "except";
28
28
  @forward "focus-ring";
29
29
  @forward "font-face";
30
+ @forward "gradient";
30
31
  @forward "hide";
31
32
  @forward "line-clamp";
32
- @forward "linear-gradient";
33
33
  @forward "loadify";
34
34
  @forward "motion-safe";
35
35
  @forward "only";
36
36
  @forward "placeholder";
37
37
  @forward "placeholder-shown";
38
38
  @forward "position";
39
- @forward "radial-gradient";
40
39
  @forward "remove";
41
40
  @forward "reset-css";
42
41
  @forward "reset-figure";
@@ -1,19 +1,8 @@
1
1
  @charset "UTF-8";
2
2
 
3
- @use "sass:list";
4
3
  @use "sass:math";
5
4
  @use "sass:meta";
6
- @use "sass:string";
7
-
8
- // True for an unquoted call to one of $names, such as var(--x). A quoted string
9
- // stays text, so `"var(--x)"` is not a call.
10
- @function -is-css-function($value, $names) {
11
- @if meta.type-of($value) != "string" or string.slice(meta.inspect($value), 1, 1) == "\"" {
12
- @return false;
13
- }
14
- $open: string.index($value, "(");
15
- @return $open != null and list.index($names, string.to-lower-case(string.slice($value, 1, $open - 1))) != null;
16
- }
5
+ @use "../internal/is-css-function" as *;
17
6
 
18
7
  // Truncates text after a number of lines. `ellipsis` does one line; this does
19
8
  // several, and needs five declarations rather than one because -webkit-line-clamp
@@ -33,7 +22,7 @@
33
22
  // what actually does the work today.
34
23
  @mixin line-clamp($lines: 3) {
35
24
  // var() is resolved by the browser, so only a literal count is checked.
36
- @if $lines != none and not -is-css-function($lines, "var" "env") {
25
+ @if $lines != none and not isCssFunction($lines, "var" "env") {
37
26
  @if meta.type-of($lines) != "number" or not math.is-unitless($lines) {
38
27
  @error "`#{$lines}` is not a valid $lines for `line-clamp`. Pass a whole number of lines, or `none` to undo a clamp.";
39
28
  }