mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
fix: correct popup fallback positioning offsets (#981)
This commit is contained in:
@@ -8,7 +8,7 @@ export { tryHidePopover, tryShowPopover } from './popover';
|
||||
export { isHTMLAudioElement, isHTMLMediaElement, isHTMLVideoElement } from './predicates';
|
||||
export { type RafThrottled, rafThrottle } from './raf-throttle';
|
||||
export { getSlottedElement, querySlot } from './slotted';
|
||||
export { applyStyles } from './style';
|
||||
export { applyStyles, resolveCSSLength } from './style';
|
||||
export { supportsAnchorPositioning, supportsAnimationFrame, supportsIdleCallback } from './supports';
|
||||
export { findTrackElement, getTextTrackList } from './text-track';
|
||||
export { serializeTimeRanges } from './time-ranges';
|
||||
|
||||
@@ -9,3 +9,50 @@ export function applyStyles(element: HTMLElement, styles: Record<string, string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveCSSLength(el: Element, value: string): number {
|
||||
const trimmed = value.trim();
|
||||
|
||||
if (!trimmed) return 0;
|
||||
|
||||
const parsed = Number.parseFloat(trimmed);
|
||||
|
||||
if (Number.isNaN(parsed)) return 0;
|
||||
if (/^-?\d*\.?\d+$/.test(trimmed) || trimmed.endsWith('px')) return parsed;
|
||||
|
||||
const doc = el.ownerDocument;
|
||||
const root = doc?.documentElement;
|
||||
|
||||
if (trimmed.endsWith('rem')) {
|
||||
const rootFontSize = root ? Number.parseFloat(getComputedStyle(root).fontSize) || 16 : 16;
|
||||
return parsed * rootFontSize;
|
||||
}
|
||||
|
||||
if (trimmed.endsWith('em')) {
|
||||
const fontSize = el instanceof HTMLElement ? Number.parseFloat(getComputedStyle(el).fontSize) || 16 : 16;
|
||||
return parsed * fontSize;
|
||||
}
|
||||
|
||||
if (!doc) return parsed;
|
||||
|
||||
const measurementEl = doc.createElement('div');
|
||||
measurementEl.style.position = 'absolute';
|
||||
measurementEl.style.visibility = 'hidden';
|
||||
measurementEl.style.pointerEvents = 'none';
|
||||
measurementEl.style.inlineSize = trimmed;
|
||||
measurementEl.style.blockSize = '0';
|
||||
measurementEl.style.padding = '0';
|
||||
measurementEl.style.border = '0';
|
||||
measurementEl.style.inset = '0';
|
||||
|
||||
const parent = doc.body ?? doc.documentElement;
|
||||
|
||||
if (!parent) return parsed;
|
||||
|
||||
parent.appendChild(measurementEl);
|
||||
|
||||
const pixels = measurementEl.getBoundingClientRect().width;
|
||||
measurementEl.remove();
|
||||
|
||||
return Number.isFinite(pixels) ? pixels : parsed;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { resolveCSSLength } from '../style';
|
||||
|
||||
describe('resolveCSSLength', () => {
|
||||
it('Returns px values directly', () => {
|
||||
const el = document.createElement('div');
|
||||
|
||||
expect(resolveCSSLength(el, '8px')).toBe(8);
|
||||
});
|
||||
|
||||
it('Resolves rem values using the root font size', () => {
|
||||
const el = document.createElement('div');
|
||||
const getComputedStyleSpy = vi.spyOn(globalThis, 'getComputedStyle').mockImplementation(
|
||||
(target: Element) =>
|
||||
({
|
||||
fontSize: target === document.documentElement ? '16px' : '14px',
|
||||
}) as CSSStyleDeclaration
|
||||
);
|
||||
|
||||
expect(resolveCSSLength(el, '0.5rem')).toBe(8);
|
||||
|
||||
getComputedStyleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('Resolves em values using the element font size', () => {
|
||||
const el = document.createElement('div');
|
||||
const getComputedStyleSpy = vi.spyOn(globalThis, 'getComputedStyle').mockImplementation(
|
||||
() =>
|
||||
({
|
||||
fontSize: '14px',
|
||||
}) as CSSStyleDeclaration
|
||||
);
|
||||
|
||||
expect(resolveCSSLength(el, '1em')).toBe(14);
|
||||
|
||||
getComputedStyleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('Falls back to measurement for other CSS lengths', () => {
|
||||
const el = document.createElement('div');
|
||||
const createElement = document.createElement.bind(document);
|
||||
const appendChildSpy = vi.spyOn(document.body, 'appendChild');
|
||||
const createElementSpy = vi.spyOn(document, 'createElement').mockImplementation((tagName: string) => {
|
||||
const node = createElement(tagName);
|
||||
|
||||
if (tagName === 'div') {
|
||||
vi.spyOn(node, 'getBoundingClientRect').mockImplementation(() => {
|
||||
const width = node.style.inlineSize === '10vw' ? 24 : 0;
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: width,
|
||||
bottom: 0,
|
||||
toJSON() {},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
|
||||
expect(resolveCSSLength(el, '10vw')).toBe(24);
|
||||
expect(appendChildSpy).toHaveBeenCalled();
|
||||
|
||||
createElementSpy.mockRestore();
|
||||
appendChildSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('Preserves measured zero pixel 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: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
toJSON() {},
|
||||
}));
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
|
||||
expect(resolveCSSLength(el, 'calc(1px - 1px)')).toBe(0);
|
||||
|
||||
createElementSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user