From: Mark Otto Date: Thu, 2 Oct 2025 22:24:19 +0000 (-0700) Subject: v6: Range media queries (#41786) X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=235be686085f4d39e30a1d8a6e90cb4ebd65cab6;p=thirdparty%2Fbootstrap.git v6: Range media queries (#41786) * wip Co-Authored-By: mdo <98681+mdo@users.noreply.github.com> * linty linterton --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mdo <98681+mdo@users.noreply.github.com> --- diff --git a/scss/layout/_breakpoints.scss b/scss/layout/_breakpoints.scss index 2320792d13..c8d33116c2 100644 --- a/scss/layout/_breakpoints.scss +++ b/scss/layout/_breakpoints.scss @@ -21,7 +21,7 @@ @function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map.keys($breakpoints)) { $n: list.index($breakpoint-names, $name); @if not $n { - @error "breakpoint `#{$name}` not found in `#{$breakpoints}`"; + @error "breakpoint `#{$name}` not found in `#{$breakpoint-names}`"; } @return if($n < list.length($breakpoint-names), list.nth($breakpoint-names, $n + 1), null); } @@ -35,18 +35,19 @@ @return if($min != 0, $min, null); } -// Maximum breakpoint width. -// The maximum value is reduced by 0.02px to work around the limitations of -// `min-` and `max-` prefixes and viewports with fractional widths. -// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max -// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari. -// See https://bugs.webkit.org/show_bug.cgi?id=178261 +// Maximum breakpoint width for range media queries. +// Returns the breakpoint value to use as an upper bound in range queries. // -// >> breakpoint-max(md, (xs: 0, sm: 576px, md: 768px, lg: 1024px, xl: 1280px, 2xl: 1536px)) -// 767.98px +// >> breakpoint-max(sm, (xs: 0, sm: 576px, md: 768px, lg: 1024px, xl: 1280px, 2xl: 1536px)) +// 576px +// >> breakpoint-max(xxl, (xs: 0, sm: 576px, md: 768px, lg: 1024px, xl: 1280px, 2xl: 1536px)) +// null @function breakpoint-max($name, $breakpoints: $grid-breakpoints) { + @if $name == null { + @return null; + } $max: map.get($breakpoints, $name); - @return if($max and $max > 0, $max - .02, null); + @return if($max and $max > 0, $max, null); } // Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front. @@ -65,7 +66,7 @@ @mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) { $min: breakpoint-min($name, $breakpoints); @if $min { - @media (min-width: $min) { + @media (width >= $min) { @content; } } @else { @@ -78,7 +79,7 @@ @mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) { $max: breakpoint-max($name, $breakpoints); @if $max { - @media (max-width: $max) { + @media (width < $max) { @content; } } @else { @@ -93,7 +94,7 @@ $max: breakpoint-max($upper, $breakpoints); @if $min != null and $max != null { - @media (min-width: $min) and (max-width: $max) { + @media (width >= $min) and (width < $max) { @content; } } @else if $max == null { @@ -116,7 +117,7 @@ $max: breakpoint-max($next, $breakpoints); @if $min != null and $max != null { - @media (min-width: $min) and (max-width: $max) { + @media (width >= $min) and (width < $max) { @content; } } @else if $max == null { diff --git a/scss/tests/mixins/_breakpoints.test.scss b/scss/tests/mixins/_breakpoints.test.scss new file mode 100644 index 0000000000..7136bf11be --- /dev/null +++ b/scss/tests/mixins/_breakpoints.test.scss @@ -0,0 +1,95 @@ +@import "true"; +@import "../../mixins/breakpoints"; + +// Test breakpoint functions and mixins for range media query syntax + +@include test-module("Breakpoint Functions") { + $test-breakpoints: ( + xs: 0, + sm: 576px, + md: 768px, + lg: 1024px, + xl: 1280px, + xxl: 1536px + ); + + @include test("breakpoint-max with range syntax") { + @include assert-equal(breakpoint-max(xs, $test-breakpoints), null); + @include assert-equal(breakpoint-max(sm, $test-breakpoints), 576px); + @include assert-equal(breakpoint-max(md, $test-breakpoints), 768px); + @include assert-equal(breakpoint-max(lg, $test-breakpoints), 1024px); + @include assert-equal(breakpoint-max(xl, $test-breakpoints), 1280px); + @include assert-equal(breakpoint-max(xxl, $test-breakpoints), 1536px); + } +} + +@include test-module("Media Query Mixins - Range Syntax") { + $test-breakpoints: ( + xs: 0, + sm: 576px, + md: 768px, + lg: 1024px, + xl: 1280px, + xxl: 1536px + ); + + @include test("media-breakpoint-up generates range syntax") { + @include assert() { + @include output() { + @include media-breakpoint-up(sm, $test-breakpoints) { + .test { color: #f00; } + } + } + @include expect() { + @media (width >= 576px) { + .test { color: #f00; } + } + } + } + } + + @include test("media-breakpoint-down generates range syntax") { + @include assert() { + @include output() { + @include media-breakpoint-down(md, $test-breakpoints) { + .test { color: #00f; } + } + } + @include expect() { + @media (width < 768px) { + .test { color: #00f; } + } + } + } + } + + @include test("media-breakpoint-between generates range syntax") { + @include assert() { + @include output() { + @include media-breakpoint-between(sm, lg, $test-breakpoints) { + .test { color: #0f0; } + } + } + @include expect() { + @media (width >= 576px) and (width < 1024px) { + .test { color: #0f0; } + } + } + } + } + + @include test("media-breakpoint-only generates range syntax") { + @include assert() { + @include output() { + @include media-breakpoint-only(md, $test-breakpoints) { + .test { color: #ff0; } + } + } + @include expect() { + @media (width >= 768px) and (width < 1024px) { + .test { color: #ff0; } + } + } + } + } +} diff --git a/scss/tests/utilities/_api.test.scss b/scss/tests/utilities/_api.test.scss index 2b38bb9964..4020b2217b 100644 --- a/scss/tests/utilities/_api.test.scss +++ b/scss/tests/utilities/_api.test.scss @@ -53,13 +53,13 @@ $utilities: (); font-size: 1.25rem !important; } - @media (min-width: 333px) { + @media (width >= 333px) { .padding-sm-1rem { padding: 1rem !important; } } - @media (min-width: 666px) { + @media (width >= 666px) { .padding-md-1rem { padding: 1rem !important; } diff --git a/site/src/content/callouts/info-mediaqueries-breakpoints.md b/site/src/content/callouts/info-mediaqueries-breakpoints.md deleted file mode 100644 index 52be67386a..0000000000 --- a/site/src/content/callouts/info-mediaqueries-breakpoints.md +++ /dev/null @@ -1 +0,0 @@ -**Why subtract .02px?** Browsers don’t currently support [range context queries](https://www.w3.org/TR/mediaqueries-4/#range-context), so we work around the limitations of [`min-` and `max-` prefixes](https://www.w3.org/TR/mediaqueries-4/#mq-min-max) and viewports with fractional widths (which can occur under certain conditions on high-dpi devices, for instance) by using values with higher precision. diff --git a/site/src/content/docs/layout/breakpoints.mdx b/site/src/content/docs/layout/breakpoints.mdx index c9c43fa40c..50c74bf2d8 100644 --- a/site/src/content/docs/layout/breakpoints.mdx +++ b/site/src/content/docs/layout/breakpoints.mdx @@ -74,19 +74,19 @@ These Sass mixins translate in our compiled CSS using the values declared in our // No media query for `xs` since this is the default in Bootstrap // Small devices (landscape phones, 576px and up) -@media (min-width: 576px) { ... } +@media (width >= 576px) { ... } // Medium devices (tablets, 768px and up) -@media (min-width: 768px) { ... } +@media (width >= 768px) { ... } // Large devices (desktops, 1024px and up) -@media (min-width: 1024px) { ... } +@media (width >= 1024px) { ... } // X-Large devices (large desktops, 1280px and up) -@media (min-width: 1280px) { ... } +@media (width >= 1280px) { ... } // XX-Large devices (larger desktops, 1536px and up) -@media (min-width: 1536px) { ... } +@media (width >= 1536px) { ... } ``` ### Max-width @@ -109,30 +109,28 @@ We occasionally use media queries that go in the other direction (the given scre } ``` -These mixins take those declared breakpoints, subtract `.02px` from them, and use them as our `max-width` values. For example: +These mixins use the breakpoint values to create `max-width` media queries using modern range syntax. For example: ```scss // `xs` returns only a ruleset and no media query // ... { ... } // `sm` applies to x-small devices (portrait phones, less than 576px) -@media (max-width: 575.98px) { ... } +@media (width < 576px) { ... } // `md` applies to small devices (landscape phones, less than 768px) -@media (max-width: 767.98px) { ... } +@media (width < 768px) { ... } -// `lg` applies to medium devices (tablets, less than 992px) -@media (max-width: 991.98px) { ... } +// `lg` applies to medium devices (tablets, less than 1024px) +@media (width < 1024px) { ... } -// `xl` applies to large devices (desktops, less than 1200px) -@media (max-width: 1199.98px) { ... } +// `xl` applies to large devices (desktops, less than 1280px) +@media (width < 1280px) { ... } -// `2xl` applies to x-large devices (large desktops, less than 1600px) -@media (max-width: 1599.98px) { ... } +// `2xl` applies to x-large devices (large desktops, less than 1536px) +@media (width < 1536px) { ... } ``` - - ### Single breakpoint There are also media queries and mixins for targeting a single segment of screen sizes using the minimum and maximum breakpoint widths. @@ -149,7 +147,7 @@ There are also media queries and mixins for targeting a single segment of screen For example the `@include media-breakpoint-only(md) { ... }` will result in : ```scss -@media (min-width: 768px) and (max-width: 991.98px) { ... } +@media (width >= 768px) and (width < 992px) { ... } ``` ### Between breakpoints @@ -165,5 +163,5 @@ Which results in: ```scss // Example // Apply styles starting from medium devices and up to extra large devices -@media (min-width: 768px) and (max-width: 1199.98px) { ... } +@media (width >= 768px) and (width < 1200px) { ... } ```