@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
150 lines • 4.98 kB
JavaScript
import { cloneElement } from 'react';
import { clsx } from 'clsx';
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
export function transition(states) {
const entries = Object.entries(states);
const [defaultName, defaultIcon] = entries[0];
const defaultPoints = parsePath(extractPathD(defaultIcon));
const canMorph = defaultPoints.length > 0 && entries.every(([name, icon]) => {
const d = extractPathD(icon);
if (!isFullyParseable(d)) {
return false;
}
if (name === defaultName) {
return true;
}
const points = parsePath(d);
return points.length === defaultPoints.length;
});
const iconFn = props => {
const activeState = props?.['data-transition-state'] || defaultName;
const {
'data-transition-state': _,
...svgProps
} = props || {};
return _jsx(_Fragment, {
children: entries.map(([name, iconRenderer]) => {
const element = iconRenderer(svgProps);
return cloneElement(element, {
key: name,
'data-icon-state': name,
className: clsx(element.props.className, name === activeState && 'dnb-icon__state--active')
});
})
});
};
iconFn.__iconTransitionFallback = true;
if (canMorph) {
const style = {};
for (const [name, icon] of entries) {
const points = name === defaultName ? defaultPoints : alignPath(defaultPoints, parsePath(extractPathD(icon)));
style[`--icon-transition-${name}`] = `path('${pointsToString(points)}')`;
}
style['--icon-transition-default'] = `var(--icon-transition-${defaultName})`;
iconFn.__iconTransitionStyle = style;
}
Object.defineProperty(iconFn, 'name', {
value: defaultIcon.name
});
return iconFn;
}
transition.activate = (element, state) => {
element.querySelectorAll('svg[data-icon-state]').forEach(svg => {
svg.classList.toggle('dnb-icon__state--active', svg.dataset.iconState === state);
});
if (element.style.getPropertyValue('--icon-transition-default')) {
element.style.setProperty('--icon-transition', `var(--icon-transition-${state})`);
}
};
export function suppressTransitions(element, callback) {
const targets = [element, ...Array.from(element.querySelectorAll('svg, svg path'))];
for (const el of targets) {
el.style.setProperty('transition', 'none');
}
callback();
element.getBoundingClientRect();
for (const el of targets) {
el.style.removeProperty('transition');
}
}
transition.isSupported = typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('d: path("M0 0")');
function extractPathD(iconFn) {
const svg = iconFn();
return svg.props.children?.props?.d;
}
function isFullyParseable(d) {
if (!d) {
return false;
}
const parts = d.match(/[a-zA-Z][^a-zA-Z]*/g) || [];
return parts.every(part => 'mlhvMLHV'.includes(part[0]));
}
function parsePath(d) {
if (!d) {
return [];
}
const parts = d.match(/[a-zA-Z][^a-zA-Z]*/g) || [];
const points = [];
let x = 0;
let y = 0;
for (const part of parts) {
const command = part[0];
const type = command.toLowerCase();
const numbers = Array.from(part.slice(1).matchAll(/-?\d*\.?\d+/g), match => Number(match[0]));
const isRelative = command === type;
const resolve = (current, value) => isRelative ? current + value : value;
if (type === 'm' || type === 'l') {
for (let i = 0; i < numbers.length; i += 2) {
x = resolve(x, numbers[i]);
y = resolve(y, numbers[i + 1]);
points.push({
cmd: i === 0 && type === 'm' ? 'M' : 'L',
x,
y
});
}
} else if (type === 'h') {
x = resolve(x, numbers[0]);
points.push({
cmd: 'L',
x,
y
});
} else if (type === 'v') {
y = resolve(y, numbers[0]);
points.push({
cmd: 'L',
x,
y
});
}
}
return points;
}
function pointsToString(points) {
return points.map(point => `${point.cmd}${point.x} ${point.y}`).join(' ');
}
function squaredDistance(pointsA, pointsB) {
return pointsA.reduce((sum, point, i) => sum + (point.x - pointsB[i].x) ** 2 + (point.y - pointsB[i].y) ** 2, 0);
}
function alignPath(reference, target) {
if (reference.length !== target.length || reference.length === 0) {
return target;
}
const subpaths = [];
for (let i = 0; i < reference.length; i++) {
if (reference[i].cmd === 'M') {
subpaths.push([[], []]);
}
subpaths[subpaths.length - 1][0].push(reference[i]);
subpaths[subpaths.length - 1][1].push(target[i]);
}
return subpaths.flatMap(([referenceSubpath, targetSubpath]) => {
const reversed = [...targetSubpath].reverse().map((point, index) => ({
...point,
cmd: index === 0 ? 'M' : 'L'
}));
return squaredDistance(referenceSubpath, reversed) < squaredDistance(referenceSubpath, targetSubpath) ? reversed : targetSubpath;
});
}
//# sourceMappingURL=IconTransition.js.map