@haiyue/ui 0.2.1 → 0.2.2

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.
@@ -0,0 +1,509 @@
1
+ const properties = ['--_hy-carousel-transform', '--_hy-carousel-visibility'];
2
+ const attributes = ['role', 'aria-roledescription', 'aria-label', 'aria-hidden', 'inert'];
3
+ function numeric(value, fallback, min, max) {
4
+ if (value === null || value === '')
5
+ return fallback;
6
+ const number = Number(value);
7
+ return Number.isFinite(number) ? Math.min(max, Math.max(min, number)) : fallback;
8
+ }
9
+ /** A slotted, accessible carousel. Panels stay mounted and inactive panels are inert. */
10
+ export class HYCarousel extends HTMLElement {
11
+ _viewport = document.createElement('div');
12
+ _slot = document.createElement('slot');
13
+ _indicators = document.createElement('div');
14
+ _previous = document.createElement('button');
15
+ _next = document.createElement('button');
16
+ _playback = document.createElement('button');
17
+ _motion = window.matchMedia('(prefers-reduced-motion: reduce)');
18
+ _saved = new Map();
19
+ _animations = new Set();
20
+ _hostAttributes = new Map();
21
+ _slides = [];
22
+ _index = 0;
23
+ _reflecting = false;
24
+ _connected = false;
25
+ _timer = 0;
26
+ _paused = false;
27
+ _hovered = false;
28
+ _motionOverride = false;
29
+ _drag = null;
30
+ _blockClickUntil = 0;
31
+ _playbackPointerAction = null;
32
+ _indicatorRenderer = null;
33
+ static get observedAttributes() {
34
+ return ['index', 'direction', 'indicator-position', 'indicator-style', 'show-indicators', 'show-arrows', 'autoplay', 'interval', 'draggable', 'loop'];
35
+ }
36
+ constructor() {
37
+ super();
38
+ const root = this.attachShadow({ mode: 'open' });
39
+ const style = document.createElement('style');
40
+ style.textContent = `
41
+ :host { display: block; position: relative; box-sizing: border-box; width: 100%; height: var(--hy-carousel-height, 240px); min-width: 0; min-height: 0; color: var(--hy-text-color, #eef0ff); font: inherit; }
42
+ :host([hidden]), [hidden] { display: none !important; }
43
+ .viewport { position: relative; width: 100%; height: 100%; overflow: hidden; border-radius: var(--hy-carousel-radius, 10px); background: var(--hy-carousel-bg-color, var(--hy-surface-color, #10132f)); touch-action: pan-y; }
44
+ :host([direction="vertical"]) .viewport { touch-action: pan-x; }
45
+ :host([draggable="false"]) .viewport { touch-action: auto; }
46
+ slot { display: contents; }
47
+ ::slotted(*) { position: absolute; box-sizing: border-box; inset: 0; width: 100%; height: 100%; margin: 0; overflow: auto; touch-action: pan-y; transform: var(--_hy-carousel-transform, translate3d(0,0,0)); visibility: var(--_hy-carousel-visibility, hidden); }
48
+ :host([direction="vertical"]) ::slotted(*) { touch-action: pan-x; }
49
+ :host([draggable="false"]) ::slotted(*) { touch-action: auto; }
50
+ button { box-sizing: border-box; color: inherit; font: inherit; cursor: pointer; border: 0; }
51
+ button:disabled { opacity: .35; cursor: default; }
52
+ button:focus-visible, .viewport:focus-visible { outline: 2px solid var(--hy-focus-border-color, #9c8fff); outline-offset: -3px; }
53
+ .arrow, .playback { position: absolute; z-index: 2; display: grid; place-items: center; width: 32px; height: 32px; border-radius: var(--hy-carousel-control-radius, 50%); background: var(--hy-carousel-control-bg-color, var(--hy-surface-elevated-color, #181a40)); color: var(--hy-carousel-control-color, var(--hy-text-color, #eef0ff)); }
54
+ .arrow:hover, .playback:hover { background: var(--hy-hover-bg-color, #242752); }
55
+ .previous { left: 12px; top: calc(50% - 16px); }
56
+ .next { right: 12px; top: calc(50% - 16px); }
57
+ :host(:dir(rtl)) .previous { left: auto; right: 12px; }
58
+ :host(:dir(rtl)) .next { right: auto; left: 12px; }
59
+ :host([direction="vertical"]) .previous { left: calc(50% - 16px); right: auto; top: 12px; }
60
+ :host([direction="vertical"]) .next { left: calc(50% - 16px); right: auto; top: auto; bottom: 12px; }
61
+ :host(:not([direction="vertical"])[indicator-position="left"]) .arrow,
62
+ :host(:not([direction="vertical"])[indicator-position="right"]) .arrow { top: auto; bottom: 12px; }
63
+ :host([direction="vertical"]:not([indicator-position="left"]):not([indicator-position="right"])) .arrow { left: auto; right: auto; inset-inline-start: 12px; }
64
+ .playback { top: 12px; inset-inline-end: 12px; font-size: 12px; }
65
+ .indicators { position: absolute; z-index: 2; display: flex; align-items: center; max-width: calc(100% - 88px); overflow: auto; gap: var(--hy-carousel-indicator-gap, 4px); bottom: var(--hy-carousel-indicator-offset, 12px); left: 50%; transform: translateX(-50%); }
66
+ :host([indicator-position="top"]) .indicators { top: var(--hy-carousel-indicator-offset, 12px); bottom: auto; }
67
+ :host([indicator-position="left"]) .indicators, :host([indicator-position="right"]) .indicators { flex-direction: column; max-height: calc(100% - 88px); top: 50%; bottom: auto; transform: translateY(-50%); }
68
+ :host([indicator-position="left"]) .indicators { left: var(--hy-carousel-indicator-offset, 12px); }
69
+ :host([indicator-position="right"]) .indicators { left: auto; right: var(--hy-carousel-indicator-offset, 12px); }
70
+ .indicator { display: grid; flex: 0 0 auto; place-items: center; min-width: 24px; min-height: 24px; padding: 4px; background: transparent; border-radius: 4px; }
71
+ .indicator-content { display: grid; place-items: center; width: var(--hy-carousel-indicator-size, 8px); height: var(--hy-carousel-indicator-size, 8px); border-radius: var(--hy-carousel-indicator-radius, 50%); background: var(--hy-carousel-indicator-color, var(--hy-muted-color, #747ba8)); }
72
+ .indicator[aria-current="true"] .indicator-content { background: var(--hy-carousel-indicator-active-color, var(--hy-accent-color, #8177f2)); }
73
+ :host([indicator-style="line"]) .indicator-content { width: var(--hy-carousel-indicator-width, 24px); height: var(--hy-carousel-indicator-thickness, 3px); border-radius: var(--hy-carousel-indicator-radius, 2px); }
74
+ :host([indicator-style="number"]) .indicator-content { width: 24px; height: 24px; font-size: 12px; color: var(--hy-carousel-indicator-text-color, var(--hy-text-color, #eef0ff)); }
75
+ .indicators.custom .indicator-content { width: auto; height: auto; background: transparent; }
76
+ `;
77
+ this._viewport.className = 'viewport';
78
+ this._viewport.part.add('viewport');
79
+ this._viewport.tabIndex = 0;
80
+ this._viewport.append(this._slot);
81
+ this._indicators.className = 'indicators';
82
+ this._indicators.part.add('indicators');
83
+ this._indicators.setAttribute('role', 'group');
84
+ this._indicators.setAttribute('aria-label', 'Choose slide');
85
+ for (const [button, name] of [[this._previous, 'previous'], [this._next, 'next'], [this._playback, 'playback']]) {
86
+ button.type = 'button';
87
+ button.className = name === 'playback' ? name : `arrow ${name}`;
88
+ button.part.add(name);
89
+ }
90
+ this._previous.setAttribute('aria-label', 'Previous slide');
91
+ this._next.setAttribute('aria-label', 'Next slide');
92
+ this._previous.addEventListener('click', () => this._select(this._index - 1, 'arrow'));
93
+ this._next.addEventListener('click', () => this._select(this._index + 1, 'arrow'));
94
+ // Pointer focus pauses rotation before click fires. Preserve the action
95
+ // the user saw on pointerdown so the first click still pauses playback.
96
+ this._playback.addEventListener('pointerdown', () => { this._playbackPointerAction = !this._paused && !(this._motion.matches && !this._motionOverride); });
97
+ this._playback.addEventListener('click', event => {
98
+ const pause = event.detail > 0 && this._playbackPointerAction !== null ? this._playbackPointerAction : !this._paused && !(this._motion.matches && !this._motionOverride);
99
+ this._playbackPointerAction = null;
100
+ if (pause)
101
+ this.pause();
102
+ else
103
+ this.play();
104
+ });
105
+ this._indicators.addEventListener('click', event => {
106
+ const button = event.target.closest('button[data-index]');
107
+ if (button)
108
+ this._select(Number(button.dataset.index), 'indicator');
109
+ });
110
+ root.append(style, this._viewport, this._previous, this._next, this._indicators, this._playback);
111
+ }
112
+ connectedCallback() {
113
+ if (this._connected)
114
+ return;
115
+ this._connected = true;
116
+ for (const [name, value] of [['role', 'region'], ['aria-roledescription', 'carousel'], ['aria-label', 'Carousel']]) {
117
+ this._hostAttributes.set(name, this.getAttribute(name));
118
+ if (!this.hasAttribute(name))
119
+ this.setAttribute(name, value);
120
+ }
121
+ this._slot.addEventListener('slotchange', this._syncSlides);
122
+ this.addEventListener('keydown', this._onKeyDown);
123
+ this.addEventListener('pointerenter', this._onEnter);
124
+ this.addEventListener('pointerleave', this._onLeave);
125
+ this.addEventListener('focusin', this._onFocus);
126
+ this._viewport.addEventListener('pointerdown', this._onPointerDown);
127
+ this._viewport.addEventListener('lostpointercapture', this._onPointerCancel);
128
+ this._viewport.addEventListener('dragstart', this._onNativeDrag);
129
+ this._viewport.addEventListener('click', this._onClick, true);
130
+ document.addEventListener('visibilitychange', this._schedule);
131
+ this._motion.addEventListener('change', this._onMotionChange);
132
+ this._syncSlides();
133
+ }
134
+ disconnectedCallback() {
135
+ this._connected = false;
136
+ this._clearTimer();
137
+ this._cancelDrag();
138
+ this._cancelAnimations();
139
+ this._hovered = false;
140
+ this._playbackPointerAction = null;
141
+ this._slot.removeEventListener('slotchange', this._syncSlides);
142
+ this.removeEventListener('keydown', this._onKeyDown);
143
+ this.removeEventListener('pointerenter', this._onEnter);
144
+ this.removeEventListener('pointerleave', this._onLeave);
145
+ this.removeEventListener('focusin', this._onFocus);
146
+ this._viewport.removeEventListener('pointerdown', this._onPointerDown);
147
+ this._viewport.removeEventListener('lostpointercapture', this._onPointerCancel);
148
+ this._viewport.removeEventListener('dragstart', this._onNativeDrag);
149
+ this._viewport.removeEventListener('click', this._onClick, true);
150
+ document.removeEventListener('visibilitychange', this._schedule);
151
+ this._motion.removeEventListener('change', this._onMotionChange);
152
+ for (const slide of this._saved.keys())
153
+ this._releaseSlide(slide);
154
+ for (const [name, value] of this._hostAttributes) {
155
+ if (value === null)
156
+ this.removeAttribute(name);
157
+ else
158
+ this.setAttribute(name, value);
159
+ }
160
+ this._hostAttributes.clear();
161
+ }
162
+ attributeChangedCallback(name, oldValue, value) {
163
+ if (oldValue === value || this._reflecting)
164
+ return;
165
+ if (name === 'index') {
166
+ const index = Math.floor(numeric(value, 0, 0, Number.MAX_SAFE_INTEGER));
167
+ if (this._connected)
168
+ this._select(index, 'api');
169
+ else
170
+ this._index = index;
171
+ return;
172
+ }
173
+ if (name === 'direction' || name === 'draggable') {
174
+ this._cancelDrag();
175
+ this._cancelAnimations();
176
+ }
177
+ if (name === 'autoplay') {
178
+ this._paused = false;
179
+ this._motionOverride = false;
180
+ }
181
+ if (this._connected) {
182
+ this._render();
183
+ this._schedule();
184
+ }
185
+ }
186
+ get index() { return this._index; }
187
+ set index(value) { this.setAttribute('index', String(Math.floor(numeric(value, 0, 0, Number.MAX_SAFE_INTEGER)))); }
188
+ get direction() { return this.getAttribute('direction') === 'vertical' ? 'vertical' : 'horizontal'; }
189
+ set direction(value) { this.setAttribute('direction', value === 'vertical' ? value : 'horizontal'); }
190
+ get indicatorPosition() {
191
+ const value = this.getAttribute('indicator-position');
192
+ return value === 'top' || value === 'left' || value === 'right' ? value : 'bottom';
193
+ }
194
+ set indicatorPosition(value) { this.setAttribute('indicator-position', value); }
195
+ get indicatorStyle() {
196
+ const value = this.getAttribute('indicator-style');
197
+ return value === 'line' || value === 'number' ? value : 'dot';
198
+ }
199
+ set indicatorStyle(value) { this.setAttribute('indicator-style', value); }
200
+ get indicatorRenderer() { return this._indicatorRenderer; }
201
+ set indicatorRenderer(value) { this._indicatorRenderer = typeof value === 'function' ? value : null; if (this._connected)
202
+ this._render(); }
203
+ get showIndicators() { return this.getAttribute('show-indicators') !== 'false'; }
204
+ set showIndicators(value) { this.setAttribute('show-indicators', String(Boolean(value))); }
205
+ get showArrows() { return this.getAttribute('show-arrows') !== 'false'; }
206
+ set showArrows(value) { this.setAttribute('show-arrows', String(Boolean(value))); }
207
+ get autoplay() { return this.hasAttribute('autoplay') && this.getAttribute('autoplay') !== 'false'; }
208
+ set autoplay(value) { if (value)
209
+ this.setAttribute('autoplay', '');
210
+ else
211
+ this.removeAttribute('autoplay'); }
212
+ get interval() { return numeric(this.getAttribute('interval'), 3000, 100, 86_400_000); }
213
+ set interval(value) { this.setAttribute('interval', String(numeric(value, 3000, 100, 86_400_000))); }
214
+ get draggable() { return this.getAttribute('draggable') !== 'false'; }
215
+ set draggable(value) { this.setAttribute('draggable', String(Boolean(value))); }
216
+ get loop() { return this.getAttribute('loop') !== 'false'; }
217
+ set loop(value) { this.setAttribute('loop', String(Boolean(value))); }
218
+ next() { this._select(this._index + 1, 'api'); }
219
+ previous() { this._select(this._index - 1, 'api'); }
220
+ goTo(index) { this._select(Math.floor(numeric(index, 0, -Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER)), 'api'); }
221
+ play() { this._paused = false; this.autoplay = true; this._motionOverride = true; if (this._connected)
222
+ this._render(); this._schedule(); }
223
+ pause() { this._paused = true; this._clearTimer(); if (this._connected)
224
+ this._render(); }
225
+ _syncSlides = () => {
226
+ const active = this._slides[this._index];
227
+ const slides = this._slot.assignedElements().filter((slide) => slide instanceof HTMLElement);
228
+ this._cancelDrag();
229
+ this._cancelAnimations();
230
+ for (const slide of this._saved.keys())
231
+ if (!slides.includes(slide))
232
+ this._releaseSlide(slide);
233
+ this._slides = slides;
234
+ for (const slide of slides)
235
+ if (!this._saved.has(slide))
236
+ this._saved.set(slide, {
237
+ attributes: attributes.map(name => slide.getAttribute(name)),
238
+ properties: properties.map(name => ({ value: slide.style.getPropertyValue(name), priority: slide.style.getPropertyPriority(name) })),
239
+ });
240
+ this._indicators.replaceChildren(...slides.map((_, index) => {
241
+ const button = document.createElement('button');
242
+ button.type = 'button';
243
+ button.className = 'indicator';
244
+ button.dataset.index = String(index);
245
+ button.part.add('indicator');
246
+ button.setAttribute('aria-label', `Go to slide ${index + 1}`);
247
+ const content = document.createElement('span');
248
+ content.className = 'indicator-content';
249
+ content.part.add('indicator-content');
250
+ content.setAttribute('aria-hidden', 'true');
251
+ button.append(content);
252
+ return button;
253
+ }));
254
+ const index = active && slides.includes(active) ? slides.indexOf(active) : Math.min(this._index, Math.max(0, slides.length - 1));
255
+ this._select(index, 'content', false);
256
+ };
257
+ _releaseSlide(slide) {
258
+ const saved = this._saved.get(slide);
259
+ if (!saved)
260
+ return;
261
+ attributes.forEach((name, index) => { const value = saved.attributes[index]; if (value == null)
262
+ slide.removeAttribute(name);
263
+ else
264
+ slide.setAttribute(name, value); });
265
+ properties.forEach((name, index) => { const entry = saved.properties[index]; if (entry.value)
266
+ slide.style.setProperty(name, entry.value, entry.priority);
267
+ else
268
+ slide.style.removeProperty(name); });
269
+ this._saved.delete(slide);
270
+ }
271
+ _resolve(index) {
272
+ const length = this._slides.length;
273
+ return !length ? 0 : this.loop ? ((index % length) + length) % length : Math.max(0, Math.min(length - 1, index));
274
+ }
275
+ _select(requested, source, animate = true, dragOffset = 0) {
276
+ if (!this._connected) {
277
+ this.index = Math.max(0, requested);
278
+ return;
279
+ }
280
+ if (this._drag)
281
+ this._cancelDrag();
282
+ const index = this._resolve(requested);
283
+ const previousIndex = this._index;
284
+ const previous = this._slides[previousIndex];
285
+ const next = this._slides[index];
286
+ this._cancelAnimations();
287
+ if (previous && index !== previousIndex && previous.matches(':focus-within'))
288
+ this._viewport.focus({ preventScroll: true });
289
+ this._index = index;
290
+ this._reflecting = true;
291
+ this.setAttribute('index', String(index));
292
+ this._reflecting = false;
293
+ this._render();
294
+ if (animate && index !== previousIndex && previous && next) {
295
+ const sign = requested > previousIndex ? 1 : -1;
296
+ this._animate(previous, dragOffset, -sign * 100);
297
+ this._animate(next, dragOffset + sign * 100, 0);
298
+ }
299
+ this._schedule();
300
+ if (index !== previousIndex)
301
+ this.dispatchEvent(new CustomEvent('change', {
302
+ detail: { index, previousIndex, source }, bubbles: true, composed: true,
303
+ }));
304
+ }
305
+ _transform(offset) {
306
+ return this.direction === 'vertical' ? `translate3d(0, ${offset}%, 0)` : `translate3d(${offset * (getComputedStyle(this).direction === 'rtl' ? -1 : 1)}%, 0, 0)`;
307
+ }
308
+ _position(slide, offset, visible) {
309
+ slide.style.setProperty(properties[0], this._transform(offset));
310
+ slide.style.setProperty(properties[1], visible ? 'visible' : 'hidden');
311
+ }
312
+ _paint() { this._slides.forEach((slide, index) => this._position(slide, 0, index === this._index)); }
313
+ _render() {
314
+ this._paint();
315
+ this._slides.forEach((slide, index) => {
316
+ const saved = this._saved.get(slide);
317
+ if (!saved)
318
+ return;
319
+ if (!saved.attributes[0])
320
+ slide.setAttribute('role', 'group');
321
+ if (!saved.attributes[1])
322
+ slide.setAttribute('aria-roledescription', 'slide');
323
+ if (!saved.attributes[2])
324
+ slide.setAttribute('aria-label', `${index + 1} of ${this._slides.length}`);
325
+ for (const name of ['inert', 'aria-hidden']) {
326
+ const value = saved.attributes[attributes.indexOf(name)];
327
+ if (index !== this._index)
328
+ slide.setAttribute(name, name === 'inert' ? '' : 'true');
329
+ else if (value == null)
330
+ slide.removeAttribute(name);
331
+ else
332
+ slide.setAttribute(name, value);
333
+ }
334
+ });
335
+ this._indicators.hidden = !this.showIndicators || this._slides.length < 2;
336
+ this._indicators.classList.toggle('custom', Boolean(this._indicatorRenderer));
337
+ [...this._indicators.children].forEach((element, index) => {
338
+ const button = element;
339
+ const active = index === this._index;
340
+ button.setAttribute('aria-current', String(active));
341
+ button.tabIndex = active ? 0 : -1;
342
+ button.part.toggle('indicator-active', active);
343
+ const content = button.firstElementChild;
344
+ if (this._indicatorRenderer) {
345
+ const rendered = this._indicatorRenderer(index, active);
346
+ content.replaceChildren(...(rendered == null ? [] : [rendered]));
347
+ }
348
+ else
349
+ content.textContent = this.indicatorStyle === 'number' ? String(index + 1) : '';
350
+ });
351
+ const vertical = this.direction === 'vertical';
352
+ const rtl = getComputedStyle(this).direction === 'rtl';
353
+ this._previous.textContent = vertical ? '↑' : rtl ? '→' : '←';
354
+ this._next.textContent = vertical ? '↓' : rtl ? '←' : '→';
355
+ this._previous.hidden = this._next.hidden = !this.showArrows || this._slides.length < 2;
356
+ this._previous.disabled = !this.loop && this._index === 0;
357
+ this._next.disabled = !this.loop && this._index >= this._slides.length - 1;
358
+ this._playback.hidden = !this.autoplay || this._slides.length < 2;
359
+ const paused = this._paused || (this._motion.matches && !this._motionOverride);
360
+ this._playback.textContent = paused ? '▶' : 'Ⅱ';
361
+ this._playback.setAttribute('aria-label', paused ? 'Start automatic slide rotation' : 'Pause automatic slide rotation');
362
+ this._viewport.setAttribute('aria-live', this.autoplay && !paused ? 'off' : 'polite');
363
+ }
364
+ _animate(slide, from, to) {
365
+ if (this._motion.matches)
366
+ return;
367
+ const raw = getComputedStyle(this).getPropertyValue('--hy-carousel-duration').trim() || '320ms';
368
+ const duration = numeric(Number.parseFloat(raw) * (raw.endsWith('ms') ? 1 : 1000), 320, 0, 5000);
369
+ if (!duration)
370
+ return;
371
+ slide.style.setProperty(properties[1], 'visible');
372
+ const animation = slide.animate([{ transform: this._transform(from) }, { transform: this._transform(to) }], { duration, easing: 'ease', fill: 'forwards' });
373
+ this._animations.add(animation);
374
+ void animation.finished.then(() => {
375
+ if (!this._animations.delete(animation))
376
+ return;
377
+ animation.cancel();
378
+ this._position(slide, 0, slide === this._slides[this._index]);
379
+ }, () => { this._animations.delete(animation); });
380
+ }
381
+ _cancelAnimations() { for (const animation of this._animations)
382
+ animation.cancel(); this._animations.clear(); }
383
+ _clearTimer() { window.clearTimeout(this._timer); this._timer = 0; }
384
+ _schedule = () => {
385
+ this._clearTimer();
386
+ if (!this._connected || !this.autoplay || this._paused || this._hovered || this._drag || document.hidden || this._slides.length < 2 ||
387
+ (this._motion.matches && !this._motionOverride) || (!this.loop && this._index === this._slides.length - 1))
388
+ return;
389
+ this._timer = window.setTimeout(() => this._select(this._index + 1, 'autoplay'), this.interval);
390
+ };
391
+ _onEnter = () => { this._hovered = true; this._schedule(); };
392
+ _onLeave = () => { this._hovered = false; this._schedule(); };
393
+ _onMotionChange = () => { this._cancelAnimations(); if (this._connected)
394
+ this._render(); this._schedule(); };
395
+ _onFocus = (event) => { if (!(event.relatedTarget instanceof Node) || !this.contains(event.relatedTarget))
396
+ this.pause(); };
397
+ _onKeyDown = (event) => {
398
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || !this._slides.length)
399
+ return;
400
+ const path = event.composedPath();
401
+ const indicator = path.find(node => node instanceof HTMLButtonElement && node.dataset.index != null);
402
+ if (!indicator && path.some(node => node instanceof HTMLElement && /^(INPUT|SELECT|TEXTAREA|BUTTON|A)$/.test(node.tagName) || node instanceof HTMLElement && node.isContentEditable))
403
+ return;
404
+ const backward = this.direction === 'vertical' ? 'ArrowUp' : 'ArrowLeft';
405
+ const forward = this.direction === 'vertical' ? 'ArrowDown' : 'ArrowRight';
406
+ let step = event.key === forward ? 1 : event.key === backward ? -1 : 0;
407
+ if (indicator && !step)
408
+ step = event.key === 'ArrowDown' ? 1 : event.key === 'ArrowUp' ? -1 : 0;
409
+ if (this.direction === 'horizontal' && getComputedStyle(this).direction === 'rtl' && ['ArrowLeft', 'ArrowRight'].includes(event.key))
410
+ step *= -1;
411
+ if (!step && event.key !== 'Home' && event.key !== 'End')
412
+ return;
413
+ event.preventDefault();
414
+ this._select(event.key === 'Home' ? 0 : event.key === 'End' ? this._slides.length - 1 : this._index + step, 'keyboard');
415
+ if (indicator)
416
+ this._indicators.children[this._index]?.focus();
417
+ };
418
+ _onNativeDrag = (event) => { if (this.draggable)
419
+ event.preventDefault(); };
420
+ _onClick = (event) => { if (event.detail > 0 && performance.now() < this._blockClickUntil) {
421
+ event.preventDefault();
422
+ event.stopImmediatePropagation();
423
+ } };
424
+ _onPointerDown = (event) => {
425
+ if (!this.draggable || !event.isPrimary || event.button !== 0 || this._slides.length < 2 ||
426
+ event.composedPath().some(node => node instanceof HTMLElement && (node.matches('a, button, input, textarea, select, [contenteditable]:not([contenteditable="false"])'))))
427
+ return;
428
+ this._cancelAnimations();
429
+ this._paint();
430
+ this._drag = { id: event.pointerId, x: event.clientX, y: event.clientY, offset: 0, moved: false };
431
+ this._clearTimer();
432
+ window.addEventListener('pointermove', this._onPointerMove, { passive: false });
433
+ window.addEventListener('pointerup', this._onPointerUp);
434
+ window.addEventListener('pointercancel', this._onPointerCancel);
435
+ };
436
+ _onPointerMove = (event) => {
437
+ const drag = this._drag;
438
+ if (!drag || drag.id !== event.pointerId)
439
+ return;
440
+ const vertical = this.direction === 'vertical';
441
+ const delta = vertical ? event.clientY - drag.y : event.clientX - drag.x;
442
+ const cross = vertical ? event.clientX - drag.x : event.clientY - drag.y;
443
+ if (!drag.moved && Math.abs(cross) > Math.max(8, Math.abs(delta))) {
444
+ this._cancelDrag();
445
+ this._schedule();
446
+ return;
447
+ }
448
+ if (!drag.moved && Math.abs(delta) < 6)
449
+ return;
450
+ if (!drag.moved) {
451
+ drag.moved = true;
452
+ this._viewport.setPointerCapture(event.pointerId);
453
+ }
454
+ event.preventDefault();
455
+ const rect = this._viewport.getBoundingClientRect();
456
+ const size = vertical ? rect.height : rect.width;
457
+ drag.offset = size > 0 ? delta / size * 100 * (!vertical && getComputedStyle(this).direction === 'rtl' ? -1 : 1) : 0;
458
+ const step = drag.offset < 0 ? 1 : -1;
459
+ const neighbor = this._resolve(this._index + step);
460
+ this._paint();
461
+ const offset = neighbor === this._index ? drag.offset * .25 : drag.offset;
462
+ this._position(this._slides[this._index], offset, true);
463
+ if (neighbor !== this._index)
464
+ this._position(this._slides[neighbor], offset + step * 100, true);
465
+ };
466
+ _onPointerUp = (event) => {
467
+ const drag = this._drag;
468
+ if (!drag || drag.id !== event.pointerId)
469
+ return;
470
+ const offset = drag.offset;
471
+ const step = offset < 0 ? 1 : -1;
472
+ const neighbor = this._resolve(this._index + step);
473
+ this._cancelDrag();
474
+ if (drag.moved) {
475
+ this._blockClickUntil = performance.now() + 400;
476
+ if (Math.abs(offset) >= 18 && neighbor !== this._index)
477
+ this._select(this._index + step, 'drag', true, offset);
478
+ else
479
+ this._animate(this._slides[this._index], neighbor === this._index ? offset * .25 : offset, 0);
480
+ }
481
+ this._schedule();
482
+ };
483
+ _onPointerCancel = (event) => {
484
+ // Touch starts with implicit capture on the slotted content. Moving capture
485
+ // to the viewport emits a bubbling loss from that content, not a cancellation.
486
+ if (event.type === 'lostpointercapture' && event.target !== this._viewport)
487
+ return;
488
+ if (this._drag?.id === event.pointerId) {
489
+ this._cancelDrag();
490
+ this._schedule();
491
+ }
492
+ };
493
+ _cancelDrag() {
494
+ const drag = this._drag;
495
+ this._drag = null;
496
+ window.removeEventListener('pointermove', this._onPointerMove);
497
+ window.removeEventListener('pointerup', this._onPointerUp);
498
+ window.removeEventListener('pointercancel', this._onPointerCancel);
499
+ if (drag && this._viewport.hasPointerCapture(drag.id))
500
+ this._viewport.releasePointerCapture(drag.id);
501
+ if (this._connected)
502
+ this._paint();
503
+ }
504
+ }
505
+ export function defineCarouselComponents() {
506
+ if (!customElements.get('hy-carousel'))
507
+ customElements.define('hy-carousel', HYCarousel);
508
+ }
509
+ //# sourceMappingURL=carousel.js.map
@@ -23,9 +23,12 @@ export declare class HYCollapse extends HTMLElement {
23
23
  private _itemsAssigned;
24
24
  private _activeKeys;
25
25
  private _panels;
26
+ private readonly _motion;
27
+ private readonly _resizeObserver;
26
28
  static get observedAttributes(): string[];
27
29
  constructor();
28
30
  connectedCallback(): void;
31
+ disconnectedCallback(): void;
29
32
  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
30
33
  get items(): readonly HYCollapseItem[];
31
34
  set items(value: readonly HYCollapseItem[]);
@@ -43,10 +46,20 @@ export declare class HYCollapse extends HTMLElement {
43
46
  set collapsible(value: HYCollapseTrigger);
44
47
  get destroyOnHidden(): boolean;
45
48
  set destroyOnHidden(value: boolean);
49
+ /** Opt-in height and arrow transitions. Initial rendering is always immediate. */
50
+ get animated(): boolean;
51
+ set animated(value: boolean);
52
+ /** Milliseconds; zero disables animation. Defaults to 200 (or the inherited duration token). */
53
+ get animationDuration(): number;
54
+ set animationDuration(value: number);
46
55
  toggle(key: HYCollapseKey, expanded?: boolean): void;
47
56
  private _readItemsAttribute;
48
57
  private _syncItems;
49
58
  private _syncExpanded;
59
+ private _duration;
60
+ private _transition;
61
+ private _finishTransition;
62
+ private _onMotionChange;
50
63
  private _renderContent;
51
64
  private _normalizeActiveKeys;
52
65
  private _reflectActiveKeys;