feat: Reinitialize frontend with SvelteKit and TypeScript

- Delete old Vite+Svelte frontend
- Initialize new SvelteKit project with TypeScript
- Configure Tailwind CSS v4 + DaisyUI
- Implement JWT authentication with auto-refresh
- Create login page with form validation (Zod)
- Add protected route guards
- Update Docker configuration for single-stage build
- Add E2E tests with Playwright (6/11 passing)
- Fix Svelte 5 reactivity with $state() runes

Known issues:
- 5 E2E tests failing (timing/async issues)
- Token refresh implementation needs debugging
- Validation error display timing
This commit is contained in:
2026-02-17 16:19:59 -05:00
parent 54df6018f5
commit de2d83092e
28274 changed files with 3816354 additions and 90 deletions

View File

@@ -0,0 +1,12 @@
export default interface IMediaQueryRange {
before: {
value: string;
operator: string;
};
type: string;
after: {
value: string;
operator: string;
};
}
//# sourceMappingURL=IMediaQueryRange.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"IMediaQueryRange.d.ts","sourceRoot":"","sources":["../../src/match-media/IMediaQueryRange.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,WAAW,gBAAgB;IACxC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=IMediaQueryRange.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"IMediaQueryRange.js","sourceRoot":"","sources":["../../src/match-media/IMediaQueryRange.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,5 @@
export default interface IMediaQueryRule {
name: string;
value: string | null;
}
//# sourceMappingURL=IMediaQueryRule.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"IMediaQueryRule.d.ts","sourceRoot":"","sources":["../../src/match-media/IMediaQueryRule.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,WAAW,eAAe;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=IMediaQueryRule.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"IMediaQueryRule.js","sourceRoot":"","sources":["../../src/match-media/IMediaQueryRule.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,77 @@
import BrowserWindow from '../window/BrowserWindow.js';
import IMediaQueryRange from './IMediaQueryRange.js';
import IMediaQueryRule from './IMediaQueryRule.js';
import MediaQueryTypeEnum from './MediaQueryTypeEnum.js';
/**
* Media query this.
*/
export default class MediaQueryItem {
mediaTypes: MediaQueryTypeEnum[];
not: boolean;
rules: IMediaQueryRule[];
ranges: IMediaQueryRange[];
private rootFontSize;
private window;
/**
* Constructor.
*
* @param options Options.
* @param options.window Owner window.
* @param [options.rootFontSize] Root font size.
* @param [options.mediaTypes] Media types.
* @param [options.not] Not.
* @param [options.rules] Rules.
* @param [options.ranges] Ranges.
*/
constructor(options: {
window: BrowserWindow;
rootFontSize?: string | number | null;
mediaTypes?: MediaQueryTypeEnum[];
not?: boolean;
rules?: IMediaQueryRule[];
ranges?: IMediaQueryRange[];
});
/**
* Returns media string.
*/
toString(): string;
/**
* Returns "true" if the item matches.
*/
matches(): boolean;
/**
* Returns "true" if all matches.
*
* @returns "true" if all matches.
*/
private matchesAll;
/**
* Returns "true" if the mediaType matches.
*
* @param mediaType Media type.
* @returns "true" if the mediaType matches.
*/
private matchesMediaType;
/**
* Returns "true" if the range matches.
*
* @param range Range.
* @returns "true" if the range matches.
*/
private matchesRange;
/**
* Returns "true" if the rule matches.
*
* @param rule Rule.
* @returns "true" if the rule matches.
*/
private matchesRule;
/**
* Convert to pixels.
*
* @param value Value.
* @returns Value in pixels.
*/
private toPixels;
}
//# sourceMappingURL=MediaQueryItem.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MediaQueryItem.d.ts","sourceRoot":"","sources":["../../src/match-media/MediaQueryItem.ts"],"names":[],"mappings":"AACA,OAAO,aAAa,MAAM,4BAA4B,CAAC;AAEvD,OAAO,gBAAgB,MAAM,uBAAuB,CAAC;AACrD,OAAO,eAAe,MAAM,sBAAsB,CAAC;AACnD,OAAO,kBAAkB,MAAM,yBAAyB,CAAC;AAEzD;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,cAAc;IAC3B,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAClC,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,MAAM,CAAgB;IAE9B;;;;;;;;;;OAUG;gBACS,OAAO,EAAE;QACpB,MAAM,EAAE,aAAa,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;QACtC,UAAU,CAAC,EAAE,kBAAkB,EAAE,CAAC;QAClC,GAAG,CAAC,EAAE,OAAO,CAAC;QACd,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;QAC1B,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;KAC5B;IASD;;OAEG;IACI,QAAQ,IAAI,MAAM;IAiBzB;;OAEG;IACI,OAAO,IAAI,OAAO;IAIzB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IA8BlB;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAoEpB;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IA6HnB;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;CAsBhB"}

View File

@@ -0,0 +1,297 @@
import CSSMeasurementConverter from '../css/declaration/measurement-converter/CSSMeasurementConverter.js';
import WindowBrowserContext from '../window/WindowBrowserContext.js';
import MediaQueryTypeEnum from './MediaQueryTypeEnum.js';
/**
* Media query this.
*/
export default class MediaQueryItem {
mediaTypes;
not;
rules;
ranges;
rootFontSize = null;
window;
/**
* Constructor.
*
* @param options Options.
* @param options.window Owner window.
* @param [options.rootFontSize] Root font size.
* @param [options.mediaTypes] Media types.
* @param [options.not] Not.
* @param [options.rules] Rules.
* @param [options.ranges] Ranges.
*/
constructor(options) {
this.window = options.window;
this.rootFontSize = options.rootFontSize || null;
this.mediaTypes = options.mediaTypes || [];
this.not = options.not || false;
this.rules = options.rules || [];
this.ranges = options.ranges || [];
}
/**
* Returns media string.
*/
toString() {
return `${this.not ? 'not ' : ''}${this.mediaTypes.join(', ')}${(this.not || this.mediaTypes.length > 0) && !!this.ranges.length ? ' and ' : ''}${this.ranges
.map((range) => `(${range.before ? `${range.before.value} ${range.before.operator} ` : ''}${range.type}${range.after ? ` ${range.after.operator} ${range.after.value}` : ''})`)
.join(' and ')}${(this.not || this.mediaTypes.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.
*/
matches() {
return this.not ? !this.matchesAll() : this.matchesAll();
}
/**
* Returns "true" if all matches.
*
* @returns "true" if all matches.
*/
matchesAll() {
if (!!this.mediaTypes.length) {
let isMediaTypeMatch = false;
for (const mediaType of this.mediaTypes) {
if (this.matchesMediaType(mediaType)) {
isMediaTypeMatch = true;
break;
}
}
if (!isMediaTypeMatch) {
return false;
}
}
for (const rule of this.rules) {
if (!this.matchesRule(rule)) {
return false;
}
}
for (const range of this.ranges) {
if (!this.matchesRange(range)) {
return false;
}
}
return true;
}
/**
* Returns "true" if the mediaType matches.
*
* @param mediaType Media type.
* @returns "true" if the mediaType matches.
*/
matchesMediaType(mediaType) {
if (mediaType === MediaQueryTypeEnum.all) {
return true;
}
return mediaType === new WindowBrowserContext(this.window).getSettings()?.device.mediaType;
}
/**
* Returns "true" if the range matches.
*
* @param range Range.
* @returns "true" if the range matches.
*/
matchesRange(range) {
const windowSize = range.type === 'width' ? this.window.innerWidth : this.window.innerHeight;
if (range.before) {
const beforeValue = this.toPixels(range.before.value);
if (beforeValue === null) {
return false;
}
switch (range.before.operator) {
case '<':
if (beforeValue >= windowSize) {
return false;
}
break;
case '<=':
if (beforeValue > windowSize) {
return false;
}
break;
case '>':
if (beforeValue <= windowSize) {
return false;
}
break;
case '>=':
if (beforeValue < windowSize) {
return false;
}
break;
}
}
if (range.after) {
const afterValue = this.toPixels(range.after.value);
if (afterValue === null) {
return false;
}
switch (range.after.operator) {
case '<':
if (windowSize >= afterValue) {
return false;
}
break;
case '<=':
if (windowSize > afterValue) {
return false;
}
break;
case '>':
if (windowSize <= afterValue) {
return false;
}
break;
case '>=':
if (windowSize < afterValue) {
return false;
}
break;
}
}
return true;
}
/**
* Returns "true" if the rule matches.
*
* @param rule Rule.
* @returns "true" if the rule matches.
*/
matchesRule(rule) {
const settings = new WindowBrowserContext(this.window).getSettings();
if (!settings) {
return false;
}
if (!rule.value) {
switch (rule.name) {
case 'min-width':
case 'max-width':
case 'min-height':
case 'max-height':
case 'width':
case '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;
case 'prefers-reduced-motion':
return settings.device.prefersReducedMotion === 'reduce';
case 'forced-colors':
return settings.device.forcedColors === 'active';
}
return false;
}
switch (rule.name) {
case 'min-width':
const minWidth = this.toPixels(rule.value);
return minWidth !== null && this.window.innerWidth >= minWidth;
case 'max-width':
const maxWidth = this.toPixels(rule.value);
return maxWidth !== null && this.window.innerWidth <= maxWidth;
case 'min-height':
const minHeight = this.toPixels(rule.value);
return minHeight !== null && this.window.innerHeight >= minHeight;
case 'max-height':
const maxHeight = this.toPixels(rule.value);
return maxHeight !== null && this.window.innerHeight <= maxHeight;
case 'width':
const width = this.toPixels(rule.value);
return width !== null && this.window.innerWidth === width;
case 'height':
const height = this.toPixels(rule.value);
return height !== null && this.window.innerHeight === height;
case 'orientation':
return rule.value === 'landscape'
? this.window.innerWidth > this.window.innerHeight
: this.window.innerWidth < this.window.innerHeight;
case 'prefers-color-scheme':
return rule.value === settings.device.prefersColorScheme;
case 'prefers-reduced-motion':
return rule.value === settings.device.prefersReducedMotion;
case 'forced-colors':
return ((rule.value === 'none' || rule.value === 'active') &&
rule.value === settings.device.forcedColors);
case 'any-hover':
case 'hover':
if (rule.value === 'none') {
return this.window.navigator.maxTouchPoints > 0;
}
if (rule.value === 'hover') {
return this.window.navigator.maxTouchPoints === 0;
}
return false;
case 'any-pointer':
case 'pointer':
if (rule.value === 'none') {
return false;
}
if (rule.value === 'coarse') {
return this.window.navigator.maxTouchPoints > 0;
}
if (rule.value === 'fine') {
return this.window.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 aspectRatioWidth = parseInt(aspectRatio[0], 10);
const aspectRatioHeight = parseInt(aspectRatio[1], 10);
if (isNaN(aspectRatioWidth) || isNaN(aspectRatioHeight)) {
return false;
}
switch (rule.name) {
case 'min-aspect-ratio':
return (aspectRatioWidth / aspectRatioHeight <=
this.window.innerWidth / this.window.innerHeight);
case 'max-aspect-ratio':
return (aspectRatioWidth / aspectRatioHeight >=
this.window.innerWidth / this.window.innerHeight);
case 'aspect-ratio':
return (aspectRatioWidth / aspectRatioHeight ===
this.window.innerWidth / this.window.innerHeight);
}
}
return false;
}
/**
* Convert to pixels.
*
* @param value Value.
* @returns Value in pixels.
*/
toPixels(value) {
if (!new WindowBrowserContext(this.window).getSettings()?.disableComputedStyleRendering &&
value.endsWith('em')) {
this.rootFontSize =
this.rootFontSize ||
parseFloat(this.window.getComputedStyle(this.window.document.documentElement).fontSize);
return CSSMeasurementConverter.toPixels({
window: this.window,
value,
rootFontSize: this.rootFontSize,
parentFontSize: this.rootFontSize
});
}
return CSSMeasurementConverter.toPixels({
window: this.window,
value,
rootFontSize: 16,
parentFontSize: 16
});
}
}
//# sourceMappingURL=MediaQueryItem.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,62 @@
import EventTarget from '../event/EventTarget.js';
import Event from '../event/Event.js';
import BrowserWindow from '../window/BrowserWindow.js';
import TEventListener from '../event/TEventListener.js';
/**
* Media Query List.
*
* Reference:
* https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList.
*/
export default class MediaQueryList extends EventTarget {
#private;
onchange: (event: Event) => void;
/**
* Constructor.
*
* @param options Options.
* @param options.window Owner window.
* @param options.media Media.
* @param [options.rootFontSize] Root font size.
*/
constructor(options: {
window: BrowserWindow;
media: string;
rootFontSize?: string | number;
});
/**
* Returns media.
*
* @returns Media.
*/
get media(): string;
/**
* Returns "true" if the document matches.
*
* @returns Matches.
*/
get matches(): boolean;
/**
* Adds a listener.
*
* @deprecated
* @param callback Callback.
*/
addListener(callback: (event: Event) => void): void;
/**
* Removes listener.
*
* @deprecated
* @param callback Callback.
*/
removeListener(callback: (event: Event) => void): void;
/**
* @override
*/
addEventListener(type: string, listener: TEventListener): void;
/**
* @override
*/
removeEventListener(type: string, listener: TEventListener): void;
}
//# sourceMappingURL=MediaQueryList.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MediaQueryList.d.ts","sourceRoot":"","sources":["../../src/match-media/MediaQueryList.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,yBAAyB,CAAC;AAElD,OAAO,KAAK,MAAM,mBAAmB,CAAC;AACtC,OAAO,aAAa,MAAM,4BAA4B,CAAC;AACvD,OAAO,cAAc,MAAM,4BAA4B,CAAC;AAKxD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,WAAW;;IAC/C,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAQ;IAM/C;;;;;;;OAOG;gBACS,OAAO,EAAE;QAAE,MAAM,EAAE,aAAa,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE;IAO7F;;;;OAIG;IACH,IAAW,KAAK,IAAI,MAAM,CAUzB;IAED;;;;OAIG;IACH,IAAW,OAAO,IAAI,OAAO,CAgB5B;IAED;;;;;OAKG;IACI,WAAW,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAI1D;;;;;OAKG;IACI,cAAc,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAI7D;;OAEG;IACI,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;IAgBrE;;OAEG;IACI,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;CAMxE"}

View File

@@ -0,0 +1,112 @@
import EventTarget from '../event/EventTarget.js';
import * as PropertySymbol from '../PropertySymbol.js';
import MediaQueryListEvent from '../event/events/MediaQueryListEvent.js';
import MediaQueryParser from './MediaQueryParser.js';
/**
* Media Query List.
*
* Reference:
* https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList.
*/
export default class MediaQueryList extends EventTarget {
onchange = null;
#window;
#items = null;
#media;
#rootFontSize = null;
/**
* Constructor.
*
* @param options Options.
* @param options.window Owner window.
* @param options.media Media.
* @param [options.rootFontSize] Root font size.
*/
constructor(options) {
super();
this.#window = options.window;
this.#media = options.media;
this.#rootFontSize = options.rootFontSize || null;
}
/**
* Returns media.
*
* @returns Media.
*/
get media() {
this.#items =
this.#items ||
MediaQueryParser.parse({
window: this.#window,
mediaQuery: this.#media,
rootFontSize: this.#rootFontSize
});
return this.#items.map((item) => item.toString()).join(', ');
}
/**
* Returns "true" if the document matches.
*
* @returns Matches.
*/
get matches() {
this.#items =
this.#items ||
MediaQueryParser.parse({
window: this.#window,
mediaQuery: this.#media,
rootFontSize: this.#rootFontSize
});
for (const item of this.#items) {
if (!item.matches()) {
return false;
}
}
return true;
}
/**
* Adds a listener.
*
* @deprecated
* @param callback Callback.
*/
addListener(callback) {
this.addEventListener('change', callback);
}
/**
* Removes listener.
*
* @deprecated
* @param callback Callback.
*/
removeListener(callback) {
this.removeEventListener('change', callback);
}
/**
* @override
*/
addEventListener(type, listener) {
super.addEventListener(type, listener);
if (type === 'change') {
let matchesState = false;
const resizeListener = () => {
const matches = this.matches;
if (matches !== matchesState) {
matchesState = matches;
this.dispatchEvent(new MediaQueryListEvent('change', { matches, media: this.media }));
}
};
listener[PropertySymbol.windowResizeListener] = resizeListener;
this.#window.addEventListener('resize', resizeListener);
}
}
/**
* @override
*/
removeEventListener(type, listener) {
super.removeEventListener(type, listener);
if (type === 'change' && listener[PropertySymbol.windowResizeListener]) {
this.#window.removeEventListener('resize', listener[PropertySymbol.windowResizeListener]);
}
}
}
//# sourceMappingURL=MediaQueryList.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MediaQueryList.js","sourceRoot":"","sources":["../../src/match-media/MediaQueryList.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,22 @@
import MediaQueryItem from './MediaQueryItem.js';
import BrowserWindow from '../window/BrowserWindow.js';
/**
* Utility for parsing a query string.
*/
export default class MediaQueryParser {
/**
* Parses a media query string.
*
* @param options Options.
* @param options.window Owner window.
* @param options.mediaQuery Media query string.
* @param [options.rootFontSize] Root font size.
* @returns Media query items.
*/
static parse(options: {
window: BrowserWindow;
mediaQuery: string;
rootFontSize?: string | number | null;
}): MediaQueryItem[];
}
//# sourceMappingURL=MediaQueryParser.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MediaQueryParser.d.ts","sourceRoot":"","sources":["../../src/match-media/MediaQueryParser.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,qBAAqB,CAAC;AAEjD,OAAO,aAAa,MAAM,4BAA4B,CAAC;AA8BvD;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACpC;;;;;;;;OAQG;WACW,KAAK,CAAC,OAAO,EAAE;QAC5B,MAAM,EAAE,aAAa,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;KACtC,GAAG,cAAc,EAAE;CA+DpB"}

View File

@@ -0,0 +1,106 @@
import MediaQueryItem from './MediaQueryItem.js';
import MediaQueryTypeEnum from './MediaQueryTypeEnum.js';
/**
* Media query RegExp.
*
* Group 1: "not", "only", "all", "screen", "print".
* Group 2: Rule.
* Group 3: Rule end paranthesis (must be present if no value).
* Group 4: Comma (,).
* Group 5: "or", "and".
*/
const MEDIA_QUERY_REGEXP = /(not|only|all|screen|print)|\(([^\)]+)(\)){0,1}|(,)| +(or|and) +/g;
/**
* Check if resolution RegExp.
*/
const IS_RESOLUTION_REGEXP = /[<>]/;
/**
* Resolution RegExp.
*
* Group 1: First resolution value.
* Group 2: First resolution operator.
* Group 3: Resolution type.
* Group 4: Second resolution operator.
* Group 5: Second resolution value.
*/
const RESOLUTION_REGEXP = /(?:([0-9]+[a-z]+) *(<|<=|>|=>)){0,1} *(width|height) *(?:(<|<=|>|=>) *([0-9]+[a-z]+)){0,1}/;
/**
* Utility for parsing a query string.
*/
export default class MediaQueryParser {
/**
* Parses a media query string.
*
* @param options Options.
* @param options.window Owner window.
* @param options.mediaQuery Media query string.
* @param [options.rootFontSize] Root font size.
* @returns Media query items.
*/
static parse(options) {
let currentMediaQueryItem = new MediaQueryItem({
window: options.window,
rootFontSize: options.rootFontSize
});
const mediaQueryItems = [currentMediaQueryItem];
const regexp = new RegExp(MEDIA_QUERY_REGEXP);
let match = null;
while ((match = regexp.exec(options.mediaQuery.toLowerCase()))) {
if (match[4] === ',' || match[5] === 'or') {
currentMediaQueryItem = new MediaQueryItem({
window: options.window,
rootFontSize: options.rootFontSize
});
mediaQueryItems.push(currentMediaQueryItem);
}
else if (match[1] === 'all' || match[1] === 'screen' || match[1] === 'print') {
currentMediaQueryItem.mediaTypes.push(match[1]);
}
else if (match[1] === 'not') {
currentMediaQueryItem.not = true;
}
else if (match[2]) {
const resolutionMatch = IS_RESOLUTION_REGEXP.test(match[2])
? match[2].match(RESOLUTION_REGEXP)
: null;
if (resolutionMatch && (resolutionMatch[1] || resolutionMatch[5])) {
currentMediaQueryItem.ranges.push({
before: resolutionMatch[1]
? {
value: resolutionMatch[1],
operator: resolutionMatch[2]
}
: null,
type: resolutionMatch[3],
after: resolutionMatch[5]
? {
value: resolutionMatch[5],
operator: resolutionMatch[4]
}
: null
});
}
else {
const [name, value] = match[2].split(':');
const trimmedValue = value ? value.trim() : null;
if (!trimmedValue && !match[3]) {
return [
new MediaQueryItem({
window: options.window,
rootFontSize: options.rootFontSize,
not: true,
mediaTypes: [MediaQueryTypeEnum.all]
})
];
}
currentMediaQueryItem.rules.push({
name: name.trim(),
value: trimmedValue
});
}
}
}
return mediaQueryItems;
}
}
//# sourceMappingURL=MediaQueryParser.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MediaQueryParser.js","sourceRoot":"","sources":["../../src/match-media/MediaQueryParser.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,7 @@
declare enum MediaQueryTypeEnum {
all = "all",
print = "print",
screen = "screen"
}
export default MediaQueryTypeEnum;
//# sourceMappingURL=MediaQueryTypeEnum.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MediaQueryTypeEnum.d.ts","sourceRoot":"","sources":["../../src/match-media/MediaQueryTypeEnum.ts"],"names":[],"mappings":"AAAA,aAAK,kBAAkB;IACtB,GAAG,QAAQ;IACX,KAAK,UAAU;IACf,MAAM,WAAW;CACjB;AAED,eAAe,kBAAkB,CAAC"}

View File

@@ -0,0 +1,8 @@
var MediaQueryTypeEnum;
(function (MediaQueryTypeEnum) {
MediaQueryTypeEnum["all"] = "all";
MediaQueryTypeEnum["print"] = "print";
MediaQueryTypeEnum["screen"] = "screen";
})(MediaQueryTypeEnum || (MediaQueryTypeEnum = {}));
export default MediaQueryTypeEnum;
//# sourceMappingURL=MediaQueryTypeEnum.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MediaQueryTypeEnum.js","sourceRoot":"","sources":["../../src/match-media/MediaQueryTypeEnum.ts"],"names":[],"mappings":"AAAA,IAAK,kBAIJ;AAJD,WAAK,kBAAkB;IACtB,iCAAW,CAAA;IACX,qCAAe,CAAA;IACf,uCAAiB,CAAA;AAClB,CAAC,EAJI,kBAAkB,KAAlB,kBAAkB,QAItB;AAED,eAAe,kBAAkB,CAAC"}