UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

150 lines 4.98 kB
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