Skip to content

Commit

Permalink
#921@trivial: Continues on implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
capricorn86 committed May 16, 2023
1 parent 38b68d5 commit a0c560e
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 143 deletions.
10 changes: 0 additions & 10 deletions packages/happy-dom/src/match-media/IMediaQueryItem.ts

This file was deleted.

198 changes: 198 additions & 0 deletions packages/happy-dom/src/match-media/MediaQueryItem.ts
@@ -0,0 +1,198 @@
import IWindow from '../window/IWindow';
import MediaQueryDeviceEnum from './MediaQueryDeviceEnum';

/**
* Media query this.
*/
export default class MediaQueryItem {
public devices: MediaQueryDeviceEnum[];
public not: boolean;
public rules: Array<{ name: string; value: string | null }>;
private ownerWindow: IWindow;

/**
* Constructor.
*
* @param ownerWindow Window.
* @param [options] Options.
* @param [options.devices] Devices.
* @param [options.not] Not.
* @param [options.rules] Rules.
*/
constructor(
ownerWindow: IWindow,
options?: {
devices?: MediaQueryDeviceEnum[];
not?: boolean;
rules?: Array<{ name: string; value: string | null }>;
}
) {
this.ownerWindow = ownerWindow;
this.devices = (options && options.devices) || [];
this.not = (options && options.not) || false;
this.rules = (options && options.rules) || [];
}

/**
* Returns media string.
*/
public toString(): string {
return `${this.not ? 'not ' : ''}${this.devices.join(', ')}${
(this.not || this.devices.length > 0) && !!this.rules.length ? ' and ' : ''
}${this.rules
.map((rule) => (rule.value ? `(${rule.name}: ${rule.value})` : `(${rule.name})`))
.join(' and ')}`;
}

/**
* Returns "true" if the item matches.
*/
public matches(): boolean {
return this.not ? !this.matchesAll() : this.matchesAll();
}

/**
* Returns "true" if all matches.
*
* @returns "true" if all matches.
*/
private matchesAll(): boolean {
if (!!this.devices.length) {
let isDeviceMatch = false;
for (const device of this.devices) {
if (this.matchesDevice(device)) {
isDeviceMatch = true;
break;
}
}

if (!isDeviceMatch) {
return false;
}
}

for (const rule of this.rules) {
if (!this.matchesRule(rule)) {
return false;
}
}

return true;
}

/**
* Returns "true" if the device matches.
*
* @param device Device.
* @returns "true" if the device matches.
*/
private matchesDevice(device: MediaQueryDeviceEnum): boolean {
switch (device) {
case MediaQueryDeviceEnum.all:
return true;
case MediaQueryDeviceEnum.screen:
return true;
case MediaQueryDeviceEnum.print:
return false;
}
}

/**
* Returns "true" if the rule matches.
*
* @param rule Rule.
* @param rule.name Rule name.
* @param rule.value Rule value.
* @returns "true" if the rule matches.
*/
private matchesRule(rule: { name: string; value: string | null }): boolean {
if (!rule.value) {
switch (rule.name) {
case 'min-width':
case 'max-width':
case 'min-height':
case 'max-height':
case 'orientation':
case 'prefers-color-scheme':
case 'hover':
case 'any-hover':
case 'any-pointer':
case 'pointer':
case 'display-mode':
case 'min-aspect-ratio':
case 'max-aspect-ratio':
case 'aspect-ratio':
return true;
}
return false;
}

switch (rule.name) {
case 'min-width':
const minWidth = parseInt(rule.value, 10);
return !isNaN(minWidth) && this.ownerWindow.innerWidth >= minWidth;
case 'max-width':
const maxWidth = parseInt(rule.value, 10);
return !isNaN(maxWidth) && this.ownerWindow.innerWidth <= maxWidth;
case 'min-height':
const minHeight = parseInt(rule.value, 10);
return !isNaN(minHeight) && this.ownerWindow.innerHeight >= minHeight;
case 'max-height':
const maxHeight = parseInt(rule.value, 10);
return !isNaN(maxHeight) && this.ownerWindow.innerHeight <= maxHeight;
case 'orientation':
return rule.value === 'landscape'
? this.ownerWindow.innerWidth > this.ownerWindow.innerHeight
: this.ownerWindow.innerWidth < this.ownerWindow.innerHeight;
case 'prefers-color-scheme':
return rule.value === this.ownerWindow.happyDOM.settings.colorScheme;
case 'any-hover':
case 'hover':
if (rule.value === 'none') {
return this.ownerWindow.navigator.maxTouchPoints > 0;
}
if (rule.value === 'hover') {
return this.ownerWindow.navigator.maxTouchPoints === 0;
}
return false;
case 'any-pointer':
case 'pointer':
if (rule.value === 'none') {
return false;
}

if (rule.value === 'coarse') {
return this.ownerWindow.navigator.maxTouchPoints > 0;
}

if (rule.value === 'fine') {
return this.ownerWindow.navigator.maxTouchPoints === 0;
}

return false;
case 'display-mode':
return rule.value === 'browser';
case 'min-aspect-ratio':
case 'max-aspect-ratio':
case 'aspect-ratio':
const aspectRatio = rule.value.split('/');
const width = parseInt(aspectRatio[0], 10);
const height = parseInt(aspectRatio[1], 10);

if (isNaN(width) || isNaN(height)) {
return false;
}

switch (rule.name) {
case 'min-aspect-ratio':
return width / height <= this.ownerWindow.innerWidth / this.ownerWindow.innerHeight;
case 'max-aspect-ratio':
return width / height >= this.ownerWindow.innerWidth / this.ownerWindow.innerHeight;
case 'aspect-ratio':
return width / height === this.ownerWindow.innerWidth / this.ownerWindow.innerHeight;
}
}

return false;
}
}
93 changes: 16 additions & 77 deletions packages/happy-dom/src/match-media/MediaQueryList.ts
Expand Up @@ -3,9 +3,8 @@ import Event from '../event/Event';
import IWindow from '../window/IWindow';
import IEventListener from '../event/IEventListener';
import MediaQueryListEvent from '../event/events/MediaQueryListEvent';
import IMediaQueryItem from './IMediaQueryItem';
import IMediaQueryItem from './MediaQueryItem';
import MediaQueryParser from './MediaQueryParser';
import MediaQueryDeviceEnum from './MediaQueryDeviceEnum';

/**
* Media Query List.
Expand All @@ -14,10 +13,10 @@ import MediaQueryDeviceEnum from './MediaQueryDeviceEnum';
* https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList.
*/
export default class MediaQueryList extends EventTarget {
public readonly media: string = '';
public onchange: (event: Event) => void = null;
private _ownerWindow: IWindow;
private _items: IMediaQueryItem[] = [];
private _items: IMediaQueryItem[] | null = null;
private _media: string;

/**
* Constructor.
Expand All @@ -28,8 +27,17 @@ export default class MediaQueryList extends EventTarget {
constructor(ownerWindow: IWindow, media: string) {
super();
this._ownerWindow = ownerWindow;
this._items = MediaQueryParser.getMediaQueryItems(media);
this.media = this._items.length > 0 ? media : 'not all';
this._media = media;
}

/**
* Returns media.
*
* @returns Media.
*/
public get media(): string {
this._items = this._items || MediaQueryParser.parse(this._ownerWindow, this._media);
return this._items.map((item) => item.toString()).join(', ');
}

/**
Expand All @@ -38,12 +46,10 @@ export default class MediaQueryList extends EventTarget {
* @returns Matches.
*/
public get matches(): boolean {
if (!this._items.length || this.media === 'not all') {
return false;
}
this._items = this._items || MediaQueryParser.parse(this._ownerWindow, this._media);

for (const item of this._items) {
if (!this._matchesItem(item)) {
if (!item.matches()) {
return false;
}
}
Expand Down Expand Up @@ -102,71 +108,4 @@ export default class MediaQueryList extends EventTarget {
this._ownerWindow.removeEventListener('resize', listener['_windowResizeListener']);
}
}

/**
*
* @param item
*/
private _matchesItem(item: IMediaQueryItem): boolean {
if (
item.device === MediaQueryDeviceEnum.print ||
(item.not &&
(item.device === MediaQueryDeviceEnum.screen || item.device === MediaQueryDeviceEnum.all))
) {
return false;
}

switch (item.rule.key) {
case 'min-width':
const minWidth = parseInt(item.rule.value, 10);
return !isNaN(minWidth) && this._ownerWindow.innerWidth >= minWidth;
case 'max-width':
const maxWidth = parseInt(item.rule.value, 10);
return !isNaN(maxWidth) && this._ownerWindow.innerWidth <= maxWidth;
case 'min-height':
const minHeight = parseInt(item.rule.value, 10);
return !isNaN(minHeight) && this._ownerWindow.innerHeight >= minHeight;
case 'max-height':
const maxHeight = parseInt(item.rule.value, 10);
return !isNaN(maxHeight) && this._ownerWindow.innerHeight <= maxHeight;
case 'orientation':
return item.rule.value === 'landscape'
? this._ownerWindow.innerWidth > this._ownerWindow.innerHeight
: this._ownerWindow.innerWidth < this._ownerWindow.innerHeight;
case 'prefers-color-scheme':
return item.rule.value === this._ownerWindow.happyDOM.settings.colorScheme;
case 'hover':
return item.rule.value === 'none'
? this._ownerWindow.navigator.maxTouchPoints === 0
: this._ownerWindow.navigator.maxTouchPoints > 0;
case 'any-pointer':
case 'pointer':
return item.rule.value === 'none' || item.rule.value === 'fine'
? this._ownerWindow.navigator.maxTouchPoints === 0
: this._ownerWindow.navigator.maxTouchPoints > 0;
case 'display-mode':
return item.rule.value === 'browser';
case 'min-aspect-ratio':
case 'max-aspect-ratio':
case 'aspect-ratio':
const aspectRatio = item.rule.value.split('/');
const width = parseInt(aspectRatio[0], 10);
const height = parseInt(aspectRatio[1], 10);

if (isNaN(width) || isNaN(height)) {
return false;
}

switch (item.rule.key) {
case 'min-aspect-ratio':
return width / height <= this._ownerWindow.innerWidth / this._ownerWindow.innerHeight;
case 'max-aspect-ratio':
return width / height >= this._ownerWindow.innerWidth / this._ownerWindow.innerHeight;
case 'aspect-ratio':
return width / height === this._ownerWindow.innerWidth / this._ownerWindow.innerHeight;
}
}

return false;
}
}

0 comments on commit a0c560e

Please sign in to comment.