use-scroll-animate 5.7.0 → 5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/bin/usa-codemod-6.mjs +146 -0
- package/dist/components/angular.d.cts +2 -1
- package/dist/components/angular.d.ts +2 -1
- package/dist/components/click.cjs +23 -4
- package/dist/components/click.cjs.map +1 -1
- package/dist/components/click.d.cts +12 -7
- package/dist/components/click.d.ts +12 -7
- package/dist/components/click.js +28 -9
- package/dist/components/click.js.map +1 -1
- package/dist/components/effects.cjs +669 -2
- package/dist/components/effects.cjs.map +1 -1
- package/dist/components/effects.d.cts +176 -2
- package/dist/components/effects.d.ts +176 -2
- package/dist/components/effects.js +653 -5
- package/dist/components/effects.js.map +1 -1
- package/dist/components/fx.cjs +2 -2
- package/dist/components/fx.cjs.map +1 -1
- package/dist/components/fx.js +2 -2
- package/dist/components/fx.js.map +1 -1
- package/dist/components/lite.cjs +35 -14
- package/dist/components/lite.cjs.map +1 -1
- package/dist/components/lite.js +35 -14
- package/dist/components/lite.js.map +1 -1
- package/dist/components/page.cjs +2 -0
- package/dist/components/page.cjs.map +1 -1
- package/dist/components/page.js +3 -1
- package/dist/components/page.js.map +1 -1
- package/dist/components/solid.d.cts +2 -1
- package/dist/components/solid.d.ts +2 -1
- package/dist/components/svelte.d.cts +2 -1
- package/dist/components/svelte.d.ts +2 -1
- package/dist/components/vue.d.cts +2 -1
- package/dist/components/vue.d.ts +2 -1
- package/dist/components.cjs +5 -5
- package/dist/components.d.cts +12 -7
- package/dist/components.d.ts +12 -7
- package/dist/components.js +2 -2
- package/dist/components.umd.js +2 -2
- package/dist/components.umd.js.map +1 -1
- package/docs/ROADMAP.md +2 -2
- package/docs/components.md +46 -0
- package/docs/deprecations.md +4 -0
- package/docs/upgrading-6.md +32 -0
- package/package.json +3 -2
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
var components_fx = require('./fx.cjs');
|
|
4
4
|
var base = require('../chunks/base-B5i8qQPR.cjs');
|
|
5
|
-
require('
|
|
6
|
-
require('
|
|
5
|
+
var components_tokens = require('./tokens.cjs');
|
|
6
|
+
var core = require('../chunks/core-BDcszY4L.cjs');
|
|
7
7
|
require('../chunks/fx-B8hk1Fby.cjs');
|
|
8
8
|
|
|
9
9
|
let layer = null;
|
|
@@ -1823,6 +1823,645 @@ function defineGestureFx(tag = 'usa-gesture-fx') {
|
|
|
1823
1823
|
}, { id: 'usa-gesture-fx', text: 'usa-gesture-fx{display:inline-block;touch-action:none;user-select:none}' });
|
|
1824
1824
|
}
|
|
1825
1825
|
|
|
1826
|
+
const saved = new WeakMap();
|
|
1827
|
+
/** Toggle `aria-pressed` (or set it) and return the new state. */
|
|
1828
|
+
function togglePressed(el, force) {
|
|
1829
|
+
const on = force ?? el.getAttribute('aria-pressed') !== 'true';
|
|
1830
|
+
el.setAttribute('aria-pressed', String(on));
|
|
1831
|
+
return on;
|
|
1832
|
+
}
|
|
1833
|
+
/** Swap `el`'s label for `ms` (polite live region), then restore it. */
|
|
1834
|
+
function swapLabel(el, text, ms) {
|
|
1835
|
+
if (!saved.has(el))
|
|
1836
|
+
saved.set(el, el.innerHTML);
|
|
1837
|
+
el.setAttribute('aria-live', 'polite');
|
|
1838
|
+
el.textContent = text;
|
|
1839
|
+
return new Promise((r) => setTimeout(() => {
|
|
1840
|
+
el.innerHTML = saved.get(el);
|
|
1841
|
+
saved.delete(el);
|
|
1842
|
+
el.removeAttribute('aria-live');
|
|
1843
|
+
r();
|
|
1844
|
+
}, ms));
|
|
1845
|
+
}
|
|
1846
|
+
/** Add `delta` to the number in `[data-count]` (or `el`), keeping it in `data-count`. Returns the new value. */
|
|
1847
|
+
function bumpCount(el, delta, ctx) {
|
|
1848
|
+
const t = el.querySelector('[data-count]') || el;
|
|
1849
|
+
const n = (Number(t.dataset.count ?? (t.textContent || '').replace(/[^\d.-]/g, '')) || 0) + delta;
|
|
1850
|
+
t.dataset.count = String(n);
|
|
1851
|
+
t.textContent = String(n);
|
|
1852
|
+
ctx?.animate(t, [{ transform: `translateY(${delta > 0 ? 60 : -60}%)`, opacity: 0 }, { transform: 'none', opacity: 1 }], { duration: 260, easing: 'cubic-bezier(.2,1.4,.4,1)' });
|
|
1853
|
+
return n;
|
|
1854
|
+
}
|
|
1855
|
+
const pop = (el, ctx, s = 1.25, d = 380) => ctx.animate(el, [{ transform: 'scale(1)' }, { transform: `scale(${s})`, offset: 0.4 }, { transform: 'scale(1)' }], { duration: d, easing: 'cubic-bezier(.2,1.4,.4,1)' });
|
|
1856
|
+
/** A few glyphs flying out of the pointer / centre. */
|
|
1857
|
+
function burst(el, ctx, glyph, color, n = 6, rise = false) {
|
|
1858
|
+
const { x, y } = origin(el, ctx);
|
|
1859
|
+
return all(Array.from({ length: n }, (_, i) => {
|
|
1860
|
+
const a = rise ? -Math.PI / 2 + rand(-0.6, 0.6) : (i / n) * Math.PI * 2;
|
|
1861
|
+
const d = rand(28, 56);
|
|
1862
|
+
return spawn(x - 7, y - 7, `font-size:14px;line-height:1;color:${color}`, ctx, [{ transform: 'translate(0,0) scale(.4)', opacity: 1 }, { transform: `translate(${Math.cos(a) * d}px,${Math.sin(a) * d}px) scale(1)`, opacity: 0 }], { duration: rand(500, 750), easing: 'cubic-bezier(.2,.8,.3,1)' }, glyph);
|
|
1863
|
+
}));
|
|
1864
|
+
}
|
|
1865
|
+
const click = (name, description, defaults, run) => ({ name, kind: 'click', description, defaults, run });
|
|
1866
|
+
const attn = (name, description, defaults, run) => ({ name, kind: 'attention', description, defaults, run });
|
|
1867
|
+
const MICRO_FX = [
|
|
1868
|
+
click('copy-success', 'Copies `text` (or `data-copy` / the target of `for`) to the clipboard and swaps the label to "Copied ✓".', { text: '', label: 'Copied ✓', ms: 1500 }, (el, o, ctx) => {
|
|
1869
|
+
const src = el.getAttribute('for') ? document.getElementById(el.getAttribute('for')) : null;
|
|
1870
|
+
const text = o.text || el.dataset.copy || src?.value || src?.textContent || '';
|
|
1871
|
+
navigator.clipboard?.writeText(text).catch(() => undefined);
|
|
1872
|
+
pop(el, ctx, 1.08);
|
|
1873
|
+
return swapLabel(el, o.label, o.ms);
|
|
1874
|
+
}),
|
|
1875
|
+
click('toggle-morph', 'Toggles `aria-pressed` with a squash-and-stretch morph.', {}, (el, _o, ctx) => {
|
|
1876
|
+
const on = togglePressed(el);
|
|
1877
|
+
return ctx.animate(el, [{ transform: 'scale(1,1)' }, { transform: `scale(${on ? 1.15 : 0.85},${on ? 0.85 : 1.15})`, offset: 0.35 }, { transform: 'scale(1,1)' }], { duration: 360, easing: 'ease-out' });
|
|
1878
|
+
}),
|
|
1879
|
+
click('password-reveal', 'Shows / hides the password input (`for` id, or the previous input) with an eye blink.', { show: 'Hide password', hide: 'Show password' }, (el, o, ctx) => {
|
|
1880
|
+
const input = (el.getAttribute('for') ? document.getElementById(el.getAttribute('for')) : el.previousElementSibling);
|
|
1881
|
+
if (!input || !('type' in input))
|
|
1882
|
+
return;
|
|
1883
|
+
const show = input.type === 'password';
|
|
1884
|
+
input.type = show ? 'text' : 'password';
|
|
1885
|
+
togglePressed(el, show);
|
|
1886
|
+
el.setAttribute('aria-label', show ? o.show : o.hide);
|
|
1887
|
+
return ctx.animate(el, [{ transform: 'scaleY(1)' }, { transform: 'scaleY(.1)', offset: 0.5 }, { transform: 'scaleY(1)' }], { duration: 240, easing: 'ease-in-out' });
|
|
1888
|
+
}),
|
|
1889
|
+
click('favorite-star', 'Toggles a favourite: the star pops and throws little stars (`color`).', { color: '#facc15' }, (el, o, ctx) => {
|
|
1890
|
+
const on = togglePressed(el);
|
|
1891
|
+
return Promise.all([pop(el, ctx, on ? 1.35 : 0.85)?.finished, on ? burst(el, ctx, '★', o.color) : null]);
|
|
1892
|
+
}),
|
|
1893
|
+
click('like-heart', 'Toggles a like: the heart beats and hearts float up (`color`).', { color: '#ff5c8a' }, (el, o, ctx) => {
|
|
1894
|
+
const on = togglePressed(el);
|
|
1895
|
+
return Promise.all([pop(el, ctx, on ? 1.3 : 0.9)?.finished, on ? burst(el, ctx, '♥', o.color, 5, true) : null]);
|
|
1896
|
+
}),
|
|
1897
|
+
click('bookmark-flip', 'Toggles a bookmark with a 3D flip.', {}, (el, _o, ctx) => {
|
|
1898
|
+
togglePressed(el);
|
|
1899
|
+
return ctx.animate(el, [{ transform: 'perspective(400px) rotateY(0)' }, { transform: 'perspective(400px) rotateY(180deg)' }, { transform: 'perspective(400px) rotateY(360deg)' }], { duration: 520, easing: 'ease-in-out' });
|
|
1900
|
+
}),
|
|
1901
|
+
click('download-progress', 'Fills a progress bar over `duration` ms, then shows "Done ✓" (`aria-busy` while running; fires `usa-done`).', { duration: 1600, label: 'Done ✓', color: '#34d399' }, (el, o, ctx) => {
|
|
1902
|
+
if (el.getAttribute('aria-busy') === 'true')
|
|
1903
|
+
return;
|
|
1904
|
+
el.setAttribute('aria-busy', 'true');
|
|
1905
|
+
const [bar, remove] = overlay(el, `background:${o.color}55;transform-origin:left;transform:scaleX(0)`);
|
|
1906
|
+
const a = ctx.animate(bar, [{ transform: 'scaleX(0)' }, { transform: 'scaleX(1)' }], { duration: o.duration, easing: 'linear', fill: 'forwards' });
|
|
1907
|
+
const done = a ? a.finished.catch(() => undefined) : Promise.resolve();
|
|
1908
|
+
return done.then(() => {
|
|
1909
|
+
remove();
|
|
1910
|
+
el.removeAttribute('aria-busy');
|
|
1911
|
+
el.dispatchEvent(new CustomEvent('usa-done', { bubbles: true }));
|
|
1912
|
+
return swapLabel(el, o.label, 1400);
|
|
1913
|
+
});
|
|
1914
|
+
}),
|
|
1915
|
+
click('submit-loading', 'Shows "Sending…" with pulsing dots for `duration` ms, then "Sent ✓" (`aria-busy`, `usa-done`).', { duration: 1200, loading: 'Sending…', label: 'Sent ✓' }, (el, o, ctx) => {
|
|
1916
|
+
if (el.getAttribute('aria-busy') === 'true')
|
|
1917
|
+
return;
|
|
1918
|
+
el.setAttribute('aria-busy', 'true');
|
|
1919
|
+
const t = swapLabel(el, o.loading, o.duration);
|
|
1920
|
+
const a = ctx.animate(el, [{ opacity: 1 }, { opacity: 0.55 }, { opacity: 1 }], { duration: 600, iterations: Math.max(1, Math.round(o.duration / 600)) });
|
|
1921
|
+
return t.then(() => {
|
|
1922
|
+
a?.cancel();
|
|
1923
|
+
el.removeAttribute('aria-busy');
|
|
1924
|
+
el.dispatchEvent(new CustomEvent('usa-done', { bubbles: true }));
|
|
1925
|
+
return swapLabel(el, o.label, 1400);
|
|
1926
|
+
});
|
|
1927
|
+
}),
|
|
1928
|
+
click('send-plane', 'A paper plane ✈ takes off from the button.', { color: '#22d3ee' }, (el, o, ctx) => {
|
|
1929
|
+
const r = el.getBoundingClientRect();
|
|
1930
|
+
pop(el, ctx, 0.92, 200);
|
|
1931
|
+
return spawn(r.left + r.width / 2 - 8, r.top + r.height / 2 - 8, `font-size:16px;color:${o.color}`, ctx, [{ transform: 'translate(0,0) rotate(0)', opacity: 1 }, { transform: 'translate(40px,-10px) rotate(-10deg)', opacity: 1, offset: 0.4 }, { transform: 'translate(160px,-90px) rotate(-25deg)', opacity: 0 }], { duration: 800, easing: 'ease-in' }, '✈')?.finished;
|
|
1932
|
+
}),
|
|
1933
|
+
click('add-to-cart', 'Bumps `[data-count]` by one and floats a "+1" (`text`).', { text: '+1', color: '#34d399' }, (el, o, ctx) => {
|
|
1934
|
+
bumpCount(el, 1, ctx);
|
|
1935
|
+
const { x, y } = origin(el, ctx);
|
|
1936
|
+
return spawn(x - 10, y - 10, `font:700 14px system-ui;color:${o.color}`, ctx, [{ transform: 'translateY(0)', opacity: 1 }, { transform: 'translateY(-40px)', opacity: 0 }], { duration: 700, easing: 'ease-out' }, o.text)?.finished;
|
|
1937
|
+
}),
|
|
1938
|
+
click('counter-bump', 'Adds `step` to `[data-count]` with a rolling number.', { step: 1 }, (el, o, ctx) => void bumpCount(el, Number(o.step) || 1, ctx)),
|
|
1939
|
+
click('upvote', 'Toggles an upvote: the arrow nudges up and `[data-count]` ±1.', {}, (el, _o, ctx) => {
|
|
1940
|
+
const on = togglePressed(el);
|
|
1941
|
+
bumpCount(el, on ? 1 : -1, ctx);
|
|
1942
|
+
return ctx.animate(el, [{ transform: 'translateY(0)' }, { transform: `translateY(${on ? -6 : 4}px)`, offset: 0.4 }, { transform: 'translateY(0)' }], { duration: 320, easing: 'cubic-bezier(.2,1.4,.4,1)' });
|
|
1943
|
+
}),
|
|
1944
|
+
click('clap', 'Counts claps in `[data-count]` with a 👏 burst on every press.', {}, (el, _o, ctx) => {
|
|
1945
|
+
bumpCount(el, 1, ctx);
|
|
1946
|
+
return Promise.all([pop(el, ctx, 1.15, 240)?.finished, burst(el, ctx, '👏', 'inherit', 3, true)]);
|
|
1947
|
+
}),
|
|
1948
|
+
click('emoji-react', 'Pops one `emoji` up from the pointer (reaction button).', { emoji: '🎉' }, (el, o, ctx) => burst(el, ctx, o.emoji, 'inherit', 1, true)),
|
|
1949
|
+
click('refresh-spin', 'Spins the icon one turn (`turns`).', { turns: 1 }, (el, o, ctx) => ctx.animate(el, [{ transform: 'rotate(0)' }, { transform: `rotate(${360 * o.turns}deg)` }], { duration: 600 * o.turns, easing: 'cubic-bezier(.4,0,.2,1)' })),
|
|
1950
|
+
click('trash-shake', 'Shakes, then drops and fades (`remove: true` removes the element afterwards).', { remove: false }, (el, o, ctx) => {
|
|
1951
|
+
const a = ctx.animate(el, [{ transform: 'none', opacity: 1 }, { transform: 'rotate(-6deg)', offset: 0.15 }, { transform: 'rotate(6deg)', offset: 0.3 }, { transform: 'rotate(0)', offset: 0.45, opacity: 1 }, { transform: 'translateY(30px) scale(.8)', opacity: 0 }], { duration: 650, easing: 'ease-in', fill: 'forwards' });
|
|
1952
|
+
const end = () => (o.remove ? el.remove() : a?.cancel());
|
|
1953
|
+
return a ? a.finished.then(end, end) : void end();
|
|
1954
|
+
}),
|
|
1955
|
+
click('check-toggle', 'Toggles a check mark (`aria-checked` for role=checkbox, else `aria-pressed`) that scales in.', {}, (el, _o, ctx) => {
|
|
1956
|
+
const attr = el.getAttribute('role') === 'checkbox' ? 'aria-checked' : 'aria-pressed';
|
|
1957
|
+
const on = el.getAttribute(attr) !== 'true';
|
|
1958
|
+
el.setAttribute(attr, String(on));
|
|
1959
|
+
return ctx.animate(el, on ? [{ transform: 'scale(.6)' }, { transform: 'scale(1.15)', offset: 0.6 }, { transform: 'scale(1)' }] : [{ transform: 'scale(1)' }, { transform: 'scale(.85)' }, { transform: 'scale(1)' }], { duration: 280, easing: 'ease-out' });
|
|
1960
|
+
}),
|
|
1961
|
+
attn('input-shake', 'Shakes an invalid field side to side and sets `aria-invalid` (`color` outline flash).', { color: '#ff5c8a' }, (el, o, ctx) => {
|
|
1962
|
+
el.setAttribute('aria-invalid', 'true');
|
|
1963
|
+
const prev = el.style.outline;
|
|
1964
|
+
el.style.outline = `2px solid ${o.color}`;
|
|
1965
|
+
setTimeout(() => (el.style.outline = prev), 900);
|
|
1966
|
+
return ctx.animate(el, [0, -8, 8, -6, 6, -3, 0].map((x) => ({ transform: `translateX(${x}px)` })), { duration: 420, easing: 'ease-out' });
|
|
1967
|
+
}),
|
|
1968
|
+
attn('error-flash', 'Flashes the element red once (no more than one flash per call).', { color: '#ff5c8a' }, (el, o, ctx) => ctx.animate(el, [{ boxShadow: `0 0 0 0 ${o.color}00` }, { boxShadow: `0 0 0 4px ${o.color}`, offset: 0.3 }, { boxShadow: `0 0 0 0 ${o.color}00` }], { duration: 700 })),
|
|
1969
|
+
attn('success-check', 'A green ✓ badge pops over the element and fades.', { color: '#34d399' }, (el, o, ctx) => {
|
|
1970
|
+
const [b, remove] = overlay(el, `display:grid;place-items:center;font:700 22px system-ui;color:#fff;background:${o.color}d0`);
|
|
1971
|
+
b.textContent = '✓';
|
|
1972
|
+
const a = ctx.animate(b, [{ opacity: 0, transform: 'scale(.6)' }, { opacity: 1, transform: 'scale(1)', offset: 0.3 }, { opacity: 1, offset: 0.75 }, { opacity: 0 }], { duration: 1100, easing: 'ease-out' });
|
|
1973
|
+
return a ? a.finished.then(remove, remove) : void setTimeout(remove, 900);
|
|
1974
|
+
}),
|
|
1975
|
+
attn('nudge-hint', 'A small sideways nudge that says "try me" (`distance`).', { distance: 6 }, (el, o, ctx) => ctx.animate(el, [0, o.distance, 0, o.distance / 2, 0].map((x) => ({ transform: `translateX(${x}px)` })), { duration: 700, easing: 'ease-in-out' })),
|
|
1976
|
+
attn('focus-pulse', 'A ring pulses around the element to draw focus to it (`color`).', { color: '#7c5cff' }, (el, o, ctx) => ctx.animate(el, [{ boxShadow: `0 0 0 0 ${o.color}aa` }, { boxShadow: `0 0 0 12px ${o.color}00` }], { duration: 900, iterations: 2, easing: 'ease-out' })),
|
|
1977
|
+
attn('notify-badge', 'Bumps the badge count (`[data-count]`, `step`) with a pop — for new notifications.', { step: 1 }, (el, o, ctx) => {
|
|
1978
|
+
bumpCount(el, Number(o.step) || 1, ctx);
|
|
1979
|
+
return pop(el, ctx, 1.3, 320);
|
|
1980
|
+
}),
|
|
1981
|
+
];
|
|
1982
|
+
|
|
1983
|
+
const THEME_ROLES = ['enter', 'hover', 'click', 'attention', 'background'];
|
|
1984
|
+
const P = (enter, hover, click, attention, background) => ({
|
|
1985
|
+
enter: { effect: enter },
|
|
1986
|
+
hover: { effect: hover },
|
|
1987
|
+
click: { effect: click },
|
|
1988
|
+
attention: { effect: attention },
|
|
1989
|
+
background: { effect: background },
|
|
1990
|
+
});
|
|
1991
|
+
const THEMES = {
|
|
1992
|
+
neon: {
|
|
1993
|
+
name: 'neon',
|
|
1994
|
+
vars: { bg: '#07070c', fg: '#e8e8ff', accent: '#22d3ee', 'accent-2': '#ff2bd6', surface: '#11111c', border: '1px solid #22d3ee', radius: '10px', shadow: '0 0 18px #22d3ee66, inset 0 0 12px #ff2bd622', font: 'ui-monospace, SFMono-Regular, Menlo, monospace' },
|
|
1995
|
+
motion: { duration: { fast: 120, normal: 220 }, easing: { standard: 'cubic-bezier(0.2, 0, 0, 1)' } },
|
|
1996
|
+
presets: P('fade-up', 'neon-flicker', 'shockwave', 'neon-flicker', 'starfield'),
|
|
1997
|
+
},
|
|
1998
|
+
paper: {
|
|
1999
|
+
name: 'paper',
|
|
2000
|
+
vars: { bg: '#f6f1e7', fg: '#2b2721', accent: '#c2410c', 'accent-2': '#0f766e', surface: '#fffdf8', border: '1px solid #e3dccd', radius: '4px', shadow: '0 1px 0 #0000000d, 0 10px 24px -14px #00000040', font: 'Georgia, "Times New Roman", serif' },
|
|
2001
|
+
motion: { duration: { fast: 200, normal: 380, slow: 700 }, easing: { standard: 'cubic-bezier(0, 0, 0, 1)' } },
|
|
2002
|
+
presets: P('paper-fold', 'wiggle', 'ink-splash', 'nudge-hint', 'contours'),
|
|
2003
|
+
},
|
|
2004
|
+
glass: {
|
|
2005
|
+
name: 'glass',
|
|
2006
|
+
vars: { bg: 'linear-gradient(135deg, #1e1b4b, #0e7490)', fg: '#f8fafc', accent: '#a5f3fc', 'accent-2': '#c4b5fd', surface: '#ffffff1f', border: '1px solid #ffffff40', radius: '18px', shadow: '0 8px 32px #0000003d', font: 'system-ui, -apple-system, "Segoe UI", sans-serif' },
|
|
2007
|
+
motion: { duration: { normal: 320 }, easing: { standard: 'cubic-bezier(0.22, 1, 0.36, 1)' } },
|
|
2008
|
+
presets: P('blur', 'glass-shine', 'ripple', 'focus-pulse', 'mesh-gradient'),
|
|
2009
|
+
},
|
|
2010
|
+
retro: {
|
|
2011
|
+
name: 'retro',
|
|
2012
|
+
vars: { bg: '#1d1135', fg: '#ffe9a8', accent: '#ff6b35', 'accent-2': '#2ec4b6', surface: '#2a1b4d', border: '3px solid #ffe9a8', radius: '0', shadow: '4px 4px 0 #ff6b35', font: '"Courier New", ui-monospace, monospace' },
|
|
2013
|
+
motion: { duration: { normal: 300 }, easing: { standard: 'steps(6, end)' } },
|
|
2014
|
+
presets: P('clip-up', 'rubber-band', 'star-burst', 'tada', 'retro-scanlines'),
|
|
2015
|
+
},
|
|
2016
|
+
brutalist: {
|
|
2017
|
+
name: 'brutalist',
|
|
2018
|
+
vars: { bg: '#ffffff', fg: '#000000', accent: '#ff3b00', 'accent-2': '#0047ff', surface: '#fff200', border: '3px solid #000000', radius: '0', shadow: '6px 6px 0 #000000', font: '"Arial Black", Arial, sans-serif' },
|
|
2019
|
+
motion: { duration: { fast: 80, normal: 160 }, easing: { standard: 'linear' } },
|
|
2020
|
+
presets: P('drop-bounce', 'jelly', 'brutal-shift', 'shake', 'voronoi'),
|
|
2021
|
+
},
|
|
2022
|
+
};
|
|
2023
|
+
const THEME_NAMES = Object.keys(THEMES);
|
|
2024
|
+
const BASE_CSS = `[data-usa-theme]{background:var(--usa-theme-bg);color:var(--usa-theme-fg);font-family:var(--usa-theme-font)}
|
|
2025
|
+
[data-usa-theme] .usa-surface{background:var(--usa-theme-surface);border:var(--usa-theme-border);border-radius:var(--usa-theme-radius);box-shadow:var(--usa-theme-shadow)}
|
|
2026
|
+
[data-usa-theme] .usa-accent{color:var(--usa-theme-accent)}
|
|
2027
|
+
[data-usa-theme=glass] .usa-surface{-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px)}`;
|
|
2028
|
+
const pack = (t) => {
|
|
2029
|
+
const p = typeof t === 'string' ? THEMES[t] : t;
|
|
2030
|
+
if (!p)
|
|
2031
|
+
throw new Error(`[use-scroll-animate] unknown theme "${String(t)}" — ${THEME_NAMES.join(', ')}`);
|
|
2032
|
+
return p;
|
|
2033
|
+
};
|
|
2034
|
+
/** The CSS custom properties of a theme (design + motion tokens). */
|
|
2035
|
+
function themeVars(t) {
|
|
2036
|
+
const p = pack(t);
|
|
2037
|
+
const out = {};
|
|
2038
|
+
for (const [k, v] of Object.entries(p.vars))
|
|
2039
|
+
out[`--usa-theme-${k}`] = v;
|
|
2040
|
+
return { ...out, ...components_tokens.motionTokensToVars(components_tokens.mergeMotionTokens(p.motion)) };
|
|
2041
|
+
}
|
|
2042
|
+
/** A theme as a CSS rule (`selector` default `[data-usa-theme=<name>]`) — for SSR / static CSS. */
|
|
2043
|
+
function themeCss(t, selector) {
|
|
2044
|
+
const p = pack(t);
|
|
2045
|
+
return `${selector || `[data-usa-theme=${p.name}]`}{${Object.entries(themeVars(p)).map(([k, v]) => `${k}:${v}`).join(';')}}`;
|
|
2046
|
+
}
|
|
2047
|
+
/** Apply a theme to `root` (default `<html>`). Returns an undo. */
|
|
2048
|
+
function applyTheme(t, root) {
|
|
2049
|
+
const p = pack(t);
|
|
2050
|
+
const el = root || document.documentElement;
|
|
2051
|
+
base.adoptStyles('usa-theme', BASE_CSS);
|
|
2052
|
+
const prevAttr = el.getAttribute('data-usa-theme');
|
|
2053
|
+
el.setAttribute('data-usa-theme', p.name);
|
|
2054
|
+
const vars = Object.entries(p.vars).map(([k, v]) => [`--usa-theme-${k}`, v]);
|
|
2055
|
+
const old = vars.map(([k]) => [k, el.style.getPropertyValue(k)]);
|
|
2056
|
+
for (const [k, v] of vars)
|
|
2057
|
+
el.style.setProperty(k, v);
|
|
2058
|
+
let undoMotion;
|
|
2059
|
+
if (el === document.documentElement)
|
|
2060
|
+
undoMotion = components_tokens.applyMotionTokens(p.motion, el);
|
|
2061
|
+
else {
|
|
2062
|
+
const mv = Object.entries(components_tokens.motionTokensToVars(components_tokens.mergeMotionTokens(p.motion)));
|
|
2063
|
+
const mold = mv.map(([k]) => [k, el.style.getPropertyValue(k)]);
|
|
2064
|
+
for (const [k, v] of mv)
|
|
2065
|
+
el.style.setProperty(k, v);
|
|
2066
|
+
undoMotion = () => mold.forEach(([k, v]) => (v ? el.style.setProperty(k, v) : el.style.removeProperty(k)));
|
|
2067
|
+
}
|
|
2068
|
+
return () => {
|
|
2069
|
+
undoMotion();
|
|
2070
|
+
old.forEach(([k, v]) => (v ? el.style.setProperty(k, v) : el.style.removeProperty(k)));
|
|
2071
|
+
if (prevAttr === null)
|
|
2072
|
+
el.removeAttribute('data-usa-theme');
|
|
2073
|
+
else
|
|
2074
|
+
el.setAttribute('data-usa-theme', prevAttr);
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
/** The effect preset of a theme for a role. */
|
|
2078
|
+
function themePreset(t, role) {
|
|
2079
|
+
return pack(t).presets[role];
|
|
2080
|
+
}
|
|
2081
|
+
/** Play the preset for `role` of the theme on the closest `[data-usa-theme]` (or `theme`). */
|
|
2082
|
+
function playThemeEffect(el, role, theme) {
|
|
2083
|
+
const name = theme || el.closest('[data-usa-theme]')?.getAttribute('data-usa-theme') || 'neon';
|
|
2084
|
+
const pr = themePreset(name, role);
|
|
2085
|
+
return components_fx.playEffect(el, pr.effect, pr.options);
|
|
2086
|
+
}
|
|
2087
|
+
const THEME_FX = [
|
|
2088
|
+
{
|
|
2089
|
+
name: 'neon-flicker',
|
|
2090
|
+
kind: 'attention',
|
|
2091
|
+
description: 'A neon tube flickering on — dims (never blacks out) twice, well under 3 flashes per second.',
|
|
2092
|
+
defaults: { color: '#22d3ee' },
|
|
2093
|
+
run: (el, o, ctx) => ctx.animate(el, [{ opacity: 1, filter: 'none' }, { opacity: 0.55, offset: 0.2 }, { opacity: 1, filter: `drop-shadow(0 0 6px ${o.color})`, offset: 0.35 }, { opacity: 0.7, offset: 0.6 }, { opacity: 1, filter: `drop-shadow(0 0 10px ${o.color})` }], { duration: 900, easing: 'linear' }),
|
|
2094
|
+
},
|
|
2095
|
+
{
|
|
2096
|
+
name: 'paper-fold',
|
|
2097
|
+
kind: 'enter',
|
|
2098
|
+
description: 'Unfolds like a sheet of paper hinged at the top.',
|
|
2099
|
+
defaults: { duration: 600 },
|
|
2100
|
+
run: (el, o, ctx) => ctx.animate(el, [{ transform: 'perspective(800px) rotateX(-85deg)', transformOrigin: 'top', opacity: 0 }, { transform: 'perspective(800px) rotateX(12deg)', transformOrigin: 'top', opacity: 1, offset: 0.7 }, { transform: 'perspective(800px) rotateX(0)', transformOrigin: 'top', opacity: 1 }], { duration: o.duration, easing: 'ease-out', fill: 'backwards' }),
|
|
2101
|
+
},
|
|
2102
|
+
{
|
|
2103
|
+
name: 'glass-shine',
|
|
2104
|
+
kind: 'hover',
|
|
2105
|
+
description: 'A bright diagonal shine sweeps across frosted glass.',
|
|
2106
|
+
defaults: { duration: 700 },
|
|
2107
|
+
run: (el, o, ctx) => {
|
|
2108
|
+
const [s, remove] = overlay(el, 'overflow:hidden;background:linear-gradient(105deg,transparent 35%,#ffffff8c 50%,transparent 65%);background-size:250% 100%;background-position:120% 0');
|
|
2109
|
+
const a = ctx.animate(s, [{ backgroundPosition: '120% 0' }, { backgroundPosition: '-20% 0' }], { duration: o.duration, easing: 'ease-in-out' });
|
|
2110
|
+
return a ? a.finished.then(remove, remove) : void remove();
|
|
2111
|
+
},
|
|
2112
|
+
},
|
|
2113
|
+
{
|
|
2114
|
+
name: 'retro-scanlines',
|
|
2115
|
+
kind: 'background',
|
|
2116
|
+
description: 'CRT scanlines with a slow roll (static lines under reduced motion; persistent).',
|
|
2117
|
+
reduced: 'run',
|
|
2118
|
+
defaults: { opacity: 0.18 },
|
|
2119
|
+
run: (el, o, ctx) => {
|
|
2120
|
+
const [s, remove] = overlay(el, `opacity:${o.opacity};background:repeating-linear-gradient(0deg,#000 0 1px,transparent 1px 3px);mix-blend-mode:multiply`);
|
|
2121
|
+
const a = ctx.reduced ? null : ctx.animate(s, [{ backgroundPosition: '0 0' }, { backgroundPosition: '0 30px' }], { duration: 2400, iterations: Infinity, easing: 'linear' });
|
|
2122
|
+
return () => {
|
|
2123
|
+
a?.cancel();
|
|
2124
|
+
remove();
|
|
2125
|
+
};
|
|
2126
|
+
},
|
|
2127
|
+
},
|
|
2128
|
+
{
|
|
2129
|
+
name: 'brutal-shift',
|
|
2130
|
+
kind: 'click',
|
|
2131
|
+
description: 'Presses into its hard drop shadow and springs back (brutalist buttons).',
|
|
2132
|
+
defaults: { offset: 6 },
|
|
2133
|
+
run: (el, o, ctx) => ctx.animate(el, [{ transform: 'translate(0,0)' }, { transform: `translate(${o.offset}px,${o.offset}px)`, boxShadow: '0 0 0 #000', offset: 0.3 }, { transform: 'translate(0,0)' }], { duration: 260, easing: 'ease-out' }),
|
|
2134
|
+
},
|
|
2135
|
+
];
|
|
2136
|
+
/** `<usa-theme name="neon | paper | glass | retro | brutalist">` — a themed subtree. */
|
|
2137
|
+
function defineTheme(tag = 'usa-theme') {
|
|
2138
|
+
return base.defineElement(tag, (Base) => class UsaTheme extends Base {
|
|
2139
|
+
static get observedAttributes() {
|
|
2140
|
+
return ['name'];
|
|
2141
|
+
}
|
|
2142
|
+
get theme() {
|
|
2143
|
+
const n = this.str('name', 'neon');
|
|
2144
|
+
return THEMES[n] ? n : 'neon';
|
|
2145
|
+
}
|
|
2146
|
+
mount() {
|
|
2147
|
+
this.onCleanup(applyTheme(this.theme, this));
|
|
2148
|
+
this.querySelectorAll('[data-theme-fx]').forEach((el) => {
|
|
2149
|
+
const role = (el.dataset.themeFx || 'click');
|
|
2150
|
+
if (!THEME_ROLES.includes(role))
|
|
2151
|
+
return;
|
|
2152
|
+
const pr = themePreset(this.theme, role);
|
|
2153
|
+
const trigger = role === 'attention' ? 'click' : role === 'background' ? 'load' : role;
|
|
2154
|
+
try {
|
|
2155
|
+
this.onCleanup(components_fx.bindEffect(el, pr.effect, { ...(pr.options || {}), trigger }));
|
|
2156
|
+
}
|
|
2157
|
+
catch {
|
|
2158
|
+
/* effect not registered: call registerAllEffects() */
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
}, { id: 'usa-theme-el', text: 'usa-theme{display:block}' });
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
/**
|
|
2166
|
+
* 5.9 — `<usa-player>`: plays JSON animations.
|
|
2167
|
+
*
|
|
2168
|
+
* Format `use-scroll-animate/animation` v1:
|
|
2169
|
+
*
|
|
2170
|
+
* ```json
|
|
2171
|
+
* { "format": "use-scroll-animate/animation", "version": 1, "name": "Hero",
|
|
2172
|
+
* "loop": false,
|
|
2173
|
+
* "tracks": [
|
|
2174
|
+
* { "target": "h1", "start": 0, "duration": 600, "preset": "fade-up" },
|
|
2175
|
+
* { "target": ".cta", "start": 500, "duration": 500, "keyframes": [{ "opacity": 0 }, { "opacity": 1 }], "easing": "ease-out" },
|
|
2176
|
+
* { "target": ".cta", "start": 1100, "effect": "jelly", "options": {} }
|
|
2177
|
+
* ] }
|
|
2178
|
+
* ```
|
|
2179
|
+
*
|
|
2180
|
+
* A track animates `target` (a selector inside the player; `:scope` for the
|
|
2181
|
+
* player itself) with a timeline preset, its own keyframes, or fires any
|
|
2182
|
+
* registered effect at `start`. Playground presets (format
|
|
2183
|
+
* `use-scroll-animate/playground`) are accepted too — their tracks map to the
|
|
2184
|
+
* player's children in order.
|
|
2185
|
+
*
|
|
2186
|
+
* Keyframe tracks are WAAPI animations driven by one clock, so the player can
|
|
2187
|
+
* play, pause, seek, change rate and be scrubbed by scroll
|
|
2188
|
+
* (`trigger="scroll"`). Reduced motion: jumps to the end state, effects skipped.
|
|
2189
|
+
*/
|
|
2190
|
+
const ANIMATION_FORMAT = 'use-scroll-animate/animation';
|
|
2191
|
+
function decodePlayground(state) {
|
|
2192
|
+
try {
|
|
2193
|
+
const b64 = state.replace(/-/g, '+').replace(/_/g, '/');
|
|
2194
|
+
const json = decodeURIComponent(escape(atob(b64)));
|
|
2195
|
+
const t = (JSON.parse(json).t || []);
|
|
2196
|
+
return t.map(([preset, start, duration, label], i) => ({ target: `:scope > :nth-child(${i + 1})`, preset, start, duration, label }));
|
|
2197
|
+
}
|
|
2198
|
+
catch {
|
|
2199
|
+
return [];
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
/** Validate / normalise an animation (object or JSON text). Throws on anything unusable. */
|
|
2203
|
+
function normalizeAnimation(input) {
|
|
2204
|
+
const d = typeof input === 'string' ? JSON.parse(input) : input;
|
|
2205
|
+
if (!d || typeof d !== 'object')
|
|
2206
|
+
throw new Error('[use-scroll-animate] animation: expected an object');
|
|
2207
|
+
let tracks;
|
|
2208
|
+
if (d.format === 'use-scroll-animate/playground')
|
|
2209
|
+
tracks = decodePlayground(String(d.state || ''));
|
|
2210
|
+
else if (Array.isArray(d.tracks))
|
|
2211
|
+
tracks = d.tracks;
|
|
2212
|
+
else
|
|
2213
|
+
throw new Error('[use-scroll-animate] animation: missing "tracks"');
|
|
2214
|
+
if (d.format && d.format !== ANIMATION_FORMAT && d.format !== 'use-scroll-animate/playground')
|
|
2215
|
+
throw new Error(`[use-scroll-animate] animation: unknown format "${d.format}"`);
|
|
2216
|
+
if (d.version && d.version > 1 && d.format === ANIMATION_FORMAT)
|
|
2217
|
+
throw new Error(`[use-scroll-animate] animation: version ${d.version} needs a newer use-scroll-animate`);
|
|
2218
|
+
const out = tracks
|
|
2219
|
+
.filter((t) => t && (t.effect || t.preset || Array.isArray(t.keyframes)))
|
|
2220
|
+
.map((t) => ({ ...t, target: t.target || ':scope', start: Math.max(0, Number(t.start) || 0), duration: t.effect ? 0 : Math.max(1, Number(t.duration) || 600) }));
|
|
2221
|
+
const end = out.reduce((m, t) => Math.max(m, t.start + t.duration), 0);
|
|
2222
|
+
return { tracks: out, duration: Math.max(end, Number(d.duration) || 0), loop: !!d.loop, name: String(d.name || 'animation') };
|
|
2223
|
+
}
|
|
2224
|
+
const pick = (root, sel) => {
|
|
2225
|
+
if (sel === ':scope')
|
|
2226
|
+
return [root];
|
|
2227
|
+
try {
|
|
2228
|
+
return Array.from(root.querySelectorAll(sel));
|
|
2229
|
+
}
|
|
2230
|
+
catch {
|
|
2231
|
+
return [];
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
/** Bind an animation to `root` and return its controller (paused at 0 unless `autoplay`). */
|
|
2235
|
+
function createPlayer(root, animation, o = {}) {
|
|
2236
|
+
const a = normalizeAnimation(animation);
|
|
2237
|
+
const loop = o.loop ?? a.loop;
|
|
2238
|
+
const anims = [];
|
|
2239
|
+
const effects = [];
|
|
2240
|
+
for (const t of a.tracks) {
|
|
2241
|
+
const els = pick(root, t.target);
|
|
2242
|
+
if (t.effect)
|
|
2243
|
+
effects.push({ els, t, fired: false });
|
|
2244
|
+
else {
|
|
2245
|
+
const kf = t.keyframes || core.TIMELINE_PRESETS[t.preset] || core.TIMELINE_PRESETS.fade;
|
|
2246
|
+
for (const el of els) {
|
|
2247
|
+
if (typeof el.animate !== 'function')
|
|
2248
|
+
continue;
|
|
2249
|
+
const an = el.animate(kf, { duration: t.duration, delay: t.start, easing: t.easing || 'cubic-bezier(0.22, 1, 0.36, 1)', fill: 'both' });
|
|
2250
|
+
an.pause();
|
|
2251
|
+
an.currentTime = 0;
|
|
2252
|
+
anims.push(an);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
let time = 0;
|
|
2257
|
+
let playing = false;
|
|
2258
|
+
let raf = 0;
|
|
2259
|
+
let last = 0;
|
|
2260
|
+
let resolve;
|
|
2261
|
+
let finished = new Promise((r) => (resolve = r));
|
|
2262
|
+
const set = (ms) => {
|
|
2263
|
+
time = Math.min(a.duration, Math.max(0, ms));
|
|
2264
|
+
for (const an of anims)
|
|
2265
|
+
an.currentTime = time;
|
|
2266
|
+
};
|
|
2267
|
+
const fireDue = () => {
|
|
2268
|
+
if (base.prefersReducedMotion())
|
|
2269
|
+
return;
|
|
2270
|
+
for (const e of effects)
|
|
2271
|
+
if (!e.fired && time >= e.t.start) {
|
|
2272
|
+
e.fired = true;
|
|
2273
|
+
for (const el of e.els)
|
|
2274
|
+
components_fx.playEffect(el, e.t.effect, e.t.options || {}).catch(() => undefined);
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
const done = () => {
|
|
2278
|
+
resolve();
|
|
2279
|
+
o.onFinish?.();
|
|
2280
|
+
finished = new Promise((r) => (resolve = r));
|
|
2281
|
+
};
|
|
2282
|
+
const frame = (now) => {
|
|
2283
|
+
raf = 0;
|
|
2284
|
+
if (!playing)
|
|
2285
|
+
return;
|
|
2286
|
+
set(time + (now - last) * player.rate);
|
|
2287
|
+
last = now;
|
|
2288
|
+
fireDue();
|
|
2289
|
+
if (time >= a.duration) {
|
|
2290
|
+
if (loop) {
|
|
2291
|
+
effects.forEach((e) => (e.fired = false));
|
|
2292
|
+
set(0);
|
|
2293
|
+
}
|
|
2294
|
+
else {
|
|
2295
|
+
playing = false;
|
|
2296
|
+
done();
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
raf = requestAnimationFrame(frame);
|
|
2301
|
+
};
|
|
2302
|
+
const player = {
|
|
2303
|
+
get duration() {
|
|
2304
|
+
return a.duration;
|
|
2305
|
+
},
|
|
2306
|
+
get currentTime() {
|
|
2307
|
+
return time;
|
|
2308
|
+
},
|
|
2309
|
+
get playing() {
|
|
2310
|
+
return playing;
|
|
2311
|
+
},
|
|
2312
|
+
rate: o.rate ?? 1,
|
|
2313
|
+
get finished() {
|
|
2314
|
+
return finished;
|
|
2315
|
+
},
|
|
2316
|
+
play() {
|
|
2317
|
+
if (base.prefersReducedMotion()) {
|
|
2318
|
+
set(a.duration);
|
|
2319
|
+
effects.forEach((e) => (e.fired = true));
|
|
2320
|
+
done();
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
if (playing)
|
|
2324
|
+
return;
|
|
2325
|
+
if (time >= a.duration) {
|
|
2326
|
+
effects.forEach((e) => (e.fired = false));
|
|
2327
|
+
set(0);
|
|
2328
|
+
}
|
|
2329
|
+
playing = true;
|
|
2330
|
+
last = performance.now();
|
|
2331
|
+
fireDue();
|
|
2332
|
+
raf = requestAnimationFrame(frame);
|
|
2333
|
+
},
|
|
2334
|
+
pause() {
|
|
2335
|
+
playing = false;
|
|
2336
|
+
cancelAnimationFrame(raf);
|
|
2337
|
+
raf = 0;
|
|
2338
|
+
},
|
|
2339
|
+
seek(ms) {
|
|
2340
|
+
set(ms);
|
|
2341
|
+
effects.forEach((e) => (e.fired = e.t.start < time));
|
|
2342
|
+
},
|
|
2343
|
+
destroy() {
|
|
2344
|
+
player.pause();
|
|
2345
|
+
anims.forEach((an) => an.cancel());
|
|
2346
|
+
},
|
|
2347
|
+
};
|
|
2348
|
+
if (o.autoplay)
|
|
2349
|
+
player.play();
|
|
2350
|
+
return player;
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
* `<usa-player src="hero.json" | <script type="application/json"> child
|
|
2354
|
+
* trigger="load | view | scroll | click | manual" loop rate controls>`.
|
|
2355
|
+
* Emits `usa-player-ready` and `usa-player-finish`; sets `data-error` when the
|
|
2356
|
+
* animation cannot be loaded.
|
|
2357
|
+
*/
|
|
2358
|
+
function definePlayer(tag = 'usa-player') {
|
|
2359
|
+
return base.defineElement(tag, (Base) => class UsaPlayer extends Base {
|
|
2360
|
+
constructor() {
|
|
2361
|
+
super(...arguments);
|
|
2362
|
+
this.player = null;
|
|
2363
|
+
this.json = null;
|
|
2364
|
+
}
|
|
2365
|
+
static get observedAttributes() {
|
|
2366
|
+
return ['src', 'trigger', 'loop', 'rate'];
|
|
2367
|
+
}
|
|
2368
|
+
load(animation) {
|
|
2369
|
+
this.json = animation;
|
|
2370
|
+
this.start();
|
|
2371
|
+
}
|
|
2372
|
+
play() {
|
|
2373
|
+
this.player?.play();
|
|
2374
|
+
}
|
|
2375
|
+
pause() {
|
|
2376
|
+
this.player?.pause();
|
|
2377
|
+
}
|
|
2378
|
+
seek(ms) {
|
|
2379
|
+
this.player?.seek(ms);
|
|
2380
|
+
}
|
|
2381
|
+
start() {
|
|
2382
|
+
this.player?.destroy();
|
|
2383
|
+
this.player = null;
|
|
2384
|
+
if (!this.json)
|
|
2385
|
+
return;
|
|
2386
|
+
try {
|
|
2387
|
+
this.player = createPlayer(this, this.json, {
|
|
2388
|
+
loop: this.hasAttribute('loop') ? true : undefined,
|
|
2389
|
+
rate: this.num('rate', 1),
|
|
2390
|
+
onFinish: () => this.dispatchEvent(new CustomEvent('usa-player-finish', { bubbles: true })),
|
|
2391
|
+
});
|
|
2392
|
+
this.removeAttribute('data-error');
|
|
2393
|
+
}
|
|
2394
|
+
catch (err) {
|
|
2395
|
+
this.setAttribute('data-error', String(err.message || err));
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
const p = this.player;
|
|
2399
|
+
this.dispatchEvent(new CustomEvent('usa-player-ready', { detail: { duration: p.duration }, bubbles: true }));
|
|
2400
|
+
const trig = this.str('trigger', 'view');
|
|
2401
|
+
if (trig === 'load')
|
|
2402
|
+
p.play();
|
|
2403
|
+
else if (trig === 'view')
|
|
2404
|
+
this.inView((v) => v && p.play(), { threshold: 0.25 });
|
|
2405
|
+
else if (trig === 'click')
|
|
2406
|
+
this.listen(this, 'click', () => (p.playing ? p.pause() : p.play()));
|
|
2407
|
+
else if (trig === 'scroll') {
|
|
2408
|
+
let f = 0;
|
|
2409
|
+
const upd = () => {
|
|
2410
|
+
f = 0;
|
|
2411
|
+
p.seek(storyProgress(this) * p.duration);
|
|
2412
|
+
};
|
|
2413
|
+
const kick = () => void (f || (f = requestAnimationFrame(upd)));
|
|
2414
|
+
this.listen(window, 'scroll', kick, { passive: true });
|
|
2415
|
+
this.listen(window, 'resize', kick);
|
|
2416
|
+
this.onCleanup(() => cancelAnimationFrame(f));
|
|
2417
|
+
upd();
|
|
2418
|
+
}
|
|
2419
|
+
if (this.flag('controls'))
|
|
2420
|
+
this.mountControls(p);
|
|
2421
|
+
}
|
|
2422
|
+
mountControls(p) {
|
|
2423
|
+
const b = document.createElement('button');
|
|
2424
|
+
b.type = 'button';
|
|
2425
|
+
b.setAttribute('data-player-toggle', '');
|
|
2426
|
+
b.textContent = '▶︎ / ❚❚';
|
|
2427
|
+
b.setAttribute('aria-label', 'Play / pause animation');
|
|
2428
|
+
this.listen(b, 'click', (e) => {
|
|
2429
|
+
e.stopPropagation();
|
|
2430
|
+
if (p.playing)
|
|
2431
|
+
p.pause();
|
|
2432
|
+
else
|
|
2433
|
+
p.play();
|
|
2434
|
+
b.setAttribute('aria-pressed', String(p.playing));
|
|
2435
|
+
});
|
|
2436
|
+
this.append(b);
|
|
2437
|
+
this.onCleanup(() => b.remove());
|
|
2438
|
+
}
|
|
2439
|
+
changed(name) {
|
|
2440
|
+
if (name === 'src')
|
|
2441
|
+
this.json = null;
|
|
2442
|
+
super.changed(name);
|
|
2443
|
+
}
|
|
2444
|
+
mount() {
|
|
2445
|
+
this.onCleanup(() => {
|
|
2446
|
+
this.player?.destroy();
|
|
2447
|
+
this.player = null;
|
|
2448
|
+
});
|
|
2449
|
+
const inline = this.querySelector('script[type="application/json"]');
|
|
2450
|
+
const src = this.str('src');
|
|
2451
|
+
if (inline && !this.json)
|
|
2452
|
+
this.json = inline.textContent || '';
|
|
2453
|
+
if (src && !this.json) {
|
|
2454
|
+
fetch(src)
|
|
2455
|
+
.then((r) => (r.ok ? r.text() : Promise.reject(new Error(`HTTP ${r.status}`))))
|
|
2456
|
+
.then((t) => this.isConnected && this.load(t))
|
|
2457
|
+
.catch((err) => this.setAttribute('data-error', String(err.message || err)));
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
this.start();
|
|
2461
|
+
}
|
|
2462
|
+
}, { id: 'usa-player', text: 'usa-player{display:block;position:relative}usa-player>script{display:none}usa-player [data-player-toggle]{position:absolute;right:8px;bottom:8px}' });
|
|
2463
|
+
}
|
|
2464
|
+
|
|
1826
2465
|
/**
|
|
1827
2466
|
* use-scroll-animate/components/effects — the 5.x effect packs, all
|
|
1828
2467
|
* registered through `registerEffect()` (5.0) and playable with
|
|
@@ -1843,6 +2482,8 @@ const EFFECT_PACKS = {
|
|
|
1843
2482
|
generative: GENERATIVE_FX,
|
|
1844
2483
|
audio: AUDIO_FX,
|
|
1845
2484
|
cursor: CURSOR_FX,
|
|
2485
|
+
micro: MICRO_FX,
|
|
2486
|
+
themes: THEME_FX,
|
|
1846
2487
|
};
|
|
1847
2488
|
/** 5.1: card & click effects 2.0. */
|
|
1848
2489
|
function registerCardClickEffects() {
|
|
@@ -1868,11 +2509,18 @@ function registerAudioEffects() {
|
|
|
1868
2509
|
function registerCursorEffects() {
|
|
1869
2510
|
components_fx.registerEffects(EFFECT_PACKS.cursor);
|
|
1870
2511
|
}
|
|
2512
|
+
/** 5.8: micro-interactions + theme-pack effects. */
|
|
2513
|
+
function registerMicroEffects() {
|
|
2514
|
+
components_fx.registerEffects(EFFECT_PACKS.micro);
|
|
2515
|
+
components_fx.registerEffects(EFFECT_PACKS.themes);
|
|
2516
|
+
}
|
|
1871
2517
|
/** Define the 5.x elements of this entry (`<usa-story>`, …) under their default tags. */
|
|
1872
2518
|
function defineEffectElements() {
|
|
1873
2519
|
defineStory();
|
|
1874
2520
|
defineAudio();
|
|
1875
2521
|
defineGestureFx();
|
|
2522
|
+
defineTheme();
|
|
2523
|
+
definePlayer();
|
|
1876
2524
|
}
|
|
1877
2525
|
/** Register the built-ins and every pack (idempotent). */
|
|
1878
2526
|
function registerAllEffects() {
|
|
@@ -1881,6 +2529,7 @@ function registerAllEffects() {
|
|
|
1881
2529
|
components_fx.registerEffects(defs);
|
|
1882
2530
|
}
|
|
1883
2531
|
|
|
2532
|
+
exports.ANIMATION_FORMAT = ANIMATION_FORMAT;
|
|
1884
2533
|
exports.AUDIO_FX = AUDIO_FX;
|
|
1885
2534
|
exports.CARD_FX = CARD_FX;
|
|
1886
2535
|
exports.CLICK_FX = CLICK_FX;
|
|
@@ -1888,19 +2537,29 @@ exports.CURSOR_FX = CURSOR_FX;
|
|
|
1888
2537
|
exports.EFFECT_PACKS = EFFECT_PACKS;
|
|
1889
2538
|
exports.GENERATIVE_FX = GENERATIVE_FX;
|
|
1890
2539
|
exports.GESTURES = GESTURES;
|
|
2540
|
+
exports.MICRO_FX = MICRO_FX;
|
|
1891
2541
|
exports.PAGE_FX = PAGE_FX;
|
|
1892
2542
|
exports.PHYSICS_FX = PHYSICS_FX;
|
|
1893
2543
|
exports.STORY_TEMPLATES = STORY_TEMPLATES;
|
|
2544
|
+
exports.THEMES = THEMES;
|
|
2545
|
+
exports.THEME_FX = THEME_FX;
|
|
2546
|
+
exports.THEME_NAMES = THEME_NAMES;
|
|
2547
|
+
exports.THEME_ROLES = THEME_ROLES;
|
|
1894
2548
|
exports.angleDelta = angleDelta;
|
|
2549
|
+
exports.applyTheme = applyTheme;
|
|
1895
2550
|
exports.bindBeat = bindBeat;
|
|
1896
2551
|
exports.bindGesture = bindGesture;
|
|
1897
2552
|
exports.bounceKeyframes = bounceKeyframes;
|
|
2553
|
+
exports.bumpCount = bumpCount;
|
|
1898
2554
|
exports.canvasBackground = canvasBackground;
|
|
1899
2555
|
exports.createBeatDetector = createBeatDetector;
|
|
2556
|
+
exports.createPlayer = createPlayer;
|
|
1900
2557
|
exports.defineAudio = defineAudio;
|
|
1901
2558
|
exports.defineEffectElements = defineEffectElements;
|
|
1902
2559
|
exports.defineGestureFx = defineGestureFx;
|
|
2560
|
+
exports.definePlayer = definePlayer;
|
|
1903
2561
|
exports.defineStory = defineStory;
|
|
2562
|
+
exports.defineTheme = defineTheme;
|
|
1904
2563
|
exports.disableAudio = disableAudio;
|
|
1905
2564
|
exports.enableAudio = enableAudio;
|
|
1906
2565
|
exports.flingVelocity = flingVelocity;
|
|
@@ -1909,15 +2568,23 @@ exports.fxLayer = fxLayer;
|
|
|
1909
2568
|
exports.getAudio = getAudio;
|
|
1910
2569
|
exports.hexRgb = hexRgb;
|
|
1911
2570
|
exports.noise2 = noise2;
|
|
2571
|
+
exports.normalizeAnimation = normalizeAnimation;
|
|
1912
2572
|
exports.onBeat = onBeat;
|
|
2573
|
+
exports.playThemeEffect = playThemeEffect;
|
|
1913
2574
|
exports.registerAllEffects = registerAllEffects;
|
|
1914
2575
|
exports.registerAudioEffects = registerAudioEffects;
|
|
1915
2576
|
exports.registerCardClickEffects = registerCardClickEffects;
|
|
1916
2577
|
exports.registerCursorEffects = registerCursorEffects;
|
|
1917
2578
|
exports.registerGenerativeEffects = registerGenerativeEffects;
|
|
2579
|
+
exports.registerMicroEffects = registerMicroEffects;
|
|
1918
2580
|
exports.registerPageEffects = registerPageEffects;
|
|
1919
2581
|
exports.registerPhysicsEffects = registerPhysicsEffects;
|
|
1920
2582
|
exports.solveSpring = solveSpring;
|
|
1921
2583
|
exports.springKeyframes = springKeyframes;
|
|
1922
2584
|
exports.storyProgress = storyProgress;
|
|
2585
|
+
exports.swapLabel = swapLabel;
|
|
2586
|
+
exports.themeCss = themeCss;
|
|
2587
|
+
exports.themePreset = themePreset;
|
|
2588
|
+
exports.themeVars = themeVars;
|
|
2589
|
+
exports.togglePressed = togglePressed;
|
|
1923
2590
|
//# sourceMappingURL=effects.cjs.map
|