feat(skin): improved responsive design

This commit is contained in:
Sam Potts
2026-07-15 08:23:53 +10:00
parent 82b9e43bb5
commit a9b097d9de
100 changed files with 1559 additions and 1260 deletions
+19 -7
View File
@@ -17,23 +17,22 @@ export function resolveCSSLength(el: Element, value: string): number {
const parsed = Number.parseFloat(trimmed);
if (Number.isNaN(parsed)) return 0;
if (/^-?\d*\.?\d+$/.test(trimmed) || trimmed.endsWith('px')) return parsed;
if (!Number.isNaN(parsed) && (/^-?\d*\.?\d+$/.test(trimmed) || trimmed.endsWith('px'))) return parsed;
const doc = el.ownerDocument;
const root = doc?.documentElement;
if (trimmed.endsWith('rem')) {
if (!Number.isNaN(parsed) && trimmed.endsWith('rem')) {
const rootFontSize = root ? Number.parseFloat(getComputedStyle(root).fontSize) || 16 : 16;
return parsed * rootFontSize;
}
if (trimmed.endsWith('em')) {
if (!Number.isNaN(parsed) && trimmed.endsWith('em')) {
const fontSize = el instanceof HTMLElement ? Number.parseFloat(getComputedStyle(el).fontSize) || 16 : 16;
return parsed * fontSize;
}
if (!doc) return parsed;
if (!doc) return Number.isNaN(parsed) ? 0 : parsed;
const measurementEl = doc.createElement('div');
measurementEl.style.position = 'absolute';
@@ -45,14 +44,27 @@ export function resolveCSSLength(el: Element, value: string): number {
measurementEl.style.border = '0';
measurementEl.style.inset = '0';
const computed = getComputedStyle(el);
measurementEl.style.fontSize = computed.fontSize;
for (let i = 0; i < computed.length; i++) {
const name = computed.item(i);
if (name.startsWith('--')) {
measurementEl.style.setProperty(name, computed.getPropertyValue(name));
}
}
const parent = doc.body ?? doc.documentElement;
if (!parent) return parsed;
if (!parent) return Number.isNaN(parsed) ? 0 : parsed;
parent.appendChild(measurementEl);
const pixels = measurementEl.getBoundingClientRect().width;
measurementEl.remove();
return Number.isFinite(pixels) ? pixels : parsed;
if (Number.isFinite(pixels)) return pixels;
return Number.isNaN(parsed) ? 0 : parsed;
}
+76 -1
View File
@@ -1,8 +1,12 @@
import { describe, expect, it, vi } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { resolveCSSLength } from '../style';
describe('resolveCSSLength', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('Returns px values directly', () => {
const el = document.createElement('div');
@@ -98,4 +102,75 @@ describe('resolveCSSLength', () => {
createElementSpy.mockRestore();
});
it('Falls back to measurement for calc values', () => {
const el = document.createElement('div');
const createElement = document.createElement.bind(document);
const createElementSpy = vi.spyOn(document, 'createElement').mockImplementation((tagName: string) => {
const node = createElement(tagName);
if (tagName === 'div') {
vi.spyOn(node, 'getBoundingClientRect').mockImplementation(() => ({
x: 0,
y: 0,
width: node.style.inlineSize.startsWith('calc(') ? 8 : 0,
height: 0,
top: 0,
left: 0,
right: 8,
bottom: 0,
toJSON() {},
}));
}
return node;
});
expect(resolveCSSLength(el, 'calc(0.5 * 16px)')).toBe(8);
createElementSpy.mockRestore();
});
it('Copies custom properties from the source element when measuring', () => {
const el = document.createElement('div');
const createElement = document.createElement.bind(document);
const getComputedStyleSpy = vi.spyOn(globalThis, 'getComputedStyle').mockImplementation(
() =>
({
length: 1,
fontSize: '14px',
item(index: number) {
return index === 0 ? '--size' : '';
},
getPropertyValue(name: string) {
return name === '--size' ? '16px' : '';
},
}) as CSSStyleDeclaration
);
const createElementSpy = vi.spyOn(document, 'createElement').mockImplementation((tagName: string) => {
const node = createElement(tagName);
if (tagName === 'div') {
vi.spyOn(node, 'getBoundingClientRect').mockImplementation(() => ({
x: 0,
y: 0,
width: node.style.getPropertyValue('--size') === '16px' ? 8 : 0,
height: 0,
top: 0,
left: 0,
right: 8,
bottom: 0,
toJSON() {},
}));
}
return node;
});
expect(resolveCSSLength(el, 'calc(0.5 * var(--size))')).toBe(8);
createElementSpy.mockRestore();
getComputedStyleSpy.mockRestore();
});
});