@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.
- package/CHANGELOG.md +16 -0
- package/LICENSE +27 -0
- package/README.md +201 -3
- package/dist/adapters/web/controls.js +28 -5
- package/dist/adapters/web/renderer.js +10 -2
- package/dist/adapters/web/styles.d.ts +2 -2
- package/dist/adapters/web/styles.js +26 -5
- package/dist/carousel.d.ts +92 -0
- package/dist/carousel.js +509 -0
- package/dist/collapse.d.ts +13 -0
- package/dist/collapse.js +94 -7
- package/dist/core/types.d.ts +22 -3
- package/dist/core/validation.js +9 -4
- package/dist/index.d.ts +8 -0
- package/dist/index.js +12 -0
- package/dist/internal/qrcodegen.d.ts +94 -0
- package/dist/internal/qrcodegen.js +856 -0
- package/dist/masonry.d.ts +36 -0
- package/dist/masonry.js +171 -0
- package/dist/modal.d.ts +73 -0
- package/dist/modal.js +361 -0
- package/dist/portable/icons.d.ts +5 -0
- package/dist/portable/icons.js +24 -0
- package/dist/portable/index.d.ts +3 -0
- package/dist/portable/index.js +2 -0
- package/dist/portable/responsive.d.ts +20 -0
- package/dist/portable/responsive.js +15 -0
- package/dist/portable/theme.d.ts +6 -1
- package/dist/portable/theme.js +23 -3
- package/dist/qr-code.d.ts +62 -0
- package/dist/qr-code.js +224 -0
- package/package.json +18 -1
package/dist/carousel.js
ADDED
|
@@ -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
|
package/dist/collapse.d.ts
CHANGED
|
@@ -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;
|