Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds match-media stylesheet behavior #3016

Merged
merged 9 commits into from
Apr 27, 2020
22 changes: 13 additions & 9 deletions packages/web-components/fast-components/src/card/card.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { css } from "@microsoft/fast-element";
import { display, elevation } from "../styles";
import { SystemColors } from "../styles/system-colors";
import { neutralLayerCardBehavior } from "../styles/recipes";
import { forcedColorsStylesheetBehavior } from "../styles/match-media-stylesheet-behavior";

export const CardStyles = css`
${display("block")} :host {
Expand All @@ -15,12 +16,15 @@ export const CardStyles = css`
border-radius: calc(var(--elevated-corner-radius) * 1px);
${elevation};
}

@media (forced-colors: active) {
:host {
forced-color-adjust: none;
border: calc(var(--outline-width) * 1px) solid ${SystemColors.CanvasText};
background: ${SystemColors.Canvas};
}
}
`.withBehaviors(neutralLayerCardBehavior);
`.withBehaviors(
neutralLayerCardBehavior,
forcedColorsStylesheetBehavior(
css`
:host {
forced-color-adjust: none;
border: calc(var(--outline-width) * 1px) solid ${SystemColors.CanvasText};
background: ${SystemColors.Canvas};
}
`
)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { Behavior, ElementStyles, FASTElement } from "@microsoft/fast-element";

type MediaQueryListListener = (this: MediaQueryList, ev?: MediaQueryListEvent) => any;

interface MatchMediaStyleSheetBehavior extends Behavior {
query: MediaQueryList;
cache: WeakMap<
typeof FASTElement,
MediaQueryListListener | Array<MediaQueryListListener>
>;
sheet: ElementStyles;
constructListener: (
source: typeof FASTElement,
sheet: ElementStyles
) => MediaQueryListListener;
}

/**
* Construct a behavior to conditionally apply a stylesheet based
* on a MediaQueryList
*/
export function matchMediaStylesheetBehaviorFactory(query: MediaQueryList) {
const cache: WeakMap<
typeof FASTElement,
((this: MediaQueryList) => void) | Array<(this: MediaQueryList) => void>
> = new WeakMap();

return (sheet: ElementStyles) => {
return Object.freeze({
query,
cache,
sheet,
constructListener(
this: MatchMediaStyleSheetBehavior,
source: typeof FASTElement,
sheet: ElementStyles
): MediaQueryListListener {
let attached = false;

function listener(this: MediaQueryList) {
const { matches } = this;
if (matches && !attached) {
(source as any).$fastController.addStyles(sheet);
attached = matches;
} else if (!matches && attached) {
(source as any).$fastController.removeStyles(sheet);
attached = matches;
}
}

return listener;
},
bind(this: MatchMediaStyleSheetBehavior, source: typeof FASTElement) {
const { constructListener, query, cache } = this;
const listener = constructListener(source, this.sheet);
const cached = cache.get(source);

// Invoke immediately to add if the query currently matches
listener.bind(query)();
query.addListener(listener);

if (cached !== void 0) {
// Support multiple bindings of the same behavior
if (Array.isArray(cached)) {
cached.push(listener);
} else {
cache.set(source, [cached, listener]);
}
} else {
cache.set(source, listener);
}
},
unbind(this: MatchMediaStyleSheetBehavior, source: typeof FASTElement) {
const { cache, query } = this;
const cached = cache.get(source);

if (cached !== void 0) {
if (Array.isArray(cached)) {
cached.forEach(listener => query.removeListener(listener));
} else {
query.removeListener(cached);
}

cache.delete(source);
}
},
});
};
}

/**
* Applies ElementStyles to a FASTElement when the
* forced-colors media query is matched, removes the
* ElementStyles when the query is no-longer matched
*/
export const forcedColorsStylesheetBehavior = matchMediaStylesheetBehaviorFactory(
window.matchMedia("(forced-colors)")
);