use-scroll-animate 2.4.0 → 2.5.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 +15 -0
- package/README.md +1 -0
- package/README_ja.md +1 -0
- package/README_zh.md +1 -0
- package/dist/components/click.cjs +985 -0
- package/dist/components/click.cjs.map +1 -0
- package/dist/components/click.css +9 -0
- package/dist/components/click.d.cts +250 -0
- package/dist/components/click.d.ts +250 -0
- package/dist/components/click.js +967 -0
- package/dist/components/click.js.map +1 -0
- package/dist/components.cjs +19 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.css +7 -0
- package/dist/components.d.cts +214 -2
- package/dist/components.d.ts +214 -2
- package/dist/components.js +5 -1
- package/dist/components.js.map +1 -1
- package/dist/components.umd.js +2 -2
- package/dist/components.umd.js.map +1 -1
- package/docs/components.md +13 -0
- package/package.json +13 -2
|
@@ -0,0 +1,967 @@
|
|
|
1
|
+
import { p as prefersReducedMotion, d as defineElement, E as EASE_OUT, a as clamp, b as caf, n as now, r as raf } from '../chunks/base-CuvCgqLy.js';
|
|
2
|
+
export { c as configureComponents } from '../chunks/base-CuvCgqLy.js';
|
|
3
|
+
import { d as springEasing, c as createSpring } from '../chunks/spring-BKUU7-Xm.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Click-effect helpers (v2.5): `burst()`, `confetti()`, `shake()`, `haptic()`.
|
|
7
|
+
* Particles live in one fixed, pointer-transparent layer and are removed when
|
|
8
|
+
* their animation ends. Under reduced motion particles are skipped and
|
|
9
|
+
* `shake()` only flashes an outline.
|
|
10
|
+
*/
|
|
11
|
+
let layer = null;
|
|
12
|
+
function fxLayer() {
|
|
13
|
+
if (layer && layer.isConnected)
|
|
14
|
+
return layer;
|
|
15
|
+
layer = document.createElement('div');
|
|
16
|
+
layer.className = 'usa-fx-layer';
|
|
17
|
+
layer.setAttribute('aria-hidden', 'true');
|
|
18
|
+
layer.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:2147483000;overflow:hidden;contain:strict';
|
|
19
|
+
document.body.appendChild(layer);
|
|
20
|
+
return layer;
|
|
21
|
+
}
|
|
22
|
+
const PALETTE = ['#7c5cff', '#22d3ee', '#f472b6', '#facc15', '#34d399', '#fb923c'];
|
|
23
|
+
const GLYPH = { star: '★', heart: '♥' };
|
|
24
|
+
function particle(x, y, size, color, shape) {
|
|
25
|
+
const p = document.createElement('span');
|
|
26
|
+
const glyph = GLYPH[shape] || (shape !== 'circle' && shape !== 'square' ? shape : '');
|
|
27
|
+
p.style.cssText = `position:absolute;left:${x}px;top:${y}px;width:${size}px;height:${size}px;margin:${-size / 2}px 0 0 ${-size / 2}px;will-change:transform,opacity;` +
|
|
28
|
+
(glyph ? `font-size:${size * 2}px;line-height:${size}px;text-align:center;color:${color}` : `background:${color};border-radius:${shape === 'square' ? '2px' : '50%'}`);
|
|
29
|
+
if (glyph)
|
|
30
|
+
p.textContent = glyph;
|
|
31
|
+
fxLayer().appendChild(p);
|
|
32
|
+
return p;
|
|
33
|
+
}
|
|
34
|
+
const done = (a, el) => {
|
|
35
|
+
if (a)
|
|
36
|
+
a.onfinish = () => el.remove();
|
|
37
|
+
else
|
|
38
|
+
el.remove();
|
|
39
|
+
};
|
|
40
|
+
/** Particles radiating from client point (x, y). Returns the number spawned. */
|
|
41
|
+
function burst(x, y, options = {}) {
|
|
42
|
+
if (typeof document === 'undefined' || prefersReducedMotion())
|
|
43
|
+
return 0;
|
|
44
|
+
const { count = 12, colors = PALETTE, distance = 48, size = 6, shape = 'circle', duration = 600 } = options;
|
|
45
|
+
for (let i = 0; i < count; i++) {
|
|
46
|
+
const angle = (i / count) * Math.PI * 2 + Math.random() * 0.4;
|
|
47
|
+
const d = distance * (0.7 + Math.random() * 0.5);
|
|
48
|
+
const p = particle(x, y, size, colors[i % colors.length], shape);
|
|
49
|
+
const a = typeof p.animate === 'function'
|
|
50
|
+
? p.animate([
|
|
51
|
+
{ transform: 'translate(0,0) scale(1)', opacity: 1 },
|
|
52
|
+
{ transform: `translate(${Math.cos(angle) * d}px, ${Math.sin(angle) * d}px) scale(0.2)`, opacity: 0 },
|
|
53
|
+
], { duration: duration * (0.8 + Math.random() * 0.4), easing: 'cubic-bezier(0.22, 1, 0.36, 1)', fill: 'forwards' })
|
|
54
|
+
: null;
|
|
55
|
+
done(a, p);
|
|
56
|
+
}
|
|
57
|
+
return count;
|
|
58
|
+
}
|
|
59
|
+
/** A confetti cannon (paper pieces with gravity, drift and spin). */
|
|
60
|
+
function confetti(options = {}) {
|
|
61
|
+
if (typeof document === 'undefined' || prefersReducedMotion())
|
|
62
|
+
return 0;
|
|
63
|
+
const W = window.innerWidth || 800;
|
|
64
|
+
const H = window.innerHeight || 600;
|
|
65
|
+
const { x = W / 2, y = H * 0.66, count = 80, spread = 70, velocity = 1, colors = PALETTE, duration = 1600 } = options;
|
|
66
|
+
for (let i = 0; i < count; i++) {
|
|
67
|
+
const angle = ((-90 + (Math.random() - 0.5) * spread) * Math.PI) / 180;
|
|
68
|
+
const speed = (260 + Math.random() * 320) * velocity;
|
|
69
|
+
const vx = Math.cos(angle) * speed;
|
|
70
|
+
const vy = Math.sin(angle) * speed;
|
|
71
|
+
const p = particle(x, y, 6 + Math.random() * 5, colors[i % colors.length], Math.random() > 0.5 ? 'square' : 'circle');
|
|
72
|
+
p.style.height = `${4 + Math.random() * 3}px`;
|
|
73
|
+
const frames = [];
|
|
74
|
+
const steps = 8;
|
|
75
|
+
const T = duration / 1000;
|
|
76
|
+
const spin = (Math.random() - 0.5) * 1440;
|
|
77
|
+
for (let s = 0; s <= steps; s++) {
|
|
78
|
+
const t = (s / steps) * T;
|
|
79
|
+
const g = 900;
|
|
80
|
+
frames.push({
|
|
81
|
+
transform: `translate(${(vx * t * 0.8).toFixed(1)}px, ${(vy * t + 0.5 * g * t * t).toFixed(1)}px) rotate(${((spin * s) / steps).toFixed(0)}deg) rotateX(${s * 120}deg)`,
|
|
82
|
+
opacity: s === steps ? 0 : 1,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const a = typeof p.animate === 'function' ? p.animate(frames, { duration: duration * (0.85 + Math.random() * 0.3), easing: 'linear', fill: 'forwards' }) : null;
|
|
86
|
+
done(a, p);
|
|
87
|
+
}
|
|
88
|
+
return count;
|
|
89
|
+
}
|
|
90
|
+
/** Horizontal error shake (`intensity` px, default 8). Reduced motion: a red outline flash. */
|
|
91
|
+
function shake(el, intensity = 8, duration = 480) {
|
|
92
|
+
const t = el;
|
|
93
|
+
if (typeof t.animate !== 'function')
|
|
94
|
+
return null;
|
|
95
|
+
if (prefersReducedMotion())
|
|
96
|
+
return t.animate([{ outline: '2px solid #e5484d' }, { outline: '2px solid transparent' }], { duration: 600 });
|
|
97
|
+
const k = intensity;
|
|
98
|
+
return t.animate([0, -k, k, -k * 0.75, k * 0.75, -k * 0.4, k * 0.4, 0].map((v) => ({ transform: `translateX(${v}px)` })), { duration, easing: 'ease-in-out' });
|
|
99
|
+
}
|
|
100
|
+
/** `navigator.vibrate()` where supported (Android Chrome, some WebViews). Returns whether it ran. */
|
|
101
|
+
function haptic(pattern = 10) {
|
|
102
|
+
try {
|
|
103
|
+
return typeof navigator !== 'undefined' && typeof navigator.vibrate === 'function' ? navigator.vibrate(pattern) : false;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var css$6 = "usa-click{position:relative;display:inline-block;overflow:hidden;isolation:isolate;border-radius:inherit;-webkit-tap-highlight-color:transparent;touch-action:manipulation}usa-click[block]{display:block}usa-click .usa-click-wave{position:absolute;border-radius:50%;pointer-events:none;z-index:-1;transform:scale(0);background:radial-gradient(circle,var(--usa-wave) 0 55%,color-mix(in srgb,var(--usa-wave) 40%,transparent) 70%,transparent 72%)}";
|
|
111
|
+
|
|
112
|
+
const CLICK_EFFECTS = ['ripple', 'burst', 'confetti', 'squish', 'press-spring', 'shake'];
|
|
113
|
+
function defineClick(tag = 'usa-click') {
|
|
114
|
+
return defineElement(tag, (Base) => class UsaClick extends Base {
|
|
115
|
+
constructor() {
|
|
116
|
+
super(...arguments);
|
|
117
|
+
this._press = null;
|
|
118
|
+
}
|
|
119
|
+
static get observedAttributes() {
|
|
120
|
+
return ['effect', 'disabled'];
|
|
121
|
+
}
|
|
122
|
+
get effects() {
|
|
123
|
+
return this.str('effect', 'ripple').split(/[\s,]+/).filter(Boolean);
|
|
124
|
+
}
|
|
125
|
+
mount() {
|
|
126
|
+
this.listen(this, 'pointerdown', (e) => {
|
|
127
|
+
if (this.flag('disabled') || (e.pointerType === 'mouse' && e.button !== 0))
|
|
128
|
+
return;
|
|
129
|
+
this.down();
|
|
130
|
+
if (this.effects.includes('ripple'))
|
|
131
|
+
this.ripple(e.clientX, e.clientY);
|
|
132
|
+
});
|
|
133
|
+
const up = () => this.up();
|
|
134
|
+
this.listen(this, 'pointerup', up);
|
|
135
|
+
this.listen(this, 'pointerleave', up);
|
|
136
|
+
this.listen(this, 'pointercancel', up);
|
|
137
|
+
this.listen(this, 'click', (e) => {
|
|
138
|
+
if (this.flag('disabled'))
|
|
139
|
+
return;
|
|
140
|
+
const kb = e.detail === 0;
|
|
141
|
+
const r = this.getBoundingClientRect();
|
|
142
|
+
const x = kb ? r.left + r.width / 2 : e.clientX;
|
|
143
|
+
const y = kb ? r.top + r.height / 2 : e.clientY;
|
|
144
|
+
if (kb && this.effects.includes('ripple'))
|
|
145
|
+
this.ripple(x, y);
|
|
146
|
+
this.play(x, y);
|
|
147
|
+
if (this.effects.includes('shake') && this.str('trigger') === 'click')
|
|
148
|
+
this.shake();
|
|
149
|
+
});
|
|
150
|
+
this.listen(this, 'keydown', (e) => {
|
|
151
|
+
if ((e.key === 'Enter' || e.key === ' ') && !e.repeat)
|
|
152
|
+
this.down();
|
|
153
|
+
});
|
|
154
|
+
this.listen(this, 'keyup', up);
|
|
155
|
+
if (this.effects.includes('shake'))
|
|
156
|
+
this.listen(this, 'invalid', () => this.shake(), { capture: true });
|
|
157
|
+
}
|
|
158
|
+
/** Particles + haptics at client (x, y) (default: centre). */
|
|
159
|
+
play(x, y) {
|
|
160
|
+
if (x === undefined || y === undefined) {
|
|
161
|
+
const r = this.getBoundingClientRect();
|
|
162
|
+
x = r.left + r.width / 2;
|
|
163
|
+
y = r.top + r.height / 2;
|
|
164
|
+
}
|
|
165
|
+
const fx = this.effects;
|
|
166
|
+
const colors = this.str('color') ? this.str('color').split(',') : undefined;
|
|
167
|
+
if (fx.includes('burst'))
|
|
168
|
+
burst(x, y, { count: this.num('count', 12), shape: this.str('shape', 'circle'), colors });
|
|
169
|
+
if (fx.includes('confetti'))
|
|
170
|
+
confetti({ x, y, count: this.num('count', 60), colors, spread: 90, velocity: 0.8 });
|
|
171
|
+
if (this.hasAttribute('haptic'))
|
|
172
|
+
haptic(this.num('haptic', 10));
|
|
173
|
+
this.emit('click-effect', { x, y, effects: fx });
|
|
174
|
+
}
|
|
175
|
+
shake() {
|
|
176
|
+
shake(this);
|
|
177
|
+
if (this.hasAttribute('haptic'))
|
|
178
|
+
haptic([30, 40, 30]);
|
|
179
|
+
}
|
|
180
|
+
ripple(x, y) {
|
|
181
|
+
if (this.reduced)
|
|
182
|
+
return;
|
|
183
|
+
const r = this.getBoundingClientRect();
|
|
184
|
+
const cx = x - r.left;
|
|
185
|
+
const cy = y - r.top;
|
|
186
|
+
const radius = Math.hypot(Math.max(cx, r.width - cx), Math.max(cy, r.height - cy));
|
|
187
|
+
const wave = document.createElement('span');
|
|
188
|
+
wave.className = 'usa-click-wave';
|
|
189
|
+
wave.setAttribute('aria-hidden', 'true');
|
|
190
|
+
wave.style.cssText = `width:${radius * 2}px;height:${radius * 2}px;left:${cx - radius}px;top:${cy - radius}px;--usa-wave:${this.str('color', 'currentColor').split(',')[0]}`;
|
|
191
|
+
this.append(wave);
|
|
192
|
+
const a = this.motion(wave, [{ transform: 'scale(0)', opacity: 0.35 }, { transform: 'scale(1)', opacity: 0.25, offset: 0.6 }, { transform: 'scale(1.05)', opacity: 0 }], {
|
|
193
|
+
duration: 650,
|
|
194
|
+
easing: EASE_OUT,
|
|
195
|
+
fill: 'forwards',
|
|
196
|
+
});
|
|
197
|
+
if (a)
|
|
198
|
+
a.onfinish = () => wave.remove();
|
|
199
|
+
else
|
|
200
|
+
wave.remove();
|
|
201
|
+
}
|
|
202
|
+
down() {
|
|
203
|
+
if (this.hasAttribute('data-pressed'))
|
|
204
|
+
return;
|
|
205
|
+
const fx = this.effects;
|
|
206
|
+
if (!fx.includes('squish') && !fx.includes('press-spring'))
|
|
207
|
+
return;
|
|
208
|
+
this.setAttribute('data-pressed', '');
|
|
209
|
+
this._press?.cancel();
|
|
210
|
+
const to = this.reduced ? { opacity: 0.75 } : fx.includes('squish') ? { transform: 'scale(1.08, 0.86)' } : { transform: 'scale(0.94)' };
|
|
211
|
+
const from = this.reduced ? { opacity: 1 } : { transform: 'none' };
|
|
212
|
+
this._press = this.motion(this, [from, to], {
|
|
213
|
+
duration: 110,
|
|
214
|
+
easing: 'ease-out',
|
|
215
|
+
fill: 'forwards',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
up() {
|
|
219
|
+
if (!this.hasAttribute('data-pressed'))
|
|
220
|
+
return;
|
|
221
|
+
this.removeAttribute('data-pressed');
|
|
222
|
+
const fx = this.effects;
|
|
223
|
+
this._press?.cancel();
|
|
224
|
+
if (this.reduced) {
|
|
225
|
+
this._press = this.motion(this, [{ opacity: 0.75 }, { opacity: 1 }], { duration: 150 });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const from = fx.includes('squish') ? 'scale(1.08, 0.86)' : 'scale(0.94)';
|
|
229
|
+
const frames = fx.includes('squish')
|
|
230
|
+
? [{ transform: from }, { transform: 'scale(0.92, 1.1)', offset: 0.3 }, { transform: 'scale(1.03, 0.97)', offset: 0.6 }, { transform: 'none' }]
|
|
231
|
+
: [{ transform: from }, { transform: 'none' }];
|
|
232
|
+
this._press = this.motion(this, frames, fx.includes('squish') ? { duration: 520, easing: 'ease-out' } : springEasing('bouncy'));
|
|
233
|
+
}
|
|
234
|
+
}, { id: 'click', text: css$6 });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
var css$5 = "usa-button{position:relative;display:inline-block;isolation:isolate;-webkit-tap-highlight-color:transparent;touch-action:manipulation;vertical-align:middle}usa-button[role=\"button\"]{cursor:pointer}usa-button .usa-button-face{position:relative;overflow:hidden;will-change:transform;transform-origin:50% 60%;white-space:nowrap}usa-button[deform~=\"gooey\"] .usa-button-face{overflow:visible}usa-button[deform~=\"dent\"] .usa-button-face::after{content:\"\";position:absolute;inset:0;border-radius:inherit;pointer-events:none;opacity:0;transition:opacity 0.2s ease;background:radial-gradient(circle at var(--usa-dent-x,50%) var(--usa-dent-y,50%),rgb(0 0 0 / 0.28),transparent 60%)}usa-button[deform~=\"dent\"][data-pressed] .usa-button-face::after{opacity:1}usa-button .usa-button-goo{position:absolute;inset:0;z-index:-1;pointer-events:none;filter:url(#usa-goo)}usa-button .usa-button-goo i{position:absolute;width:22px;height:22px;border-radius:50%;background:var(--usa-goo-bg);transform:translate(-50%,-50%) scale(0.4)}usa-button .usa-button-face[data-shape=\"circle\"],usa-button .usa-button-face[data-shape=\"icon\"]{padding-inline:0;aspect-ratio:1;border-radius:999px;display:inline-grid;place-items:center}usa-button .usa-button-face [data-label],usa-button .usa-button-face [data-icon]{transition:opacity 0.2s ease}usa-button .usa-button-face[data-shape=\"circle\"] [data-label],usa-button .usa-button-face[data-shape=\"icon\"] [data-label]{position:absolute;opacity:0;pointer-events:none}usa-button .usa-button-face[data-shape=\"circle\"] [data-icon]{opacity:0}usa-button .usa-button-status{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none}usa-button .usa-button-status svg{grid-area:1 / 1;width:1.25em;height:1.25em;fill:none;stroke:currentColor;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round;opacity:0}usa-button .usa-button-spin circle{stroke-dasharray:60 100;transform-origin:50% 50%;animation:usa-btn-spin 0.8s linear infinite}usa-button .usa-button-ok path,usa-button .usa-button-err path{stroke-dasharray:1;stroke-dashoffset:1;transition:stroke-dashoffset 0.35s ease 0.1s}usa-button .usa-button-face[data-state=\"loading\"] .usa-button-spin,usa-button .usa-button-face[data-state=\"success\"] .usa-button-ok,usa-button .usa-button-face[data-state=\"error\"] .usa-button-err{opacity:1}usa-button .usa-button-face[data-state=\"success\"] .usa-button-ok path,usa-button .usa-button-face[data-state=\"error\"] .usa-button-err path{stroke-dashoffset:0}usa-button .usa-button-face:is([data-state=\"loading\"],[data-state=\"success\"],[data-state=\"error\"])>:not(.usa-button-status){opacity:0}usa-button .usa-button-face[data-state=\"success\"]{background:#16a34a;border-color:#16a34a;color:#fff}usa-button .usa-button-face[data-state=\"error\"]{background:#e5484d;border-color:#e5484d;color:#fff}usa-button .usa-button-face[data-state]{transition:background-color 0.25s ease,color 0.25s ease,border-radius 0.3s ease}@keyframes usa-btn-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){usa-button .usa-button-spin circle{animation-duration:2.4s}usa-button .usa-button-face,usa-button .usa-button-face *{transition:none !important}}";
|
|
238
|
+
|
|
239
|
+
const BUTTON_DEFORMS = ['squash', 'wobble', 'gooey', 'dent'];
|
|
240
|
+
let gooInjected = false;
|
|
241
|
+
function injectGoo() {
|
|
242
|
+
if (gooInjected || typeof document === 'undefined')
|
|
243
|
+
return;
|
|
244
|
+
gooInjected = true;
|
|
245
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
246
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
247
|
+
svg.setAttribute('width', '0');
|
|
248
|
+
svg.setAttribute('height', '0');
|
|
249
|
+
svg.style.position = 'absolute';
|
|
250
|
+
svg.innerHTML = '<filter id="usa-goo"><feGaussianBlur in="SourceGraphic" stdDeviation="6" result="b"/><feColorMatrix in="b" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 22 -9" result="g"/><feComposite in="SourceGraphic" in2="g" operator="atop"/></filter>';
|
|
251
|
+
document.body.appendChild(svg);
|
|
252
|
+
}
|
|
253
|
+
function defineButton(tag = 'usa-button') {
|
|
254
|
+
return defineElement(tag, (Base) => class UsaButton extends Base {
|
|
255
|
+
constructor() {
|
|
256
|
+
super(...arguments);
|
|
257
|
+
this._press = null;
|
|
258
|
+
this._status = null;
|
|
259
|
+
this._shapeBefore = null;
|
|
260
|
+
}
|
|
261
|
+
static get observedAttributes() {
|
|
262
|
+
return ['deform', 'state', 'shape', 'disabled'];
|
|
263
|
+
}
|
|
264
|
+
get target() {
|
|
265
|
+
return this.querySelector(':scope > button, :scope > a, :scope > [role="button"]') || this;
|
|
266
|
+
}
|
|
267
|
+
deforms() {
|
|
268
|
+
return this.str('deform').split(/[\s,]+/).filter(Boolean);
|
|
269
|
+
}
|
|
270
|
+
get shape() {
|
|
271
|
+
return this.str('shape', 'pill') || 'pill';
|
|
272
|
+
}
|
|
273
|
+
set shape(v) {
|
|
274
|
+
this.morphTo(v);
|
|
275
|
+
}
|
|
276
|
+
get state() {
|
|
277
|
+
return this.str('state', 'idle') || 'idle';
|
|
278
|
+
}
|
|
279
|
+
set state(v) {
|
|
280
|
+
this.setAttribute('state', v);
|
|
281
|
+
}
|
|
282
|
+
mount() {
|
|
283
|
+
const t = this.target;
|
|
284
|
+
if (t === this) {
|
|
285
|
+
this.setAttribute('role', 'button');
|
|
286
|
+
if (!this.hasAttribute('tabindex'))
|
|
287
|
+
this.tabIndex = 0;
|
|
288
|
+
}
|
|
289
|
+
t.classList.add('usa-button-face');
|
|
290
|
+
if (this.str('morph') === 'submit' && !t.querySelector('.usa-button-status')) {
|
|
291
|
+
const s = document.createElement('span');
|
|
292
|
+
s.className = 'usa-button-status';
|
|
293
|
+
s.setAttribute('aria-hidden', 'true');
|
|
294
|
+
s.innerHTML =
|
|
295
|
+
'<svg class="usa-button-spin" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" pathLength="100"/></svg>' +
|
|
296
|
+
'<svg class="usa-button-ok" viewBox="0 0 24 24"><path d="M5 12.5l4.2 4.2L19 7" pathLength="1"/></svg>' +
|
|
297
|
+
'<svg class="usa-button-err" viewBox="0 0 24 24"><path d="M7 7l10 10M17 7L7 17" pathLength="1"/></svg>';
|
|
298
|
+
t.append(s);
|
|
299
|
+
const live = document.createElement('span');
|
|
300
|
+
live.className = 'usa-sr';
|
|
301
|
+
live.setAttribute('role', 'status');
|
|
302
|
+
this.append(live);
|
|
303
|
+
this._status = live;
|
|
304
|
+
}
|
|
305
|
+
if (this.deforms().includes('gooey'))
|
|
306
|
+
injectGoo();
|
|
307
|
+
this.applyState();
|
|
308
|
+
this.listen(t, 'pointerdown', (e) => {
|
|
309
|
+
if (this.isOff() || (e.pointerType === 'mouse' && e.button !== 0))
|
|
310
|
+
return;
|
|
311
|
+
this.down(e.clientX, e.clientY);
|
|
312
|
+
});
|
|
313
|
+
for (const ev of ['pointerup', 'pointerleave', 'pointercancel'])
|
|
314
|
+
this.listen(t, ev, () => this.up());
|
|
315
|
+
this.listen(t, 'keydown', (e) => (e.key === 'Enter' || e.key === ' ') && !e.repeat && this.down());
|
|
316
|
+
this.listen(t, 'keyup', () => this.up());
|
|
317
|
+
this.listen(t, 'click', (e) => {
|
|
318
|
+
if (this.isOff()) {
|
|
319
|
+
if (this.state === 'loading')
|
|
320
|
+
e.preventDefault();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (this.deforms().includes('wobble'))
|
|
324
|
+
this.wobble();
|
|
325
|
+
if (this.hasAttribute('haptic'))
|
|
326
|
+
haptic(this.num('haptic', 10));
|
|
327
|
+
if (this.str('morph') === 'submit' && this.state === 'idle')
|
|
328
|
+
this.submit();
|
|
329
|
+
});
|
|
330
|
+
if (t === this)
|
|
331
|
+
this.listen(this, 'keydown', (e) => {
|
|
332
|
+
if ((e.key === 'Enter' || e.key === ' ') && !e.repeat) {
|
|
333
|
+
e.preventDefault();
|
|
334
|
+
this.click();
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
unmount() {
|
|
339
|
+
clearTimeout(this._timer);
|
|
340
|
+
this._press?.cancel();
|
|
341
|
+
}
|
|
342
|
+
changed(name) {
|
|
343
|
+
if (name === 'state')
|
|
344
|
+
this.applyState();
|
|
345
|
+
else if (name === 'shape')
|
|
346
|
+
this.target.setAttribute('data-shape', this.shape);
|
|
347
|
+
else {
|
|
348
|
+
super.changed(name);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
isOff() {
|
|
352
|
+
return this.flag('disabled') || this.state === 'loading';
|
|
353
|
+
}
|
|
354
|
+
/* ------------------------------------------------ press deformations */
|
|
355
|
+
down(x, y) {
|
|
356
|
+
if (this.reduced || this.hasAttribute('data-pressed'))
|
|
357
|
+
return;
|
|
358
|
+
const d = this.deforms();
|
|
359
|
+
if (!d.length)
|
|
360
|
+
return;
|
|
361
|
+
this.setAttribute('data-pressed', '');
|
|
362
|
+
const t = this.target;
|
|
363
|
+
const r = t.getBoundingClientRect();
|
|
364
|
+
const px = x === undefined ? 0.5 : clamp((x - r.left) / (r.width || 1), 0, 1);
|
|
365
|
+
const py = y === undefined ? 0.5 : clamp((y - r.top) / (r.height || 1), 0, 1);
|
|
366
|
+
t.style.setProperty('--usa-dent-x', `${(px * 100).toFixed(1)}%`);
|
|
367
|
+
t.style.setProperty('--usa-dent-y', `${(py * 100).toFixed(1)}%`);
|
|
368
|
+
const parts = [];
|
|
369
|
+
if (d.includes('dent'))
|
|
370
|
+
parts.push(`perspective(600px) rotateX(${((0.5 - py) * -14).toFixed(2)}deg) rotateY(${((px - 0.5) * 14).toFixed(2)}deg) scale(0.97)`);
|
|
371
|
+
if (d.includes('squash'))
|
|
372
|
+
parts.push('scale(1.12, 0.84)');
|
|
373
|
+
if (d.includes('gooey')) {
|
|
374
|
+
parts.push('scale(0.96)');
|
|
375
|
+
this.goo(px, py);
|
|
376
|
+
}
|
|
377
|
+
if (!parts.length)
|
|
378
|
+
return;
|
|
379
|
+
this._press?.cancel();
|
|
380
|
+
this._press = this.motion(t, [{ transform: 'none' }, { transform: parts.join(' ') }], { duration: 110, easing: 'ease-out', fill: 'forwards' });
|
|
381
|
+
}
|
|
382
|
+
up() {
|
|
383
|
+
if (!this.hasAttribute('data-pressed'))
|
|
384
|
+
return;
|
|
385
|
+
this.removeAttribute('data-pressed');
|
|
386
|
+
const t = this.target;
|
|
387
|
+
const a = this._press;
|
|
388
|
+
const from = a?.effect?.getKeyframes?.().slice(-1)[0]?.transform;
|
|
389
|
+
a?.cancel();
|
|
390
|
+
if (!from)
|
|
391
|
+
return;
|
|
392
|
+
this._press = this.motion(t, [{ transform: from }, { transform: 'none' }], { ...springEasing(this.deforms().includes('squash') ? 'bouncy' : 'wobbly') });
|
|
393
|
+
}
|
|
394
|
+
wobble() {
|
|
395
|
+
if (this.reduced)
|
|
396
|
+
return;
|
|
397
|
+
const t = this.target;
|
|
398
|
+
const r = getComputedStyle(t).borderRadius || '12px';
|
|
399
|
+
this.motion(t, [
|
|
400
|
+
{ borderRadius: r },
|
|
401
|
+
{ borderRadius: '42% 58% 50% 50% / 60% 45% 55% 40%', offset: 0.2 },
|
|
402
|
+
{ borderRadius: '58% 42% 45% 55% / 40% 60% 40% 60%', offset: 0.45 },
|
|
403
|
+
{ borderRadius: '48% 52% 52% 48% / 52% 48% 52% 48%', offset: 0.7 },
|
|
404
|
+
{ borderRadius: r },
|
|
405
|
+
], { duration: 700, easing: 'ease-out' });
|
|
406
|
+
}
|
|
407
|
+
goo(px, py) {
|
|
408
|
+
const t = this.target;
|
|
409
|
+
const layer = document.createElement('span');
|
|
410
|
+
layer.className = 'usa-button-goo';
|
|
411
|
+
layer.setAttribute('aria-hidden', 'true');
|
|
412
|
+
layer.style.setProperty('--usa-goo-bg', getComputedStyle(t).backgroundColor || 'currentColor');
|
|
413
|
+
const drops = [];
|
|
414
|
+
for (let i = 0; i < 4; i++) {
|
|
415
|
+
const d = document.createElement('i');
|
|
416
|
+
d.style.left = `${(px * 100).toFixed(1)}%`;
|
|
417
|
+
d.style.top = `${(py * 100).toFixed(1)}%`;
|
|
418
|
+
layer.append(d);
|
|
419
|
+
drops.push(d);
|
|
420
|
+
}
|
|
421
|
+
this.prepend(layer);
|
|
422
|
+
let left = drops.length;
|
|
423
|
+
drops.forEach((d, i) => {
|
|
424
|
+
const ang = (i / drops.length) * Math.PI * 2 + 0.6;
|
|
425
|
+
const dist = 26 + (i % 2) * 12;
|
|
426
|
+
const a = this.motion(d, [
|
|
427
|
+
{ transform: 'translate(-50%, -50%) scale(0.4)' },
|
|
428
|
+
{ transform: `translate(calc(-50% + ${(Math.cos(ang) * dist).toFixed(1)}px), calc(-50% + ${(Math.sin(ang) * dist).toFixed(1)}px)) scale(1)`, offset: 0.45 },
|
|
429
|
+
{ transform: 'translate(-50%, -50%) scale(0.2)' },
|
|
430
|
+
], { duration: 700, easing: 'cubic-bezier(0.34, 1.3, 0.64, 1)', fill: 'forwards' });
|
|
431
|
+
const end = () => --left === 0 && layer.remove();
|
|
432
|
+
if (a)
|
|
433
|
+
a.onfinish = end;
|
|
434
|
+
else
|
|
435
|
+
end();
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
/* ------------------------------------------------ shape morph */
|
|
439
|
+
async morphTo(shape) {
|
|
440
|
+
const t = this.target;
|
|
441
|
+
const before = t.getBoundingClientRect();
|
|
442
|
+
this.setAttribute('shape', shape);
|
|
443
|
+
t.setAttribute('data-shape', shape);
|
|
444
|
+
if (this.reduced)
|
|
445
|
+
return;
|
|
446
|
+
const after = t.getBoundingClientRect();
|
|
447
|
+
if (!before.width || !after.width || Math.abs(before.width - after.width) < 0.5)
|
|
448
|
+
return;
|
|
449
|
+
const a = this.motion(t, [{ width: `${before.width}px` }, { width: `${after.width}px` }], springEasing('stiff'));
|
|
450
|
+
await a?.finished.catch(() => undefined);
|
|
451
|
+
}
|
|
452
|
+
/* ------------------------------------------------ submit morph */
|
|
453
|
+
submit() {
|
|
454
|
+
let settled = false;
|
|
455
|
+
const done = (ok = true) => {
|
|
456
|
+
if (settled)
|
|
457
|
+
return;
|
|
458
|
+
settled = true;
|
|
459
|
+
this.state = ok ? 'success' : 'error';
|
|
460
|
+
};
|
|
461
|
+
this.state = 'loading';
|
|
462
|
+
this.emit('submit', { done });
|
|
463
|
+
}
|
|
464
|
+
applyState() {
|
|
465
|
+
if (this.str('morph') !== 'submit')
|
|
466
|
+
return;
|
|
467
|
+
const t = this.target;
|
|
468
|
+
const s = this.state;
|
|
469
|
+
clearTimeout(this._timer);
|
|
470
|
+
t.setAttribute('data-state', s);
|
|
471
|
+
if (s === 'loading') {
|
|
472
|
+
if (this._shapeBefore === null)
|
|
473
|
+
this._shapeBefore = this.shape;
|
|
474
|
+
t.setAttribute('aria-busy', 'true');
|
|
475
|
+
this.morphTo('circle');
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
t.removeAttribute('aria-busy');
|
|
479
|
+
}
|
|
480
|
+
if (s === 'error') {
|
|
481
|
+
shake(t);
|
|
482
|
+
if (this.hasAttribute('haptic'))
|
|
483
|
+
haptic([30, 40, 30]);
|
|
484
|
+
}
|
|
485
|
+
if (s === 'success' && !this.reduced) {
|
|
486
|
+
const ok = t.querySelector('.usa-button-ok');
|
|
487
|
+
if (ok)
|
|
488
|
+
this.motion(ok, [{ transform: 'scale(0.4)' }, { transform: 'scale(1)' }], springEasing('bouncy'));
|
|
489
|
+
}
|
|
490
|
+
if (this._status)
|
|
491
|
+
this._status.textContent = s === 'loading' ? 'Loading…' : s === 'success' ? 'Done' : s === 'error' ? 'Failed' : '';
|
|
492
|
+
if (s === 'success' || s === 'error') {
|
|
493
|
+
this._timer = setTimeout(() => {
|
|
494
|
+
this.state = 'idle';
|
|
495
|
+
}, this.num('reset', 1800));
|
|
496
|
+
}
|
|
497
|
+
if (s === 'idle' && this._shapeBefore !== null) {
|
|
498
|
+
const back = this._shapeBefore;
|
|
499
|
+
this._shapeBefore = null;
|
|
500
|
+
this.morphTo(back);
|
|
501
|
+
}
|
|
502
|
+
this.emit('state', { state: s });
|
|
503
|
+
}
|
|
504
|
+
}, { id: 'button', text: css$5 });
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
var css$4 = "usa-icon-morph{display:inline-grid;place-items:center;line-height:0;color:inherit;-webkit-tap-highlight-color:transparent;vertical-align:middle}usa-icon-morph svg{fill:currentColor;overflow:visible}usa-icon-morph[toggle]{cursor:pointer;border-radius:8px}usa-icon-morph[toggle]:focus-visible{outline:2px solid var(--usa-accent,#7c5cff);outline-offset:3px}";
|
|
508
|
+
|
|
509
|
+
const C = [[12, 12], [12, 12], [12, 12], [12, 12]];
|
|
510
|
+
/**
|
|
511
|
+
* Morphable icons: every icon is three quads (four points each) on a 24×24
|
|
512
|
+
* grid, so any icon can morph into any other by interpolating points.
|
|
513
|
+
*/
|
|
514
|
+
const MORPH_ICONS = {
|
|
515
|
+
play: [[[7, 5], [12.5, 8.25], [12.5, 15.75], [7, 19]], [[12.5, 8.25], [19, 12], [19, 12], [12.5, 15.75]], C],
|
|
516
|
+
pause: [[[6, 5], [10, 5], [10, 19], [6, 19]], [[14, 5], [18, 5], [18, 19], [14, 19]], C],
|
|
517
|
+
menu: [[[4, 6], [20, 6], [20, 8], [4, 8]], [[4, 11], [20, 11], [20, 13], [4, 13]], [[4, 16], [20, 16], [20, 18], [4, 18]]],
|
|
518
|
+
close: [[[5.2, 6.6], [6.6, 5.2], [18.8, 17.4], [17.4, 18.8]], C, [[17.4, 5.2], [18.8, 6.6], [6.6, 18.8], [5.2, 17.4]]],
|
|
519
|
+
plus: [[[11, 4], [13, 4], [13, 20], [11, 20]], [[4, 11], [20, 11], [20, 13], [4, 13]], C],
|
|
520
|
+
minus: [[[4, 11], [20, 11], [20, 13], [4, 13]], [[4, 11], [20, 11], [20, 13], [4, 13]], C],
|
|
521
|
+
check: [[[4.3, 12.7], [5.7, 11.3], [10.4, 16], [9, 17.4]], [[9, 17.4], [7.6, 16], [18.3, 5.3], [19.7, 6.7]], C],
|
|
522
|
+
'arrow-right': [[[4, 11], [17, 11], [17, 13], [4, 13]], [[12.6, 6.4], [14, 5], [21, 12], [19.6, 13.4]], [[19.6, 10.6], [21, 12], [14, 19], [12.6, 17.6]]],
|
|
523
|
+
};
|
|
524
|
+
/** SVG path data for an icon, or for the interpolation `t` (0–1) between two. */
|
|
525
|
+
function morphPath(from, to = from, t = 0) {
|
|
526
|
+
const a = MORPH_ICONS[from] || MORPH_ICONS.menu;
|
|
527
|
+
const b = MORPH_ICONS[to] || a;
|
|
528
|
+
return a
|
|
529
|
+
.map((q, i) => {
|
|
530
|
+
const pts = q.map((p, j) => {
|
|
531
|
+
const r = b[i][j];
|
|
532
|
+
return `${(p[0] + (r[0] - p[0]) * t).toFixed(2)} ${(p[1] + (r[1] - p[1]) * t).toFixed(2)}`;
|
|
533
|
+
});
|
|
534
|
+
return `M${pts.join('L')}Z`;
|
|
535
|
+
})
|
|
536
|
+
.join('');
|
|
537
|
+
}
|
|
538
|
+
function defineIconMorph(tag = 'usa-icon-morph') {
|
|
539
|
+
return defineElement(tag, (Base) => class UsaIconMorph extends Base {
|
|
540
|
+
constructor() {
|
|
541
|
+
super(...arguments);
|
|
542
|
+
this._i = 0;
|
|
543
|
+
this._from = 'play';
|
|
544
|
+
this._to = 'play';
|
|
545
|
+
}
|
|
546
|
+
static get observedAttributes() {
|
|
547
|
+
return ['icons', 'size', 'toggle'];
|
|
548
|
+
}
|
|
549
|
+
list() {
|
|
550
|
+
return this.str('icons', 'play,pause').split(',').map((s) => s.trim()).filter((s) => MORPH_ICONS[s]);
|
|
551
|
+
}
|
|
552
|
+
get index() {
|
|
553
|
+
return this._i;
|
|
554
|
+
}
|
|
555
|
+
set index(v) {
|
|
556
|
+
this.show(v);
|
|
557
|
+
}
|
|
558
|
+
get icon() {
|
|
559
|
+
return this.list()[this._i] || 'play';
|
|
560
|
+
}
|
|
561
|
+
draw(t) {
|
|
562
|
+
this.querySelector('path')?.setAttribute('d', morphPath(this._from, this._to, t));
|
|
563
|
+
}
|
|
564
|
+
mount() {
|
|
565
|
+
const size = this.num('size', 24);
|
|
566
|
+
this.innerHTML = `<svg viewBox="0 0 24 24" width="${size}" height="${size}" aria-hidden="true" focusable="false"><path/></svg>`;
|
|
567
|
+
this._i = Math.max(0, Math.min(this.list().length - 1, this.num('index', 0)));
|
|
568
|
+
this._from = this._to = this.icon;
|
|
569
|
+
this._t = createSpring({ value: 1, spring: this.str('preset', 'wobbly'), onUpdate: (v) => this.draw(v) });
|
|
570
|
+
this.draw(1);
|
|
571
|
+
if (this.flag('toggle')) {
|
|
572
|
+
this.setAttribute('role', 'button');
|
|
573
|
+
if (!this.hasAttribute('tabindex'))
|
|
574
|
+
this.tabIndex = 0;
|
|
575
|
+
this.listen(this, 'click', () => this.next());
|
|
576
|
+
this.listen(this, 'keydown', (e) => {
|
|
577
|
+
if ((e.key === 'Enter' || e.key === ' ') && !e.repeat) {
|
|
578
|
+
e.preventDefault();
|
|
579
|
+
this.next();
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
this.label();
|
|
584
|
+
}
|
|
585
|
+
unmount() {
|
|
586
|
+
this._t?.stop();
|
|
587
|
+
}
|
|
588
|
+
label() {
|
|
589
|
+
const labels = this.str('labels').split(',').map((s) => s.trim());
|
|
590
|
+
const l = labels[this._i];
|
|
591
|
+
if (l)
|
|
592
|
+
this.setAttribute('aria-label', l);
|
|
593
|
+
if (!this.flag('toggle') && !l)
|
|
594
|
+
this.setAttribute('aria-hidden', 'true');
|
|
595
|
+
}
|
|
596
|
+
show(icon) {
|
|
597
|
+
const list = this.list();
|
|
598
|
+
const i = typeof icon === 'number' ? ((icon % list.length) + list.length) % list.length : list.indexOf(icon);
|
|
599
|
+
if (i < 0 || !this._t)
|
|
600
|
+
return;
|
|
601
|
+
const name = list[i];
|
|
602
|
+
// morph from wherever we are now (interruptible)
|
|
603
|
+
const cur = this._t.value;
|
|
604
|
+
this._from = cur >= 0.5 ? this._to : this._from;
|
|
605
|
+
this._to = name;
|
|
606
|
+
this._i = i;
|
|
607
|
+
this._t.jump(0);
|
|
608
|
+
this._t.set(1);
|
|
609
|
+
this.label();
|
|
610
|
+
this.emit('change', { index: i, icon: name });
|
|
611
|
+
}
|
|
612
|
+
next() {
|
|
613
|
+
this.show(this._i + 1);
|
|
614
|
+
}
|
|
615
|
+
}, { id: 'icon-morph', text: css$4 });
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
var css$3 = "usa-like{--usa-like-color:#f43f5e;display:inline-flex;align-items:center;gap:0.35em;cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle}usa-like .usa-like-heart{fill:transparent;stroke:currentColor;stroke-width:1.8;transition:fill 0.2s ease,stroke 0.2s ease;overflow:visible}usa-like[liked] .usa-like-heart{fill:var(--usa-like-color);stroke:var(--usa-like-color)}usa-like .usa-like-count{font-variant-numeric:tabular-nums}usa-like[disabled]{opacity:0.5;cursor:not-allowed}usa-like:focus-visible{outline:2px solid var(--usa-like-color);outline-offset:3px;border-radius:8px}";
|
|
619
|
+
|
|
620
|
+
const HEART = 'M12 21s-7.5-4.6-9.6-9.2C.9 8.4 3 4.5 6.7 4.5c2.1 0 3.6 1.2 5.3 3.2 1.7-2 3.2-3.2 5.3-3.2 3.7 0 5.8 3.9 4.3 7.3C19.5 16.4 12 21 12 21z';
|
|
621
|
+
function defineLike(tag = 'usa-like') {
|
|
622
|
+
return defineElement(tag, (Base) => class UsaLike extends Base {
|
|
623
|
+
static get observedAttributes() {
|
|
624
|
+
return ['liked', 'count', 'label', 'disabled'];
|
|
625
|
+
}
|
|
626
|
+
get liked() {
|
|
627
|
+
return this.flag('liked');
|
|
628
|
+
}
|
|
629
|
+
set liked(v) {
|
|
630
|
+
this.setFlag('liked', v);
|
|
631
|
+
}
|
|
632
|
+
get count() {
|
|
633
|
+
return this.hasAttribute('count') ? this.num('count', 0) : null;
|
|
634
|
+
}
|
|
635
|
+
set count(v) {
|
|
636
|
+
if (v === null)
|
|
637
|
+
this.removeAttribute('count');
|
|
638
|
+
else
|
|
639
|
+
this.setAttribute('count', String(v));
|
|
640
|
+
}
|
|
641
|
+
mount() {
|
|
642
|
+
if (!this.querySelector(':scope > .usa-like-heart')) {
|
|
643
|
+
const size = this.num('size', 24);
|
|
644
|
+
this.insertAdjacentHTML('afterbegin', `<svg class="usa-like-heart" viewBox="0 0 24 24" width="${size}" height="${size}" aria-hidden="true"><path d="${HEART}"/></svg><span class="usa-like-count" aria-hidden="true"></span>`);
|
|
645
|
+
}
|
|
646
|
+
if (this.str('color'))
|
|
647
|
+
this.style.setProperty('--usa-like-color', this.str('color'));
|
|
648
|
+
this.setAttribute('role', 'button');
|
|
649
|
+
if (!this.hasAttribute('tabindex'))
|
|
650
|
+
this.tabIndex = 0;
|
|
651
|
+
this.sync();
|
|
652
|
+
this.listen(this, 'click', () => this.toggle());
|
|
653
|
+
this.listen(this, 'keydown', (e) => {
|
|
654
|
+
if ((e.key === 'Enter' || e.key === ' ') && !e.repeat) {
|
|
655
|
+
e.preventDefault();
|
|
656
|
+
this.toggle();
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
changed() {
|
|
661
|
+
this.sync();
|
|
662
|
+
}
|
|
663
|
+
sync() {
|
|
664
|
+
const c = this.count;
|
|
665
|
+
const label = this.str('label', 'Like');
|
|
666
|
+
this.setAttribute('aria-pressed', String(this.liked));
|
|
667
|
+
this.setAttribute('aria-label', c === null ? label : `${label} (${c})`);
|
|
668
|
+
this.toggleAttribute('aria-disabled', this.flag('disabled'));
|
|
669
|
+
const out = this.querySelector('.usa-like-count');
|
|
670
|
+
if (out)
|
|
671
|
+
out.textContent = c === null ? '' : new Intl.NumberFormat().format(c);
|
|
672
|
+
}
|
|
673
|
+
toggle(force) {
|
|
674
|
+
if (this.flag('disabled'))
|
|
675
|
+
return;
|
|
676
|
+
const next = force === undefined ? !this.liked : force;
|
|
677
|
+
if (next === this.liked)
|
|
678
|
+
return;
|
|
679
|
+
this.liked = next;
|
|
680
|
+
if (this.count !== null)
|
|
681
|
+
this.count = Math.max(0, this.count + (next ? 1 : -1));
|
|
682
|
+
this.sync();
|
|
683
|
+
const heart = this.querySelector('.usa-like-heart');
|
|
684
|
+
if (next && heart && !this.reduced) {
|
|
685
|
+
this.motion(heart, [{ transform: 'scale(0.4)' }, { transform: 'scale(1)' }], springEasing('bouncy'));
|
|
686
|
+
const r = heart.getBoundingClientRect();
|
|
687
|
+
burst(r.left + r.width / 2, r.top + r.height / 2, { count: 10, distance: 28, size: 5, colors: [getComputedStyle(this).getPropertyValue('--usa-like-color').trim() || '#f43f5e', '#fb923c', '#facc15'] });
|
|
688
|
+
}
|
|
689
|
+
if (this.hasAttribute('haptic'))
|
|
690
|
+
haptic(this.num('haptic', 12));
|
|
691
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
692
|
+
this.emit('change', { liked: next, count: this.count });
|
|
693
|
+
}
|
|
694
|
+
}, { id: 'like', text: css$3 });
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
var css$2 = "usa-hold{--usa-hold-color:#e5484d;position:relative;display:inline-flex;align-items:center;gap:0.5em;cursor:pointer;user-select:none;-webkit-user-select:none;touch-action:manipulation;-webkit-tap-highlight-color:transparent}usa-hold .usa-hold-ring{width:1.6em;height:1.6em;transform:rotate(-90deg);flex:none}usa-hold .usa-hold-ring circle{fill:none;stroke-width:3.5;stroke-linecap:round}usa-hold .usa-hold-ring circle:first-child{stroke:color-mix(in srgb,currentColor 18%,transparent)}usa-hold .usa-hold-ring circle:last-child{stroke:var(--usa-hold-color);stroke-dasharray:100;stroke-dashoffset:100}usa-hold[data-holding]{transform:scale(calc(1 - var(--usa-hold,0) * 0.05))}usa-hold[data-done] .usa-hold-ring{animation:usa-hold-pop 0.4s cubic-bezier(0.34,1.56,0.64,1)}@keyframes usa-hold-pop{50%{transform:rotate(-90deg) scale(1.3)}}usa-hold[disabled]{opacity:0.5;cursor:not-allowed}usa-hold:focus-visible{outline:2px solid var(--usa-hold-color);outline-offset:3px;border-radius:10px}@media (prefers-reduced-motion:reduce){usa-hold[data-holding]{transform:none}usa-hold[data-done] .usa-hold-ring{animation:none}}";
|
|
698
|
+
|
|
699
|
+
function defineHold(tag = 'usa-hold') {
|
|
700
|
+
return defineElement(tag, (Base) => class UsaHold extends Base {
|
|
701
|
+
constructor() {
|
|
702
|
+
super(...arguments);
|
|
703
|
+
this._p = 0;
|
|
704
|
+
this._frame = 0;
|
|
705
|
+
this._holding = false;
|
|
706
|
+
}
|
|
707
|
+
static get observedAttributes() {
|
|
708
|
+
return ['duration', 'disabled'];
|
|
709
|
+
}
|
|
710
|
+
get progress() {
|
|
711
|
+
return this._p;
|
|
712
|
+
}
|
|
713
|
+
set(p) {
|
|
714
|
+
this._p = p;
|
|
715
|
+
this.style.setProperty('--usa-hold', p.toFixed(4));
|
|
716
|
+
const c = this.querySelector('.usa-hold-ring circle:last-child');
|
|
717
|
+
if (c)
|
|
718
|
+
c.style.strokeDashoffset = String(100 - p * 100);
|
|
719
|
+
}
|
|
720
|
+
mount() {
|
|
721
|
+
if (!this.querySelector(':scope > .usa-hold-ring')) {
|
|
722
|
+
this.insertAdjacentHTML('beforeend', '<svg class="usa-hold-ring" viewBox="0 0 36 36" aria-hidden="true"><circle cx="18" cy="18" r="15.9" pathLength="100"/><circle cx="18" cy="18" r="15.9" pathLength="100"/></svg>');
|
|
723
|
+
}
|
|
724
|
+
if (this.str('color'))
|
|
725
|
+
this.style.setProperty('--usa-hold-color', this.str('color'));
|
|
726
|
+
this.setAttribute('role', 'button');
|
|
727
|
+
if (!this.hasAttribute('tabindex'))
|
|
728
|
+
this.tabIndex = 0;
|
|
729
|
+
if (this.str('label'))
|
|
730
|
+
this.setAttribute('aria-label', this.str('label'));
|
|
731
|
+
this.setAttribute('aria-description', 'Press and hold to confirm');
|
|
732
|
+
this.set(0);
|
|
733
|
+
this.listen(this, 'pointerdown', (e) => (e.pointerType !== 'mouse' || e.button === 0) && this.start());
|
|
734
|
+
for (const t of ['pointerup', 'pointerleave', 'pointercancel'])
|
|
735
|
+
this.listen(this, t, () => this.stop());
|
|
736
|
+
this.listen(this, 'keydown', (e) => {
|
|
737
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
738
|
+
e.preventDefault();
|
|
739
|
+
if (!e.repeat)
|
|
740
|
+
this.start();
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
this.listen(this, 'keyup', (e) => (e.key === 'Enter' || e.key === ' ') && this.stop());
|
|
744
|
+
this.listen(this, 'contextmenu', (e) => this._holding && e.preventDefault());
|
|
745
|
+
}
|
|
746
|
+
unmount() {
|
|
747
|
+
caf(this._frame);
|
|
748
|
+
this._holding = false;
|
|
749
|
+
}
|
|
750
|
+
start() {
|
|
751
|
+
if (this.flag('disabled') || this._holding)
|
|
752
|
+
return;
|
|
753
|
+
this._holding = true;
|
|
754
|
+
this.setAttribute('data-holding', '');
|
|
755
|
+
const dur = Math.max(100, this.num('duration', 1200));
|
|
756
|
+
let last = now();
|
|
757
|
+
const tick = () => {
|
|
758
|
+
const t = now();
|
|
759
|
+
const p = Math.min(1, this._p + (t - last) / dur);
|
|
760
|
+
last = t;
|
|
761
|
+
this.set(p);
|
|
762
|
+
this.emit('progress', { progress: p });
|
|
763
|
+
if (p >= 1) {
|
|
764
|
+
this._holding = false;
|
|
765
|
+
this.removeAttribute('data-holding');
|
|
766
|
+
this.setAttribute('data-done', '');
|
|
767
|
+
if (this.hasAttribute('haptic'))
|
|
768
|
+
haptic(20);
|
|
769
|
+
this.emit('confirm');
|
|
770
|
+
setTimeout(() => {
|
|
771
|
+
this.removeAttribute('data-done');
|
|
772
|
+
this.rewind();
|
|
773
|
+
}, 700);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
this._frame = raf(tick);
|
|
777
|
+
};
|
|
778
|
+
this._frame = raf(tick);
|
|
779
|
+
}
|
|
780
|
+
stop() {
|
|
781
|
+
if (!this._holding)
|
|
782
|
+
return;
|
|
783
|
+
this._holding = false;
|
|
784
|
+
caf(this._frame);
|
|
785
|
+
this.removeAttribute('data-holding');
|
|
786
|
+
if (this._p < 1) {
|
|
787
|
+
this.emit('cancel', { progress: this._p });
|
|
788
|
+
this.rewind();
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
rewind() {
|
|
792
|
+
let last = now();
|
|
793
|
+
const back = () => {
|
|
794
|
+
if (this._holding)
|
|
795
|
+
return;
|
|
796
|
+
const t = now();
|
|
797
|
+
const p = Math.max(0, this._p - (t - last) / 250);
|
|
798
|
+
last = t;
|
|
799
|
+
this.set(p);
|
|
800
|
+
if (p > 0)
|
|
801
|
+
this._frame = raf(back);
|
|
802
|
+
};
|
|
803
|
+
this._frame = raf(back);
|
|
804
|
+
}
|
|
805
|
+
cancel() {
|
|
806
|
+
this.stop();
|
|
807
|
+
}
|
|
808
|
+
}, { id: 'hold', text: css$2 });
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
var css$1 = "usa-double-tap{position:relative;display:block;touch-action:manipulation;-webkit-tap-highlight-color:transparent;user-select:none;-webkit-user-select:none}usa-double-tap .usa-double-tap-icon{position:absolute;pointer-events:none;font-size:64px;line-height:1;color:#f43f5e;transform:translate(-50%,-50%);text-shadow:0 6px 18px rgb(0 0 0 / 0.35);z-index:2}";
|
|
812
|
+
|
|
813
|
+
function defineDoubleTap(tag = 'usa-double-tap') {
|
|
814
|
+
return defineElement(tag, (Base) => class UsaDoubleTap extends Base {
|
|
815
|
+
mount() {
|
|
816
|
+
let last = 0;
|
|
817
|
+
let lx = 0;
|
|
818
|
+
let ly = 0;
|
|
819
|
+
this.listen(this, 'pointerup', (e) => {
|
|
820
|
+
if (this.flag('disabled'))
|
|
821
|
+
return;
|
|
822
|
+
const t = e.timeStamp || Date.now();
|
|
823
|
+
if (t - last < this.num('delay', 300) && Math.hypot(e.clientX - lx, e.clientY - ly) < 40) {
|
|
824
|
+
last = 0;
|
|
825
|
+
this.pop(e.clientX, e.clientY);
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
last = t;
|
|
829
|
+
lx = e.clientX;
|
|
830
|
+
ly = e.clientY;
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
this.listen(this, 'dblclick', (e) => e.preventDefault());
|
|
834
|
+
this.listen(this, 'keydown', (e) => e.key.toLowerCase() === 'l' && !e.repeat && this.pop());
|
|
835
|
+
}
|
|
836
|
+
pop(x, y) {
|
|
837
|
+
const r = this.getBoundingClientRect();
|
|
838
|
+
if (x === undefined || y === undefined) {
|
|
839
|
+
x = r.left + r.width / 2;
|
|
840
|
+
y = r.top + r.height / 2;
|
|
841
|
+
}
|
|
842
|
+
const icon = document.createElement('span');
|
|
843
|
+
icon.className = 'usa-double-tap-icon';
|
|
844
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
845
|
+
icon.textContent = this.str('icon', '♥');
|
|
846
|
+
icon.style.left = `${x - r.left}px`;
|
|
847
|
+
icon.style.top = `${y - r.top}px`;
|
|
848
|
+
if (this.str('color'))
|
|
849
|
+
icon.style.color = this.str('color');
|
|
850
|
+
this.append(icon);
|
|
851
|
+
const frames = this.reduced
|
|
852
|
+
? [{ opacity: 0 }, { opacity: 1, offset: 0.3 }, { opacity: 0 }]
|
|
853
|
+
: [
|
|
854
|
+
{ opacity: 0, transform: 'translate(-50%, -50%) scale(0.2) rotate(-15deg)' },
|
|
855
|
+
{ opacity: 1, transform: 'translate(-50%, -50%) scale(1.15) rotate(5deg)', offset: 0.3 },
|
|
856
|
+
{ opacity: 1, transform: 'translate(-50%, -50%) scale(1) rotate(0deg)', offset: 0.6 },
|
|
857
|
+
{ opacity: 0, transform: 'translate(-50%, -110%) scale(0.9)' },
|
|
858
|
+
];
|
|
859
|
+
const a = this.motion(icon, frames, { duration: 900, easing: 'ease-out', fill: 'forwards' });
|
|
860
|
+
if (a)
|
|
861
|
+
a.onfinish = () => icon.remove();
|
|
862
|
+
else
|
|
863
|
+
icon.remove();
|
|
864
|
+
burst(x, y, { count: 8, distance: 40, size: 5, colors: [this.str('color', '#f43f5e'), '#fb923c', '#facc15'] });
|
|
865
|
+
if (this.hasAttribute('haptic'))
|
|
866
|
+
haptic(15);
|
|
867
|
+
this.emit('double-tap', { x: x - r.left, y: y - r.top });
|
|
868
|
+
}
|
|
869
|
+
}, { id: 'double-tap', text: css$1 });
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
var css = "usa-checkbox{--usa-checkbox-on:var(--usa-accent,#7c5cff);display:inline-flex;align-items:center;gap:0.55em;cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle}usa-checkbox .usa-checkbox-box{flex:none;width:1.25em;height:1.25em;border-radius:0.3em;border:1.5px solid color-mix(in srgb,currentColor 55%,transparent);display:grid;place-items:center;transition:background-color 0.2s ease,border-color 0.2s ease;box-sizing:border-box}usa-checkbox[shape=\"circle\"] .usa-checkbox-box{border-radius:50%}usa-checkbox svg{width:85%;height:85%;fill:none;stroke:#fff;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}usa-checkbox svg path{stroke-dasharray:1;stroke-dashoffset:1;transition:stroke-dashoffset 0.28s cubic-bezier(0.65,0,0.35,1) 0.05s}usa-checkbox[checked]:not([indeterminate]) .usa-checkbox-check,usa-checkbox[indeterminate] .usa-checkbox-dash{stroke-dashoffset:0}usa-checkbox[checked] .usa-checkbox-box,usa-checkbox[indeterminate] .usa-checkbox-box{background:var(--usa-checkbox-on);border-color:var(--usa-checkbox-on)}usa-checkbox[disabled]{opacity:0.45;cursor:not-allowed}usa-checkbox:focus-visible{outline:none}usa-checkbox:focus-visible .usa-checkbox-box{outline:2px solid var(--usa-checkbox-on);outline-offset:2px}@media (prefers-reduced-motion:reduce){usa-checkbox svg path,usa-checkbox .usa-checkbox-box{transition:none}}";
|
|
873
|
+
|
|
874
|
+
function defineCheckbox(tag = 'usa-checkbox') {
|
|
875
|
+
return defineElement(tag, (Base) => {
|
|
876
|
+
class UsaCheckbox extends Base {
|
|
877
|
+
static get observedAttributes() {
|
|
878
|
+
return ['checked', 'indeterminate', 'disabled', 'label'];
|
|
879
|
+
}
|
|
880
|
+
constructor() {
|
|
881
|
+
super();
|
|
882
|
+
this._internals = null;
|
|
883
|
+
try {
|
|
884
|
+
this._internals = this.attachInternals?.() ?? null;
|
|
885
|
+
}
|
|
886
|
+
catch {
|
|
887
|
+
this._internals = null;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
get checked() {
|
|
891
|
+
return this.flag('checked');
|
|
892
|
+
}
|
|
893
|
+
set checked(v) {
|
|
894
|
+
this.setFlag('checked', v);
|
|
895
|
+
}
|
|
896
|
+
get indeterminate() {
|
|
897
|
+
return this.flag('indeterminate');
|
|
898
|
+
}
|
|
899
|
+
set indeterminate(v) {
|
|
900
|
+
this.setFlag('indeterminate', v);
|
|
901
|
+
}
|
|
902
|
+
mount() {
|
|
903
|
+
if (!this.querySelector(':scope > .usa-checkbox-box')) {
|
|
904
|
+
this.insertAdjacentHTML('afterbegin', '<span class="usa-checkbox-box" aria-hidden="true"><svg viewBox="0 0 24 24"><path class="usa-checkbox-check" d="M5 12.5l4.2 4.2L19 7" pathLength="1"/><path class="usa-checkbox-dash" d="M6 12h12" pathLength="1"/></svg></span>');
|
|
905
|
+
}
|
|
906
|
+
this.setAttribute('role', 'checkbox');
|
|
907
|
+
if (!this.hasAttribute('tabindex'))
|
|
908
|
+
this.tabIndex = 0;
|
|
909
|
+
this.sync();
|
|
910
|
+
this.listen(this, 'click', () => this.toggle());
|
|
911
|
+
this.listen(this, 'keydown', (e) => {
|
|
912
|
+
if (e.key === ' ' && !e.repeat) {
|
|
913
|
+
e.preventDefault();
|
|
914
|
+
this.toggle();
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
changed() {
|
|
919
|
+
this.sync();
|
|
920
|
+
}
|
|
921
|
+
sync() {
|
|
922
|
+
this.setAttribute('aria-checked', this.indeterminate ? 'mixed' : String(this.checked));
|
|
923
|
+
if (this.str('label'))
|
|
924
|
+
this.setAttribute('aria-label', this.str('label'));
|
|
925
|
+
this.toggleAttribute('aria-disabled', this.flag('disabled'));
|
|
926
|
+
this._internals?.setFormValue?.(this.checked ? this.str('value', 'on') : null);
|
|
927
|
+
}
|
|
928
|
+
toggle(force) {
|
|
929
|
+
if (this.flag('disabled'))
|
|
930
|
+
return;
|
|
931
|
+
const next = force === undefined ? !this.checked || this.indeterminate : force;
|
|
932
|
+
this.indeterminate = false;
|
|
933
|
+
this.checked = next;
|
|
934
|
+
this.sync();
|
|
935
|
+
const box = this.querySelector('.usa-checkbox-box');
|
|
936
|
+
if (box && !this.reduced)
|
|
937
|
+
this.motion(box, [{ transform: 'scale(0.75)' }, { transform: 'scale(1)' }], springEasing('bouncy'));
|
|
938
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
939
|
+
this.emit('change', { checked: next });
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
UsaCheckbox.formAssociated = true;
|
|
943
|
+
return UsaCheckbox;
|
|
944
|
+
}, { id: 'checkbox', text: css });
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* use-scroll-animate/components/click — click & tap effects (v2.5).
|
|
949
|
+
* `<usa-click>` (ripple, burst, confetti, squish, press-spring, shake),
|
|
950
|
+
* `<usa-button>` (button click deformation: squash, wobble, gooey, dent;
|
|
951
|
+
* shape morph; submit → loading → success), `<usa-icon-morph>`,
|
|
952
|
+
* `<usa-like>`, `<usa-hold>`, `<usa-double-tap>`, `<usa-checkbox>`, plus
|
|
953
|
+
* `burst()`, `confetti()`, `shake()` and `haptic()`.
|
|
954
|
+
*/
|
|
955
|
+
/** Register every component of this category under its default tag. */
|
|
956
|
+
function defineClickComponents() {
|
|
957
|
+
defineClick();
|
|
958
|
+
defineButton();
|
|
959
|
+
defineIconMorph();
|
|
960
|
+
defineLike();
|
|
961
|
+
defineHold();
|
|
962
|
+
defineDoubleTap();
|
|
963
|
+
defineCheckbox();
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
export { BUTTON_DEFORMS, CLICK_EFFECTS, MORPH_ICONS, burst, confetti, defineButton, defineCheckbox, defineClick, defineClickComponents, defineDoubleTap, defineHold, defineIconMorph, defineLike, haptic, morphPath, prefersReducedMotion, shake };
|
|
967
|
+
//# sourceMappingURL=click.js.map
|