DEV: Introduce reactive JS API for viewport size (#32060)

Usage:

```js
@service capabilities;

...

this.capabilities.viewport.sm; // True when viewport is sm or larger
this.capabilities.viewport.md; // True when viewport is md or larger
// etc.
```
These booleans will update live when the browser is resized. Therefore,
they should only be used in an autotracking context, so that Ember will
re-render things appropriately when they change.
This commit is contained in:
David Taylor
2025-04-30 15:26:59 +01:00
committed by GitHub
parent 84659cd234
commit 19e93142e4
5 changed files with 75 additions and 1 deletions
@@ -0,0 +1,20 @@
import { tracked } from "@glimmer/tracking";
export default class TrackedMediaQuery {
@tracked matches;
#matcher;
#handleChange = () => {
this.matches = this.#matcher.matches;
};
constructor(query) {
this.#matcher = window.matchMedia(query);
this.#matcher.addEventListener("change", this.#handleChange);
this.matches = this.#matcher.matches;
}
teardown() {
this.#matcher.removeEventListener("change", this.#handleChange);
}
}
@@ -1,8 +1,19 @@
import TrackedMediaQuery from "discourse/lib/tracked-media-query";
const APPLE_NAVIGATOR_PLATFORMS = /iPhone|iPod|iPad|Macintosh|MacIntel/;
const APPLE_USER_AGENT_DATA_PLATFORM = /macOS/;
const ua = navigator.userAgent;
// Values match those in viewport.scss
const breakpointQueries = {
sm: new TrackedMediaQuery("(min-width: 40rem)"),
md: new TrackedMediaQuery("(min-width: 48rem)"),
lg: new TrackedMediaQuery("(min-width: 64rem)"),
xl: new TrackedMediaQuery("(min-width: 80rem)"),
"2xl": new TrackedMediaQuery("(min-width: 96rem)"),
};
class Capabilities {
touch = navigator.maxTouchPoints > 1 || "ontouchstart" in window;
@@ -37,6 +48,24 @@ class Capabilities {
window.location.search.includes("discourse_app=1");
isAppWebview = window.ReactNativeWebView !== undefined;
viewport = {
get sm() {
return breakpointQueries.sm.matches;
},
get md() {
return breakpointQueries.md.matches;
},
get lg() {
return breakpointQueries.lg.matches;
},
get xl() {
return breakpointQueries.xl.matches;
},
get "2xl"() {
return breakpointQueries["2xl"].matches;
},
};
get userHasBeenActive() {
return (
!("userActivation" in navigator) || navigator.userActivation.hasBeenActive
@@ -1,6 +1,7 @@
/* eslint-disable simple-import-sort/imports */
import Application from "../app";
import "./loader-shims";
import "discourse/static/markdown-it";
/* eslint-enable simple-import-sort/imports */
import { getOwner } from "@ember/owner";
@@ -1 +1 @@
import "discourse/static/markdown-it";
// We don't currently use this file, but it is require'd by ember-cli's test bundle
+24
View File
@@ -0,0 +1,24 @@
# frozen_string_literal: true
describe "capabilities service", type: :system do
describe "viewport helpers" do
it "works" do
def matches(name)
page.evaluate_script("Discourse.lookup('service:capabilities').viewport[#{name.to_json}]")
end
visit "/"
expect(page).to have_css("#site-logo")
expect(matches("sm")).to eq(true)
expect(matches("lg")).to eq(true)
resize_window(width: 700) do
try_until_success do
expect(matches("sm")).to eq(true)
expect(matches("lg")).to eq(false)
end
end
end
end
end