react-split-flap
Version:
Split-flap display component for React
501 lines (484 loc) • 27.4 kB
JavaScript
;
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var reactDom = require('react-dom');
function styleInject(css, ref) {
if ( ref === void 0 ) ref = {};
var insertAt = ref.insertAt;
if (typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css_248z = "@keyframes flapDownTopA{0%{transform:rotateX(0deg) translateZ(0)}50%,to{transform:rotateX(90deg) translateZ(0)}}@keyframes flapDownTopB{0%{transform:rotateX(0deg) translateZ(0)}50%,to{transform:rotateX(90deg) translateZ(0)}}@keyframes flapDownBottomA{0%,50%{transform:rotateX(90deg) translateZ(0)}80%{transform:rotateX(0deg) translateZ(0)}90%{transform:rotateX(20deg) translateZ(0)}to{transform:rotateX(0deg) translateZ(0)}}@keyframes flapDownBottomB{0%,50%{transform:rotateX(90deg) translateZ(0)}80%{transform:rotateX(0deg) translateZ(0)}90%{transform:rotateX(20deg) translateZ(0)}to{transform:rotateX(0deg) translateZ(0)}}.split-flap-display{align-items:center;display:flex;flex-direction:row;gap:2px;justify-content:center;perspective:1000px}.split-flap-display.words-mode{gap:0}.split-flap-digit{background-color:#182216;border-radius:4px;box-sizing:content-box;color:var(--flap-font-color,#fff);contain:layout style paint;display:inline-block;font-family:Monaco,Menlo,Ubuntu Mono,monospace,sans-serif;font-weight:700;height:1em;line-height:1em;overflow:hidden;position:relative;text-align:center;width:1.1ch}.split-flap-digit[data-mode=alpha]{width:1.7ch}.split-flap-digit[data-mode=words]{min-width:2ch;width:100%}.split-flap-part{align-items:center;animation-fill-mode:forwards;background-color:inherit;background:var(--flap-background,linear-gradient(145deg,#1a1a1a 0,#191919 15%));background-size:100% 200%;border:1px solid #333;border-radius:inherit;box-shadow:0 1px 3px rgba(0,0,0,.3);box-sizing:border-box;display:flex;height:50%;justify-content:center;overflow:hidden;position:absolute;width:100%;z-index:1}.split-flap-part.animated{animation-duration:var(--flap-animation-duration,.3s);z-index:2}.split-flap-part.animated.idle{visibility:hidden}.split-flap-part.top{background-position:top;border-bottom-left-radius:0;border-bottom-right-radius:0;top:0;transform-origin:center bottom}.split-flap-part.top.animated{opacity:.4;transform:rotateX(50deg)}.split-flap-part.top.animated.final{animation-timing-function:ease-in;opacity:1}.split-flap-part.top.animated.final.phase-a{animation-name:flapDownTopA}.split-flap-part.top.animated.final.phase-b{animation-name:flapDownTopB}.split-flap-part.bottom{background-position:bottom;border-top-left-radius:0;border-top-right-radius:0;bottom:0;transform-origin:center top}.split-flap-part.bottom.animated.final{animation-timing-function:ease-out}.split-flap-part.bottom.animated.final.phase-a{animation-name:flapDownBottomA}.split-flap-part.bottom.animated.final.phase-b{animation-name:flapDownBottomB}.split-flap-part.bottom.animated:not(.final){visibility:hidden}.split-flap-char{align-items:center;display:flex;font-size:inherit;height:calc(200% + 4px);justify-content:center;left:0;line-height:1;position:absolute;width:100%}.split-flap-part.top .split-flap-char{top:-1px}.split-flap-part.bottom .split-flap-char{bottom:-1px}.split-flap-hinge{background:#000;box-shadow:0 0 2px rgba(0,0,0,.3);box-sizing:border-box;height:1px;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%);z-index:3}.split-flap-display.light .split-flap-digit{background:#f0f0f0;color:var(--flap-font-color,#333)}.split-flap-display.light .split-flap-part{background:var(--flap-background,linear-gradient(145deg,#f8f8f8,#e8e8e8));border:1px solid #ccc;box-shadow:inset 2px 2px 4px hsla(0,0%,100%,.5),inset -2px -2px 10px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.2)}.split-flap-display.light .split-flap-hinge{background:linear-gradient(90deg,#b9b9b9,#cfcfcf 50%,#b9b9b9);box-shadow:0 0 2px rgba(0,0,0,.2)}.split-flap-display.dark .split-flap-digit{background:#1a1a1a;color:var(--flap-font-color,#fff)}.split-flap-display.dark .split-flap-part{background:var(--flap-background,linear-gradient(184deg,#212121 0,#191919 25%));border:1px solid #222}.split-flap-display.dark .split-flap-hinge{background:linear-gradient(90deg,#333,#222 50%,#333)}.split-flap-display.small{font-size:20px}.split-flap-display.medium{font-size:36px}.split-flap-display.large{font-size:54px}.split-flap-display.xlarge{font-size:84px}.split-flap-display.long-flap-mode{font-size:inherit}.split-flap-digit:before{background:linear-gradient(135deg,hsla(0,0%,100%,.1),hsla(0,0%,100%,.05) 25%,hsla(0,0%,100%,0) 50%,rgba(0,0,0,.05) 75%,rgba(0,0,0,.1));border-radius:inherit;bottom:0;content:\"\";left:0;pointer-events:none;position:absolute;right:0;top:0;z-index:4}.split-flap-display.performance-mode{--flap-animation-duration:160ms}.split-flap-display.performance-mode .split-flap-digit{contain:strict}.split-flap-display.performance-mode .split-flap-hinge,.split-flap-display.performance-mode .split-flap-part{box-shadow:none}.split-flap-display.performance-mode .split-flap-digit:before{content:none}@media (prefers-reduced-motion:reduce){.split-flap-part.animated{animation-duration:50ms}.split-flap-part.top.animated{opacity:1;transform:none}}@media (prefers-contrast:high){.split-flap-digit{border:2px solid}.split-flap-display.light .split-flap-digit{background:#fff;color:#000}.split-flap-display.dark .split-flap-digit{background:#000;color:#fff}}.split-flap-display.words-mode .split-flap-digit{border-radius:0;margin:0}.split-flap-display.words-mode .split-flap-digit:first-child{border-bottom-left-radius:4px;border-top-left-radius:4px}.split-flap-display.words-mode .split-flap-digit:last-child{border-bottom-right-radius:4px;border-top-right-radius:4px}.split-flap-display.words-mode .split-flap-digit:only-child{border-radius:4px}.split-flap-display:focus-within .split-flap-digit{box-shadow:0 0 0 2px rgba(59,130,246,.5)}.split-flap-part:not(.animated){transition:background-color .2s ease,border-color .2s ease}.split-flap-display.long-flap-mode .split-flap-digit{height:var(--digit-height,auto);min-height:var(--digit-height,auto);min-width:var(--digit-width,auto);width:var(--digit-width,auto)}.split-flap-display.long-flap-mode .split-flap-char{align-items:center;display:flex;height:calc(200% + 4px);justify-content:center;width:100%}";
styleInject(css_248z);
const Flap = React.forwardRef(({ bottom, animated, final, phase = 0, idle, children }, ref) => {
const classes = [
'split-flap-part',
bottom ? 'bottom' : 'top',
animated ? 'animated' : '',
final ? 'final' : '',
idle ? 'idle' : '',
animated && final ? `phase-${phase === 0 ? 'a' : 'b'}` : '',
]
.filter(Boolean)
.join(' ');
return (jsxRuntime.jsx("div", { ref: ref, className: classes, children: jsxRuntime.jsx("span", { className: "split-flap-char", children: children }) }));
});
Flap.displayName = 'Flap';
// Shared rAF-driven clock so all digits tick in the same frame,
// letting React batch every flip into a single commit
const subscribers = new Set();
let rafId = null;
let timerId = null;
// rAF stops in hidden tabs; fall back to a coarse timer so displays
// still reach their target value while backgrounded
const HIDDEN_TICK = 250;
const schedule = () => {
if (subscribers.size === 0 || rafId !== null || timerId !== null)
return;
if (typeof document !== 'undefined' && document.hidden) {
timerId = setTimeout(() => {
timerId = null;
loop(performance.now());
}, HIDDEN_TICK);
}
else {
rafId = requestAnimationFrame(loop);
}
};
const loop = (now) => {
rafId = null;
reactDom.unstable_batchedUpdates(() => {
subscribers.forEach((sub) => {
if (now >= sub.next) {
sub.next = now + sub.interval;
sub.callback();
}
});
});
schedule();
};
const subscribeTick = (callback, interval) => {
const sub = { callback, interval, next: performance.now() + interval };
subscribers.add(sub);
schedule();
return () => {
subscribers.delete(sub);
if (subscribers.size === 0) {
if (rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
if (timerId !== null) {
clearTimeout(timerId);
timerId = null;
}
}
};
};
const FlapDigit = ({ value, prevValue, final, revision, mode, hinge, digitWidth }) => {
const digitStyle = digitWidth ? { width: `${digitWidth}px` } : {};
const hasTransition = revision > 0 && value !== prevValue;
const shouldAnimate = final && hasTransition;
const phase = (revision % 2);
return (jsxRuntime.jsxs("div", { className: "split-flap-digit", "data-kind": "digit", "data-mode": mode, style: digitStyle, children: [jsxRuntime.jsx(Flap, { children: value }), jsxRuntime.jsx(Flap, { bottom: true, children: prevValue }), jsxRuntime.jsx(Flap, { animated: true, final: shouldAnimate, phase: phase, idle: !hasTransition, children: prevValue }), jsxRuntime.jsx(Flap, { bottom: true, animated: true, final: shouldAnimate, phase: phase, idle: !hasTransition, children: value }), hinge && jsxRuntime.jsx("div", { className: "split-flap-hinge", "data-kind": "hinge" })] }));
};
var FlapDigit$1 = React.memo(FlapDigit);
const STRUCTURE_SEPARATOR = '\u0001';
const TARGET_SEPARATOR = '\u0002';
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
const createCursor = (digit, animate) => ({
current: animate ? -1 : digit.value,
previous: animate ? -1 : digit.value,
target: digit.value,
stackKey: digit.stackKey,
revision: 0,
settleAt: null,
});
// Character displays share one cursor array and one ticker subscription. A
// 400-digit board therefore performs at most one React state update per tick,
// while memoized FlapDigit children only render when their own cursor changes.
const FlapBoard = ({ digits, timing, duration, mode, hinge, digitWidth, animateOnMount = true, }) => {
const [cursors, setCursors] = React.useState(() => digits.map((digit) => createCursor(digit, animateOnMount)));
const cursorRef = React.useRef(cursors);
const digitsRef = React.useRef(digits);
digitsRef.current = digits;
const structureKey = React.useMemo(() => digits.map((digit) => digit.stackKey).join(STRUCTURE_SEPARATOR), [digits]);
const targetsKey = React.useMemo(() => digits.map((digit) => digit.value).join(TARGET_SEPARATOR), [digits]);
// Reconcile added digits and physical stack changes before paint. Existing
// cursors keep their position so a new target can continue an in-flight roll.
useIsomorphicLayoutEffect(() => {
const currentDigits = digitsRef.current;
const previous = cursorRef.current;
let changed = previous.length !== currentDigits.length;
const next = currentDigits.map((digit, index) => {
const cursor = previous[index];
if (!cursor) {
changed = true;
return createCursor(digit, animateOnMount);
}
if (cursor.stackKey !== digit.stackKey) {
changed = true;
return createCursor(digit, true);
}
return cursor;
});
if (changed) {
cursorRef.current = next;
setCursors(next);
}
}, [animateOnMount, digits.length, structureKey]);
React.useEffect(() => {
const hasPendingDigit = digitsRef.current.some((digit, index) => {
const cursor = cursorRef.current[index];
return !cursor || cursor.stackKey !== digit.stackKey || cursor.current !== digit.value;
});
if (!hasPendingDigit)
return;
let unsubscribe = () => { };
unsubscribe = subscribeTick(() => {
const currentDigits = digitsRef.current;
const previous = cursorRef.current;
let changed = previous.length !== currentDigits.length;
let hasPendingAfterTick = false;
const next = currentDigits.map((digit, index) => {
let cursor = previous[index];
if (!cursor) {
cursor = createCursor(digit, animateOnMount);
changed = true;
}
else if (cursor.stackKey !== digit.stackKey) {
cursor = createCursor(digit, true);
changed = true;
}
if (cursor.current === digit.value) {
if (cursor.target === digit.value)
return cursor;
changed = true;
return Object.assign(Object.assign({}, cursor), { target: digit.value });
}
const newCurrent = cursor.current >= digit.stack.length - 1 ? 0 : cursor.current + 1;
if (newCurrent !== digit.value)
hasPendingAfterTick = true;
changed = true;
return {
current: newCurrent,
previous: cursor.current,
target: digit.value,
stackKey: digit.stackKey,
revision: cursor.revision + 1,
settleAt: newCurrent === digit.value ? performance.now() + duration : null,
};
});
if (changed) {
cursorRef.current = next;
setCursors(next);
}
if (!hasPendingAfterTick)
unsubscribe();
}, timing);
return unsubscribe;
}, [animateOnMount, duration, structureKey, targetsKey, timing]);
const settleKey = React.useMemo(() => cursors.map((cursor) => { var _a; return (_a = cursor.settleAt) !== null && _a !== void 0 ? _a : ''; }).join(TARGET_SEPARATOR), [cursors]);
// Once the compositor animation has finished, promote the current face to
// the static bottom layer. This prevents Safari from briefly revealing the
// previous transition's stale underlay while preparing the next flip.
React.useEffect(() => {
const deadlines = cursors
.map((cursor) => cursor.settleAt)
.filter((deadline) => deadline !== null);
if (deadlines.length === 0)
return;
const earliest = Math.min(...deadlines);
const timer = setTimeout(() => {
const previous = cursorRef.current;
let changed = false;
const next = previous.map((cursor) => {
if (cursor.settleAt === null || cursor.settleAt > earliest + 1)
return cursor;
changed = true;
return Object.assign(Object.assign({}, cursor), { previous: cursor.current, settleAt: null });
});
if (changed) {
cursorRef.current = next;
setCursors(next);
}
}, Math.max(0, Math.ceil(earliest - performance.now()) + 1));
return () => clearTimeout(timer);
}, [cursors, settleKey]);
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: digits.map((digit, index) => {
const storedCursor = cursors[index];
const cursor = (storedCursor === null || storedCursor === void 0 ? void 0 : storedCursor.stackKey) === digit.stackKey
? storedCursor
: createCursor(digit, storedCursor ? true : animateOnMount);
return (jsxRuntime.jsx(FlapDigit$1, { value: digit.stack[cursor.current] || '', prevValue: digit.stack[cursor.previous] || '', final: cursor.current === digit.value, revision: cursor.revision, mode: mode, hinge: hinge, digitWidth: digitWidth }, index));
}) }));
};
const FlapStack = ({ stack, stackKey, value, timing, duration, mode, hinge, digitWidth, animateOnMount = true, }) => {
const stackLength = stack.length;
const [cursor, setCursor] = React.useState(() => {
if (animateOnMount) {
return { current: -1, previous: -1, target: 0, revision: 0 };
}
const target = Math.max(0, Math.min(value, stackLength - 1));
return { current: target, previous: target, target, revision: 0 };
});
// Reset cursor when the flap set changes (identity via stackKey, not array reference)
const mountedRef = React.useRef(false);
React.useEffect(() => {
if (!mountedRef.current) {
mountedRef.current = true;
return;
}
setCursor({ current: -1, previous: -1, target: 0, revision: 0 });
}, [stackKey]);
// Animation sequence when value changes
React.useEffect(() => {
const target = Math.max(0, Math.min(value, stackLength - 1));
const unsubscribe = subscribeTick(() => {
setCursor((prevCursor) => {
if (prevCursor.current === target) {
unsubscribe();
return prevCursor.target === target ? prevCursor : Object.assign(Object.assign({}, prevCursor), { target });
}
const newCurrent = prevCursor.current >= stackLength - 1 ? 0 : prevCursor.current + 1;
if (newCurrent === target) {
unsubscribe();
}
return {
current: newCurrent,
previous: prevCursor.current,
target,
revision: prevCursor.revision + 1,
};
});
}, timing);
return unsubscribe;
}, [stackKey, stackLength, value, timing]);
React.useEffect(() => {
if (cursor.current !== cursor.target || cursor.previous === cursor.current)
return;
const revision = cursor.revision;
const timer = setTimeout(() => {
setCursor((current) => current.revision === revision && current.current === current.target
? Object.assign(Object.assign({}, current), { previous: current.current }) : current);
}, duration);
return () => clearTimeout(timer);
}, [cursor.current, cursor.previous, cursor.revision, cursor.target, duration]);
const { current, previous, target, revision } = cursor;
return (jsxRuntime.jsx(FlapDigit$1, { value: stack[current] || '', prevValue: stack[previous] || '', final: current === target, revision: revision, mode: mode, hinge: hinge, digitWidth: digitWidth }));
};
// Parent displays often update one row or digit at a time. stackKey is the
// declared identity of the physical flap set, so comparing it avoids walking
// every unchanged digit while still allowing local cursor state to animate.
var FlapStack$1 = React.memo(FlapStack, (previous, next) => previous.stackKey === next.stackKey &&
// Words mode may contain React nodes whose content changes while ids stay stable.
(previous.mode !== 'words' || previous.stack === next.stack) &&
previous.value === next.value &&
previous.timing === next.timing &&
previous.duration === next.duration &&
previous.mode === next.mode &&
previous.hinge === next.hinge &&
previous.digitWidth === next.digitWidth &&
previous.animateOnMount === next.animateOnMount);
const Presets = {
NUM: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
ALPHANUM: [
' ',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
],
SPECIAL: [
' ',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'!',
'@',
'#',
'$',
'%',
'^',
'&',
'*',
'(',
')',
'.',
',',
'?',
'-',
'_',
],
};
// Joiner unlikely to appear in user-provided chars
const KEY_SEPARATOR = '\u0000';
const SplitFlap = ({ value, chars = Presets.NUM, length, mode, padChar = ' ', align, padMode, animateOnMount = true, digitWidth, timing = 60, duration, hinge = true, theme = 'default', size = 'medium', className = '', style, background, fontColor, render, }) => {
const effectiveDuration = duration !== null && duration !== void 0 ? duration : (className.split(/\s+/).includes('performance-mode') ? 160 : 300);
// Legacy: length === 1 used to mean words mode
const wordsMode = mode === 'words' || (mode === undefined && length === 1);
const displayLength = length !== null && length !== void 0 ? length : value.length;
const charsKey = React.useMemo(() => chars.join(KEY_SEPARATOR), [chars]);
// Only uppercase the value when the char set itself is uppercase,
// so lowercase char sets stay matchable
const uppercase = React.useMemo(() => chars.every((char) => char === char.toUpperCase()), [chars]);
const digitMode = React.useMemo(() => (chars.every((char) => /^[\s0-9]$/.test(char)) ? 'num' : 'alpha'), [chars]);
const displayValue = React.useMemo(() => {
let processedValue = uppercase ? value.toUpperCase() : value;
if (wordsMode)
return processedValue;
if (processedValue.length < displayLength) {
const padding = padChar.repeat(displayLength - processedValue.length);
const effectiveAlign = align !== null && align !== void 0 ? align : (padMode === 'start' ? 'left' : padMode === 'end' ? 'right' : 'auto');
const padStart = effectiveAlign === 'right' || (effectiveAlign === 'auto' && /^[0-9.,+-]*$/.test(processedValue));
processedValue = padStart ? padding + processedValue : processedValue + padding;
}
else if (processedValue.length > displayLength) {
processedValue = processedValue.substring(0, displayLength);
}
return processedValue;
}, [value, uppercase, wordsMode, displayLength, padChar, align, padMode]);
// Chars missing from the set get an extended stack: a full rotation
// through all chars before landing on the fallback
const findCharInStack = (char) => {
const index = chars.indexOf(char);
if (index !== -1) {
return { index, stack: chars, key: charsKey };
}
const extendedStack = [...chars, char];
return { index: extendedStack.length - 1, stack: extendedStack, key: charsKey + KEY_SEPARATOR + char };
};
const digits = React.useMemo(() => {
if (wordsMode)
return [];
const result = [];
for (let i = 0; i < displayLength; i++) {
const char = displayValue[i] || padChar;
const { index, stack, key } = findCharInStack(char);
result.push({ stack, stackKey: key, value: index });
}
return result;
}, [chars, charsKey, displayLength, displayValue, padChar, wordsMode]);
const renderDigits = () => {
if (wordsMode) {
const { index, stack, key } = findCharInStack(displayValue);
return (jsxRuntime.jsx(FlapStack$1, { stack: stack, stackKey: key, value: index, mode: "words", timing: timing, duration: effectiveDuration, hinge: hinge, digitWidth: digitWidth, animateOnMount: animateOnMount }, 0));
}
return (jsxRuntime.jsx(FlapBoard, { digits: digits, timing: timing, duration: effectiveDuration, mode: digitMode, hinge: hinge, digitWidth: digitWidth, animateOnMount: animateOnMount }));
};
const displayClasses = [
'split-flap-display',
theme !== 'default' ? theme : '',
size,
wordsMode ? 'words-mode' : '',
className,
]
.filter(Boolean)
.join(' ');
const combinedStyle = Object.assign(Object.assign(Object.assign(Object.assign({}, style), { '--flap-animation-duration': `${effectiveDuration}ms` }), (background && { '--flap-background': background })), (fontColor && { '--flap-font-color': fontColor }));
// role="img" exposes the label while keeping the flap internals out of the a11y tree
const content = (jsxRuntime.jsx("div", { className: displayClasses, style: combinedStyle, role: "img", "aria-label": value, children: renderDigits() }));
return render ? render(content) : content;
};
var SplitFlap$1 = React.memo(SplitFlap);
const LongFlap = ({ flaps, displayId, animateOnMount = true, digitWidth, digitHeight = 50, timing = 60, duration, hinge = true, theme = 'default', size = 'medium', className = '', style, background, fontColor, render, }) => {
const effectiveDuration = duration !== null && duration !== void 0 ? duration : (className.split(/\s+/).includes('performance-mode') ? 160 : 300);
const flapStack = React.useMemo(() => flaps.map((f) => jsxRuntime.jsx(React.Fragment, { children: f.component }, f.id)), [flaps]);
// Cursor identity follows the id list, so inline-defined flaps arrays are safe
const stackKey = React.useMemo(() => flaps.map((f) => String(f.id)).join('\u0000'), [flaps]);
const currentIndex = React.useMemo(() => {
const flapIndex = flaps.findIndex((f) => f.id === displayId);
return flapIndex !== -1 ? flapIndex : 0;
}, [flaps, displayId]);
const displayClasses = ['split-flap-display', theme !== 'default' ? theme : '', size, 'long-flap-mode', className]
.filter(Boolean)
.join(' ');
const longFlapStyle = Object.assign(Object.assign(Object.assign(Object.assign({}, style), { '--digit-width': digitWidth ? `${digitWidth}px` : 'auto', '--digit-height': digitHeight ? `${digitHeight}px` : 'auto', '--flap-animation-duration': `${effectiveDuration}ms` }), (background && { '--flap-background': background })), (fontColor && { '--flap-font-color': fontColor }));
const content = (jsxRuntime.jsx("div", { className: displayClasses, style: longFlapStyle, "aria-hidden": "true", children: jsxRuntime.jsx(FlapStack$1, { stack: flapStack, stackKey: stackKey, value: currentIndex, timing: timing, duration: effectiveDuration, mode: "words", hinge: hinge, digitWidth: digitWidth, animateOnMount: animateOnMount }) }));
return render ? render(content) : content;
};
exports.LongFlap = LongFlap;
exports.Presets = Presets;
exports.SplitFlap = SplitFlap$1;
//# sourceMappingURL=index.cjs.map