@haiyue/ui 0.1.2 → 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 +43 -0
- package/LICENSE +27 -0
- package/README.md +643 -2
- package/dist/adapters/web/controls.d.ts +19 -0
- package/dist/adapters/web/controls.js +307 -0
- package/dist/adapters/web/dom.d.ts +10 -0
- package/dist/adapters/web/dom.js +98 -0
- package/dist/adapters/web/index.d.ts +3 -0
- package/dist/adapters/web/index.js +3 -0
- package/dist/adapters/web/renderer.d.ts +18 -0
- package/dist/adapters/web/renderer.js +553 -0
- package/dist/adapters/web/styles.d.ts +3 -0
- package/dist/adapters/web/styles.js +83 -0
- package/dist/adapters/web/types.d.ts +46 -0
- package/dist/adapters/web/types.js +2 -0
- package/dist/affix.d.ts +35 -0
- package/dist/affix.js +137 -0
- package/dist/badge.d.ts +45 -0
- package/dist/badge.js +168 -0
- package/dist/breadcrumb.d.ts +37 -0
- package/dist/breadcrumb.js +210 -0
- package/dist/button-group.d.ts +12 -0
- package/dist/button-group.js +72 -0
- package/dist/button.d.ts +15 -1
- package/dist/button.js +101 -12
- package/dist/carousel.d.ts +92 -0
- package/dist/carousel.js +509 -0
- package/dist/cascader.d.ts +65 -0
- package/dist/cascader.js +479 -0
- package/dist/collapse.d.ts +68 -0
- package/dist/collapse.js +348 -0
- package/dist/core/actions.d.ts +32 -0
- package/dist/core/actions.js +145 -0
- package/dist/core/data.d.ts +4 -0
- package/dist/core/data.js +27 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +6 -0
- package/dist/core/platform-view.d.ts +17 -0
- package/dist/core/platform-view.js +84 -0
- package/dist/core/selection.d.ts +14 -0
- package/dist/core/selection.js +93 -0
- package/dist/core/text.d.ts +31 -0
- package/dist/core/text.js +154 -0
- package/dist/core/types.d.ts +269 -0
- package/dist/core/types.js +2 -0
- package/dist/core/validation.d.ts +24 -0
- package/dist/core/validation.js +243 -0
- package/dist/date-picker.d.ts +60 -0
- package/dist/date-picker.js +355 -0
- package/dist/dialog.d.ts +4 -0
- package/dist/dialog.js +23 -5
- package/dist/divider.d.ts +33 -0
- package/dist/divider.js +113 -0
- package/dist/draggable.d.ts +78 -0
- package/dist/draggable.js +255 -0
- package/dist/index.d.ts +53 -1
- package/dist/index.js +78 -0
- package/dist/input.d.ts +30 -1
- package/dist/input.js +166 -54
- 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 +7 -0
- package/dist/portable/index.js +5 -0
- package/dist/portable/page.d.ts +38 -0
- package/dist/portable/page.js +361 -0
- package/dist/portable/responsive.d.ts +20 -0
- package/dist/portable/responsive.js +15 -0
- package/dist/portable/theme.d.ts +24 -0
- package/dist/portable/theme.js +60 -0
- package/dist/portable/tree.d.ts +10 -0
- package/dist/portable/tree.js +96 -0
- package/dist/qr-code.d.ts +62 -0
- package/dist/qr-code.js +224 -0
- package/dist/range.d.ts +3 -0
- package/dist/range.js +16 -5
- package/dist/rate.d.ts +46 -0
- package/dist/rate.js +193 -0
- package/dist/resizable.d.ts +97 -0
- package/dist/resizable.js +426 -0
- package/dist/skeleton.d.ts +41 -0
- package/dist/skeleton.js +141 -0
- package/dist/steps.d.ts +45 -0
- package/dist/steps.js +203 -0
- package/dist/table.d.ts +106 -0
- package/dist/table.js +689 -0
- package/dist/tabs.d.ts +3 -0
- package/dist/tabs.js +11 -1
- package/dist/tag.d.ts +55 -0
- package/dist/tag.js +225 -0
- package/dist/time-picker.d.ts +62 -0
- package/dist/time-picker.js +298 -0
- package/dist/timeline.d.ts +48 -0
- package/dist/timeline.js +201 -0
- package/dist/toast.d.ts +55 -0
- package/dist/toast.js +224 -0
- package/dist/tour.d.ts +89 -0
- package/dist/tour.js +306 -0
- package/dist/virtual-keyboard.d.ts +95 -0
- package/dist/virtual-keyboard.js +432 -0
- package/dist/virtual-mouse.d.ts +82 -0
- package/dist/virtual-mouse.js +358 -0
- package/dist/watermark.d.ts +44 -0
- package/dist/watermark.js +148 -0
- package/package.json +132 -4
package/dist/collapse.js
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
let collapseInstance = 0;
|
|
2
|
+
function parseKeys(value) {
|
|
3
|
+
if (!value)
|
|
4
|
+
return [];
|
|
5
|
+
try {
|
|
6
|
+
const parsed = JSON.parse(value);
|
|
7
|
+
return Array.isArray(parsed) ? [...new Set(parsed.map(String))] : [String(parsed)];
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return [...new Set(value.split(',').map(key => key.trim()).filter(Boolean))];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Accessible accordion and multi-panel disclosure group. */
|
|
14
|
+
export class HYCollapse extends HTMLElement {
|
|
15
|
+
_container = document.createElement('div');
|
|
16
|
+
_uid = `hy-collapse-${++collapseInstance}`;
|
|
17
|
+
_items = [];
|
|
18
|
+
_itemsAssigned = false;
|
|
19
|
+
_activeKeys = [];
|
|
20
|
+
_panels = new Map();
|
|
21
|
+
_motion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
22
|
+
_resizeObserver = new ResizeObserver(() => {
|
|
23
|
+
for (const panel of this._panels.values()) {
|
|
24
|
+
if (panel.animation && panel.expanded && panel.targetHeight !== panel.body.offsetHeight) {
|
|
25
|
+
this._transition(panel, Number.parseFloat(getComputedStyle(panel.region).height), this._duration());
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
static get observedAttributes() {
|
|
30
|
+
return ['items', 'active-keys', 'default-active-keys', 'accordion', 'bordered', 'ghost', 'icon-position', 'collapsible', 'destroy-on-hidden', 'animated', 'animation-duration', 'aria-label'];
|
|
31
|
+
}
|
|
32
|
+
constructor() {
|
|
33
|
+
super();
|
|
34
|
+
const root = this.attachShadow({ mode: 'open' });
|
|
35
|
+
const style = document.createElement('style');
|
|
36
|
+
style.textContent = `
|
|
37
|
+
:host { display: block; min-width: 0; color: var(--hy-text-color, #eef3ff); font: 12px/1.5 system-ui, sans-serif; }
|
|
38
|
+
.collapse { overflow: hidden; border: 1px solid var(--hy-border-color, #3d4654); border-radius: var(--hy-collapse-radius, 8px); background: var(--hy-surface-color, #111722); }
|
|
39
|
+
.panel + .panel { border-top: 1px solid var(--hy-border-color, #3d4654); }
|
|
40
|
+
.header { display: flex; min-height: var(--hy-collapse-header-height, 44px); align-items: center; background: var(--hy-collapse-header-bg, color-mix(in srgb, var(--hy-surface-elevated-color, #171d27) 82%, transparent)); }
|
|
41
|
+
.trigger { display: flex; min-width: 0; min-height: inherit; flex: 1 1 auto; align-items: center; gap: 10px; padding: 9px 14px; border: 0; color: inherit; background: transparent; cursor: pointer; font: inherit; text-align: start; }
|
|
42
|
+
.trigger:hover:not(:disabled) { background: var(--hy-hover-bg-color, #273241); }
|
|
43
|
+
.trigger:focus-visible { position: relative; z-index: 1; outline: 2px solid var(--hy-focus-border-color, #79a8ff); outline-offset: -2px; }
|
|
44
|
+
.trigger:disabled { color: var(--hy-muted-color, #788697); cursor: not-allowed; }
|
|
45
|
+
.arrow { display: grid; width: 16px; height: 16px; flex: 0 0 auto; place-items: center; color: var(--hy-muted-color, #788697); transition: transform var(--hy-collapse-animation-duration, 200ms) var(--hy-collapse-animation-easing, ease); }
|
|
46
|
+
.arrow::before { content: ''; width: 6px; height: 6px; border-block-start: 1.5px solid currentColor; border-inline-end: 1.5px solid currentColor; transform: rotate(45deg); }
|
|
47
|
+
.panel.expanded .arrow { transform: rotate(90deg); }
|
|
48
|
+
.title { min-width: 0; flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
49
|
+
.extra { flex: 0 0 auto; padding-inline: var(--hy-collapse-extra-gap, 10px) 14px; color: var(--hy-muted-color, #788697); font-size: 10px; }
|
|
50
|
+
.region[hidden] { display: none; }
|
|
51
|
+
.body { padding: var(--hy-collapse-content-padding, 16px); border-top: 1px solid var(--hy-border-color, #3d4654); color: var(--hy-secondary-text-color, #aebbd0); background: var(--hy-collapse-content-bg, transparent); }
|
|
52
|
+
:host([icon-position="end"]) .arrow { order: 2; }
|
|
53
|
+
:host([bordered="false"]) .collapse { border: 0; border-radius: 0; }
|
|
54
|
+
:host([ghost]) .collapse { border: 0; background: transparent; }
|
|
55
|
+
:host([ghost]) .panel + .panel { border-top-color: transparent; }
|
|
56
|
+
:host([ghost]) .header, :host([ghost]) .body { border: 0; background: transparent; }
|
|
57
|
+
@media (prefers-reduced-motion: reduce) { .arrow { transition: none; } }
|
|
58
|
+
`;
|
|
59
|
+
this._container.className = 'collapse';
|
|
60
|
+
this._container.part.add('collapse');
|
|
61
|
+
this._container.setAttribute('role', 'group');
|
|
62
|
+
root.append(style, this._container);
|
|
63
|
+
}
|
|
64
|
+
connectedCallback() {
|
|
65
|
+
this._motion.addEventListener('change', this._onMotionChange);
|
|
66
|
+
if (!this._itemsAssigned && this.hasAttribute('items'))
|
|
67
|
+
this._readItemsAttribute();
|
|
68
|
+
this._activeKeys = this.hasAttribute('active-keys') ? parseKeys(this.getAttribute('active-keys')) : parseKeys(this.getAttribute('default-active-keys'));
|
|
69
|
+
this._normalizeActiveKeys();
|
|
70
|
+
this._syncItems();
|
|
71
|
+
this._syncLabel();
|
|
72
|
+
}
|
|
73
|
+
disconnectedCallback() {
|
|
74
|
+
this._motion.removeEventListener('change', this._onMotionChange);
|
|
75
|
+
this._resizeObserver.disconnect();
|
|
76
|
+
for (const panel of this._panels.values())
|
|
77
|
+
this._finishTransition(panel);
|
|
78
|
+
}
|
|
79
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
80
|
+
if (oldValue === newValue)
|
|
81
|
+
return;
|
|
82
|
+
if (name === 'items' && !this._itemsAssigned) {
|
|
83
|
+
this._readItemsAttribute();
|
|
84
|
+
this._syncItems();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (name === 'active-keys') {
|
|
88
|
+
this._activeKeys = parseKeys(newValue);
|
|
89
|
+
this._normalizeActiveKeys();
|
|
90
|
+
this._syncExpanded();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (name === 'default-active-keys' && !this.hasAttribute('active-keys')) {
|
|
94
|
+
this._activeKeys = parseKeys(newValue);
|
|
95
|
+
this._normalizeActiveKeys();
|
|
96
|
+
this._syncExpanded();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (name === 'aria-label') {
|
|
100
|
+
this._syncLabel();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (name === 'accordion') {
|
|
104
|
+
this._normalizeActiveKeys();
|
|
105
|
+
this._reflectActiveKeys();
|
|
106
|
+
}
|
|
107
|
+
this._syncExpanded();
|
|
108
|
+
}
|
|
109
|
+
get items() { return this._items; }
|
|
110
|
+
set items(value) {
|
|
111
|
+
this._itemsAssigned = true;
|
|
112
|
+
this._items = Array.isArray(value) ? value.flatMap(item => item && item.key != null && item.title != null ? [{ ...item, key: String(item.key), title: String(item.title) }] : []) : [];
|
|
113
|
+
this._normalizeActiveKeys();
|
|
114
|
+
this._syncItems();
|
|
115
|
+
}
|
|
116
|
+
get activeKeys() { return [...this._activeKeys]; }
|
|
117
|
+
set activeKeys(value) {
|
|
118
|
+
this._activeKeys = Array.isArray(value) ? [...new Set(value.map(String))] : [];
|
|
119
|
+
this._normalizeActiveKeys();
|
|
120
|
+
this._reflectActiveKeys();
|
|
121
|
+
this._syncExpanded();
|
|
122
|
+
}
|
|
123
|
+
get accordion() { return this.hasAttribute('accordion'); }
|
|
124
|
+
set accordion(value) { this.toggleAttribute('accordion', Boolean(value)); }
|
|
125
|
+
get bordered() { return this.getAttribute('bordered') !== 'false'; }
|
|
126
|
+
set bordered(value) { this.setAttribute('bordered', String(Boolean(value))); }
|
|
127
|
+
get ghost() { return this.hasAttribute('ghost'); }
|
|
128
|
+
set ghost(value) { this.toggleAttribute('ghost', Boolean(value)); }
|
|
129
|
+
get iconPosition() { return this.getAttribute('icon-position') === 'end' ? 'end' : 'start'; }
|
|
130
|
+
set iconPosition(value) { this.setAttribute('icon-position', value === 'end' ? 'end' : 'start'); }
|
|
131
|
+
get collapsible() {
|
|
132
|
+
const value = this.getAttribute('collapsible');
|
|
133
|
+
return value === 'icon' || value === 'disabled' ? value : 'header';
|
|
134
|
+
}
|
|
135
|
+
set collapsible(value) { this.setAttribute('collapsible', value === 'icon' || value === 'disabled' ? value : 'header'); }
|
|
136
|
+
get destroyOnHidden() { return this.hasAttribute('destroy-on-hidden'); }
|
|
137
|
+
set destroyOnHidden(value) { this.toggleAttribute('destroy-on-hidden', Boolean(value)); }
|
|
138
|
+
/** Opt-in height and arrow transitions. Initial rendering is always immediate. */
|
|
139
|
+
get animated() { return this.hasAttribute('animated') && this.getAttribute('animated') !== 'false'; }
|
|
140
|
+
set animated(value) { if (value)
|
|
141
|
+
this.setAttribute('animated', '');
|
|
142
|
+
else
|
|
143
|
+
this.removeAttribute('animated'); }
|
|
144
|
+
/** Milliseconds; zero disables animation. Defaults to 200 (or the inherited duration token). */
|
|
145
|
+
get animationDuration() {
|
|
146
|
+
const value = Number(this.getAttribute('animation-duration') ?? 200);
|
|
147
|
+
return Number.isFinite(value) ? Math.max(0, Math.min(10_000, value)) : 200;
|
|
148
|
+
}
|
|
149
|
+
set animationDuration(value) { this.setAttribute('animation-duration', String(Number.isFinite(value) ? Math.max(0, Math.min(10_000, value)) : 200)); }
|
|
150
|
+
toggle(key, expanded) {
|
|
151
|
+
const normalized = String(key);
|
|
152
|
+
const panel = this._panels.get(normalized);
|
|
153
|
+
if (!panel || panel.item.disabled || this.collapsible === 'disabled')
|
|
154
|
+
return;
|
|
155
|
+
const wasExpanded = this._activeKeys.includes(normalized);
|
|
156
|
+
const nextExpanded = expanded ?? !wasExpanded;
|
|
157
|
+
if (wasExpanded === nextExpanded)
|
|
158
|
+
return;
|
|
159
|
+
this._activeKeys = nextExpanded ? (this.accordion ? [normalized] : [...this._activeKeys, normalized]) : this._activeKeys.filter(activeKey => activeKey !== normalized);
|
|
160
|
+
this._reflectActiveKeys();
|
|
161
|
+
this._syncExpanded();
|
|
162
|
+
this.dispatchEvent(new CustomEvent('collapse-change', { bubbles: true, composed: true, detail: { activeKeys: [...this._activeKeys], changedKey: normalized, expanded: nextExpanded } }));
|
|
163
|
+
}
|
|
164
|
+
_readItemsAttribute() {
|
|
165
|
+
try {
|
|
166
|
+
const parsed = JSON.parse(this.getAttribute('items') ?? '[]');
|
|
167
|
+
this._items = Array.isArray(parsed) ? parsed.flatMap(value => {
|
|
168
|
+
if (!value || typeof value !== 'object')
|
|
169
|
+
return [];
|
|
170
|
+
const item = value;
|
|
171
|
+
if (item.key == null || item.title == null)
|
|
172
|
+
return [];
|
|
173
|
+
return [{ key: String(item.key), title: String(item.title), ...(item.content != null ? { content: String(item.content) } : {}), ...(item.extra != null ? { extra: String(item.extra) } : {}), ...(typeof item.disabled === 'boolean' ? { disabled: item.disabled } : {}), ...(typeof item.showArrow === 'boolean' ? { showArrow: item.showArrow } : {}) }];
|
|
174
|
+
}) : [];
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
this._items = [];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
_syncItems() {
|
|
181
|
+
if (!this.isConnected)
|
|
182
|
+
return;
|
|
183
|
+
this._resizeObserver.disconnect();
|
|
184
|
+
for (const panel of this._panels.values())
|
|
185
|
+
this._finishTransition(panel);
|
|
186
|
+
this._panels.clear();
|
|
187
|
+
const sections = [];
|
|
188
|
+
this._items.forEach((item, index) => {
|
|
189
|
+
const section = document.createElement('section');
|
|
190
|
+
const header = document.createElement('div');
|
|
191
|
+
const button = document.createElement('button');
|
|
192
|
+
const arrow = document.createElement('span');
|
|
193
|
+
const title = document.createElement('span');
|
|
194
|
+
const extra = document.createElement('span');
|
|
195
|
+
const region = document.createElement('div');
|
|
196
|
+
const body = document.createElement('div');
|
|
197
|
+
const buttonId = `${this._uid}-button-${index}`;
|
|
198
|
+
const regionId = `${this._uid}-panel-${index}`;
|
|
199
|
+
section.className = 'panel';
|
|
200
|
+
section.part.add('panel');
|
|
201
|
+
header.className = 'header';
|
|
202
|
+
header.part.add('header');
|
|
203
|
+
button.type = 'button';
|
|
204
|
+
button.className = 'trigger';
|
|
205
|
+
button.part.add('trigger');
|
|
206
|
+
button.id = buttonId;
|
|
207
|
+
button.setAttribute('aria-controls', regionId);
|
|
208
|
+
arrow.className = 'arrow';
|
|
209
|
+
arrow.part.add('arrow');
|
|
210
|
+
arrow.setAttribute('aria-hidden', 'true');
|
|
211
|
+
arrow.hidden = item.showArrow === false;
|
|
212
|
+
title.className = 'title';
|
|
213
|
+
title.part.add('title');
|
|
214
|
+
title.textContent = item.title;
|
|
215
|
+
button.append(arrow, title);
|
|
216
|
+
extra.className = 'extra';
|
|
217
|
+
extra.part.add('extra');
|
|
218
|
+
extra.textContent = item.extra ?? '';
|
|
219
|
+
extra.hidden = !item.extra;
|
|
220
|
+
header.append(button, extra);
|
|
221
|
+
region.className = 'region';
|
|
222
|
+
region.part.add('region');
|
|
223
|
+
region.id = regionId;
|
|
224
|
+
region.setAttribute('role', 'region');
|
|
225
|
+
region.setAttribute('aria-labelledby', buttonId);
|
|
226
|
+
body.className = 'body';
|
|
227
|
+
body.part.add('body');
|
|
228
|
+
region.append(body);
|
|
229
|
+
section.append(header, region);
|
|
230
|
+
const record = { item, section, button, arrow, region, body, rendered: false, expanded: null, animation: null, targetHeight: 0 };
|
|
231
|
+
this._panels.set(item.key, record);
|
|
232
|
+
sections.push(section);
|
|
233
|
+
this._resizeObserver.observe(body, { box: 'border-box' });
|
|
234
|
+
button.addEventListener('click', event => { if (this.collapsible === 'icon' && event.detail !== 0 && !event.composedPath()[0]?.closest?.('.arrow'))
|
|
235
|
+
return; this.toggle(item.key); });
|
|
236
|
+
});
|
|
237
|
+
this._container.replaceChildren(...sections);
|
|
238
|
+
this._syncExpanded();
|
|
239
|
+
}
|
|
240
|
+
_syncExpanded() {
|
|
241
|
+
if (!this.isConnected)
|
|
242
|
+
return;
|
|
243
|
+
const duration = this._duration();
|
|
244
|
+
for (const [key, panel] of this._panels) {
|
|
245
|
+
const expanded = this._activeKeys.includes(key);
|
|
246
|
+
const disabled = Boolean(panel.item.disabled) || this.collapsible === 'disabled';
|
|
247
|
+
panel.section.classList.toggle('expanded', expanded);
|
|
248
|
+
panel.section.toggleAttribute('data-disabled', disabled);
|
|
249
|
+
panel.button.disabled = disabled;
|
|
250
|
+
panel.button.setAttribute('aria-expanded', String(expanded));
|
|
251
|
+
panel.arrow.style.transitionDuration = `${duration}ms`;
|
|
252
|
+
if (!expanded && panel.region.matches(':focus-within'))
|
|
253
|
+
panel.button.focus({ preventScroll: true });
|
|
254
|
+
panel.region.inert = !expanded;
|
|
255
|
+
panel.region.setAttribute('aria-hidden', String(!expanded));
|
|
256
|
+
if (expanded && !panel.rendered)
|
|
257
|
+
this._renderContent(panel);
|
|
258
|
+
if (panel.expanded !== expanded) {
|
|
259
|
+
const from = panel.animation ? Number.parseFloat(getComputedStyle(panel.region).height) : expanded ? 0 : panel.region.offsetHeight;
|
|
260
|
+
const animate = panel.expanded !== null && duration > 0;
|
|
261
|
+
panel.expanded = expanded;
|
|
262
|
+
if (animate)
|
|
263
|
+
this._transition(panel, from, duration);
|
|
264
|
+
else
|
|
265
|
+
this._finishTransition(panel);
|
|
266
|
+
}
|
|
267
|
+
else if (!panel.animation || duration === 0)
|
|
268
|
+
this._finishTransition(panel);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
_duration() {
|
|
272
|
+
if (!this.animated || this._motion.matches)
|
|
273
|
+
return 0;
|
|
274
|
+
if (this.hasAttribute('animation-duration'))
|
|
275
|
+
return this.animationDuration;
|
|
276
|
+
const token = getComputedStyle(this).getPropertyValue('--hy-collapse-animation-duration').trim();
|
|
277
|
+
if (!token)
|
|
278
|
+
return 200;
|
|
279
|
+
const value = Number.parseFloat(token) * (token.endsWith('ms') ? 1 : 1000);
|
|
280
|
+
return Number.isFinite(value) ? Math.max(0, Math.min(10_000, value)) : 200;
|
|
281
|
+
}
|
|
282
|
+
_transition(panel, from, duration) {
|
|
283
|
+
if (!Number.isFinite(from))
|
|
284
|
+
from = 0;
|
|
285
|
+
panel.animation?.cancel();
|
|
286
|
+
panel.animation = null;
|
|
287
|
+
panel.region.hidden = false;
|
|
288
|
+
const to = panel.expanded ? panel.body.offsetHeight : 0;
|
|
289
|
+
panel.targetHeight = to;
|
|
290
|
+
if (!duration || from === to) {
|
|
291
|
+
this._finishTransition(panel);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
panel.region.style.overflow = 'hidden';
|
|
295
|
+
const token = getComputedStyle(this).getPropertyValue('--hy-collapse-animation-easing').trim() || 'ease';
|
|
296
|
+
const easing = CSS.supports('transition-timing-function', token) ? token : 'ease';
|
|
297
|
+
const animation = panel.region.animate([{ height: `${from}px` }, { height: `${to}px` }], { duration, easing, fill: 'both' });
|
|
298
|
+
panel.animation = animation;
|
|
299
|
+
void animation.finished.then(() => {
|
|
300
|
+
if (panel.animation === animation)
|
|
301
|
+
this._finishTransition(panel);
|
|
302
|
+
}, () => { });
|
|
303
|
+
}
|
|
304
|
+
_finishTransition(panel) {
|
|
305
|
+
panel.animation?.cancel();
|
|
306
|
+
panel.animation = null;
|
|
307
|
+
panel.region.style.removeProperty('overflow');
|
|
308
|
+
panel.region.hidden = !panel.expanded;
|
|
309
|
+
if (!panel.expanded && this.destroyOnHidden && panel.rendered) {
|
|
310
|
+
panel.body.replaceChildren();
|
|
311
|
+
panel.rendered = false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
_onMotionChange = () => { this._syncExpanded(); };
|
|
315
|
+
_renderContent(panel) {
|
|
316
|
+
const source = typeof panel.item.content === 'function' ? panel.item.content() : panel.item.content;
|
|
317
|
+
if (source instanceof Node)
|
|
318
|
+
panel.body.replaceChildren(source);
|
|
319
|
+
else
|
|
320
|
+
panel.body.textContent = source == null ? '' : String(source);
|
|
321
|
+
panel.rendered = true;
|
|
322
|
+
}
|
|
323
|
+
_normalizeActiveKeys() {
|
|
324
|
+
if (this._items.length) {
|
|
325
|
+
const valid = new Set(this._items.map(item => item.key));
|
|
326
|
+
this._activeKeys = this._activeKeys.filter(key => valid.has(key));
|
|
327
|
+
}
|
|
328
|
+
if (this.accordion)
|
|
329
|
+
this._activeKeys = this._activeKeys.slice(0, 1);
|
|
330
|
+
}
|
|
331
|
+
_reflectActiveKeys() {
|
|
332
|
+
const value = JSON.stringify(this._activeKeys);
|
|
333
|
+
if (this.getAttribute('active-keys') !== value)
|
|
334
|
+
this.setAttribute('active-keys', value);
|
|
335
|
+
}
|
|
336
|
+
_syncLabel() {
|
|
337
|
+
const label = this.getAttribute('aria-label');
|
|
338
|
+
if (label)
|
|
339
|
+
this._container.setAttribute('aria-label', label);
|
|
340
|
+
else
|
|
341
|
+
this._container.removeAttribute('aria-label');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
export function defineCollapseComponents() {
|
|
345
|
+
if (!customElements.get('hy-collapse'))
|
|
346
|
+
customElements.define('hy-collapse', HYCollapse);
|
|
347
|
+
}
|
|
348
|
+
//# sourceMappingURL=collapse.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HYComponentProps, HYReason, HYUIEvent } from './types.js';
|
|
2
|
+
export declare class HYButtonController {
|
|
3
|
+
#private;
|
|
4
|
+
readonly key: string;
|
|
5
|
+
private readonly emit;
|
|
6
|
+
constructor(key: string, props: HYComponentProps['Button'], emit: (event: HYUIEvent) => void);
|
|
7
|
+
update(props: HYComponentProps['Button']): void;
|
|
8
|
+
/** Adapters pass the same gesture id for overlapping tap/click/accessibility callbacks. */
|
|
9
|
+
activate(reason?: HYReason, gesture?: string | number): boolean;
|
|
10
|
+
dispose(): void;
|
|
11
|
+
}
|
|
12
|
+
export declare class HYToggleController {
|
|
13
|
+
#private;
|
|
14
|
+
readonly key: string;
|
|
15
|
+
private readonly emit;
|
|
16
|
+
constructor(key: string, props: HYComponentProps['Toggle'], emit: (event: HYUIEvent) => void);
|
|
17
|
+
update(props: HYComponentProps['Toggle']): void;
|
|
18
|
+
choose(checked: boolean, reason?: HYReason): boolean;
|
|
19
|
+
dispose(): void;
|
|
20
|
+
}
|
|
21
|
+
export declare class HYSliderController {
|
|
22
|
+
#private;
|
|
23
|
+
readonly key: string;
|
|
24
|
+
private readonly emit;
|
|
25
|
+
constructor(key: string, props: HYComponentProps['Slider'], emit: (event: HYUIEvent) => void);
|
|
26
|
+
get value(): number;
|
|
27
|
+
update(props: HYComponentProps['Slider']): void;
|
|
28
|
+
change(value: number, reason?: HYReason): boolean;
|
|
29
|
+
commit(reason?: HYReason): boolean;
|
|
30
|
+
cancel(reason?: HYReason): void;
|
|
31
|
+
dispose(): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { snapshot } from './data.js';
|
|
2
|
+
import { hyKey, hyValidateProps } from './validation.js';
|
|
3
|
+
export class HYButtonController {
|
|
4
|
+
key;
|
|
5
|
+
emit;
|
|
6
|
+
#props;
|
|
7
|
+
#disposed = false;
|
|
8
|
+
#emitting = false;
|
|
9
|
+
#transaction = 0;
|
|
10
|
+
#gestures = new Set();
|
|
11
|
+
constructor(key, props, emit) {
|
|
12
|
+
this.key = key;
|
|
13
|
+
this.emit = emit;
|
|
14
|
+
hyKey(key);
|
|
15
|
+
this.update(props);
|
|
16
|
+
}
|
|
17
|
+
update(props) {
|
|
18
|
+
if (this.#disposed)
|
|
19
|
+
return;
|
|
20
|
+
hyValidateProps('Button', props, { key: this.key });
|
|
21
|
+
this.#props = snapshot(props);
|
|
22
|
+
}
|
|
23
|
+
/** Adapters pass the same gesture id for overlapping tap/click/accessibility callbacks. */
|
|
24
|
+
activate(reason = 'pointer', gesture) {
|
|
25
|
+
if (this.#disposed || this.#emitting || this.#props.disabled || this.#props.busy || this.#props.visible === false)
|
|
26
|
+
return false;
|
|
27
|
+
if (gesture !== undefined) {
|
|
28
|
+
if (this.#gestures.has(gesture))
|
|
29
|
+
return false;
|
|
30
|
+
this.#gestures.add(gesture);
|
|
31
|
+
if (this.#gestures.size > 64)
|
|
32
|
+
this.#gestures.delete(this.#gestures.values().next().value);
|
|
33
|
+
}
|
|
34
|
+
this.#emitting = true;
|
|
35
|
+
try {
|
|
36
|
+
this.emit({ key: this.key, type: 'activate', reason, transaction: ++this.#transaction });
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
this.#emitting = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
dispose() { this.#disposed = true; this.#gestures.clear(); }
|
|
44
|
+
}
|
|
45
|
+
export class HYToggleController {
|
|
46
|
+
key;
|
|
47
|
+
emit;
|
|
48
|
+
#props;
|
|
49
|
+
#disposed = false;
|
|
50
|
+
#emitting = false;
|
|
51
|
+
#transaction = 0;
|
|
52
|
+
constructor(key, props, emit) {
|
|
53
|
+
this.key = key;
|
|
54
|
+
this.emit = emit;
|
|
55
|
+
hyKey(key);
|
|
56
|
+
this.update(props);
|
|
57
|
+
}
|
|
58
|
+
update(props) {
|
|
59
|
+
if (this.#disposed)
|
|
60
|
+
return;
|
|
61
|
+
hyValidateProps('Toggle', props, { key: this.key });
|
|
62
|
+
this.#props = snapshot(props);
|
|
63
|
+
}
|
|
64
|
+
choose(checked, reason = 'pointer') {
|
|
65
|
+
if (typeof checked !== 'boolean' || this.#disposed || this.#emitting || this.#props.disabled || this.#props.visible === false || checked === this.#props.checked)
|
|
66
|
+
return false;
|
|
67
|
+
this.#emitting = true;
|
|
68
|
+
const event = { key: this.key, value: checked, reason, transaction: ++this.#transaction };
|
|
69
|
+
try {
|
|
70
|
+
this.emit({ ...event, type: 'valueChange' });
|
|
71
|
+
if (this.#disposed)
|
|
72
|
+
return false;
|
|
73
|
+
this.emit({ ...event, type: 'commit' });
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
this.#emitting = false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
dispose() { this.#disposed = true; }
|
|
81
|
+
}
|
|
82
|
+
export class HYSliderController {
|
|
83
|
+
key;
|
|
84
|
+
emit;
|
|
85
|
+
#props;
|
|
86
|
+
#draft = 0;
|
|
87
|
+
#transaction = 0;
|
|
88
|
+
#active = 0;
|
|
89
|
+
#disposed = false;
|
|
90
|
+
constructor(key, props, emit) {
|
|
91
|
+
this.key = key;
|
|
92
|
+
this.emit = emit;
|
|
93
|
+
hyKey(key);
|
|
94
|
+
this.update(props);
|
|
95
|
+
}
|
|
96
|
+
get value() { return this.#draft; }
|
|
97
|
+
update(props) {
|
|
98
|
+
if (this.#disposed)
|
|
99
|
+
return;
|
|
100
|
+
hyValidateProps('Slider', props, { key: this.key });
|
|
101
|
+
if (!this.#props || props.value !== this.#props.value || props.min !== this.#props.min || props.max !== this.#props.max || props.step !== this.#props.step || props.disabled || props.visible === false) {
|
|
102
|
+
this.#draft = props.value;
|
|
103
|
+
this.#active = 0;
|
|
104
|
+
}
|
|
105
|
+
this.#props = snapshot(props);
|
|
106
|
+
}
|
|
107
|
+
change(value, reason = 'pointer') {
|
|
108
|
+
if (this.#disposed || this.#props.disabled || this.#props.visible === false || !Number.isFinite(value))
|
|
109
|
+
return false;
|
|
110
|
+
const { min, max, step } = this.#props;
|
|
111
|
+
const quantized = min + Math.round((Math.max(min, Math.min(max, value)) - min) / step) * step;
|
|
112
|
+
const next = Math.max(min, Math.min(max, Number(quantized.toPrecision(14))));
|
|
113
|
+
if (next === this.#draft)
|
|
114
|
+
return false;
|
|
115
|
+
this.#draft = next;
|
|
116
|
+
this.#active ||= ++this.#transaction;
|
|
117
|
+
this.emit({ key: this.key, type: 'valueChange', value: next, reason, transaction: this.#active });
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
commit(reason = 'pointer') {
|
|
121
|
+
if (this.#disposed || this.#props.disabled || !this.#active)
|
|
122
|
+
return false;
|
|
123
|
+
const transaction = this.#active;
|
|
124
|
+
this.#active = 0;
|
|
125
|
+
if (this.#draft === this.#props.value)
|
|
126
|
+
return false;
|
|
127
|
+
try {
|
|
128
|
+
this.emit({ key: this.key, type: 'commit', value: this.#draft, reason, transaction });
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
this.#draft = this.#props.value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
cancel(reason = 'pointer') {
|
|
136
|
+
if (this.#disposed || !this.#active)
|
|
137
|
+
return;
|
|
138
|
+
const transaction = this.#active;
|
|
139
|
+
this.#active = 0;
|
|
140
|
+
this.#draft = this.#props.value;
|
|
141
|
+
this.emit({ key: this.key, type: 'cancel', reason, transaction });
|
|
142
|
+
}
|
|
143
|
+
dispose() { this.#disposed = true; this.#active = 0; }
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Internal structural comparison for immutable protocol data; never serializes props. */
|
|
2
|
+
export declare function equalData(a: unknown, b: unknown): boolean;
|
|
3
|
+
export declare function isSnapshot(value: object): boolean;
|
|
4
|
+
export declare function snapshot<T>(value: T): T;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Internal structural comparison for immutable protocol data; never serializes props. */
|
|
2
|
+
export function equalData(a, b) {
|
|
3
|
+
if (Object.is(a, b))
|
|
4
|
+
return true;
|
|
5
|
+
if (!a || !b || typeof a !== 'object' || typeof b !== 'object')
|
|
6
|
+
return false;
|
|
7
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
8
|
+
return false;
|
|
9
|
+
const left = a, right = b;
|
|
10
|
+
const keys = Object.keys(left);
|
|
11
|
+
return keys.length === Object.keys(right).length
|
|
12
|
+
&& keys.every(key => Object.hasOwn(right, key) && equalData(left[key], right[key]));
|
|
13
|
+
}
|
|
14
|
+
/** Called only after schema validation, so cycles and platform handles are impossible. */
|
|
15
|
+
const snapshots = new WeakSet();
|
|
16
|
+
export function isSnapshot(value) { return snapshots.has(value); }
|
|
17
|
+
export function snapshot(value) {
|
|
18
|
+
if (!value || typeof value !== 'object')
|
|
19
|
+
return value;
|
|
20
|
+
if (snapshots.has(value))
|
|
21
|
+
return value;
|
|
22
|
+
const result = Array.isArray(value) ? Object.freeze(value.map(snapshot))
|
|
23
|
+
: Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshot(item)])));
|
|
24
|
+
snapshots.add(result);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=data.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type * from './types.js';
|
|
2
|
+
export { HYUIError, HY_COMPONENTS, HY_FEATURES, hyValidateCapabilities, hyValidateProps, hyValidateNode, hySupports, hyKey, hyNode } from './validation.js';
|
|
3
|
+
export { HYSelectionController } from './selection.js';
|
|
4
|
+
export { HYButtonController, HYToggleController, HYSliderController } from './actions.js';
|
|
5
|
+
export { HYTextController } from './text.js';
|
|
6
|
+
export type { HYTextView, HYTextViewState } from './text.js';
|
|
7
|
+
export { HYPlatformViewController } from './platform-view.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { HYUIError, HY_COMPONENTS, HY_FEATURES, hyValidateCapabilities, hyValidateProps, hyValidateNode, hySupports, hyKey, hyNode } from './validation.js';
|
|
2
|
+
export { HYSelectionController } from './selection.js';
|
|
3
|
+
export { HYButtonController, HYToggleController, HYSliderController } from './actions.js';
|
|
4
|
+
export { HYTextController } from './text.js';
|
|
5
|
+
export { HYPlatformViewController } from './platform-view.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HYBounds, HYPlatformView } from './types.js';
|
|
2
|
+
/** Backend-owned resource lifetime. No GPU acquire/present or platform handles in page state. */
|
|
3
|
+
export declare class HYPlatformViewController {
|
|
4
|
+
#private;
|
|
5
|
+
private readonly factory;
|
|
6
|
+
private resourceId;
|
|
7
|
+
private shown;
|
|
8
|
+
constructor(factory: () => HYPlatformView, resourceId: string, shown?: boolean);
|
|
9
|
+
mount(): void;
|
|
10
|
+
update(resourceId: string): void;
|
|
11
|
+
layout(bounds: HYBounds): void;
|
|
12
|
+
visible(value: boolean): void;
|
|
13
|
+
focus(): void;
|
|
14
|
+
suspend(): void;
|
|
15
|
+
resume(): void;
|
|
16
|
+
dispose(): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { equalData, snapshot } from './data.js';
|
|
2
|
+
import { HYUIError } from './validation.js';
|
|
3
|
+
/** Backend-owned resource lifetime. No GPU acquire/present or platform handles in page state. */
|
|
4
|
+
export class HYPlatformViewController {
|
|
5
|
+
factory;
|
|
6
|
+
resourceId;
|
|
7
|
+
shown;
|
|
8
|
+
#view;
|
|
9
|
+
#disposed = false;
|
|
10
|
+
#suspended = false;
|
|
11
|
+
#bounds;
|
|
12
|
+
constructor(factory, resourceId, shown = true) {
|
|
13
|
+
this.factory = factory;
|
|
14
|
+
this.resourceId = resourceId;
|
|
15
|
+
this.shown = shown;
|
|
16
|
+
this.#validateId(resourceId);
|
|
17
|
+
}
|
|
18
|
+
#validateId(id) { if (typeof id !== 'string' || !id.trim())
|
|
19
|
+
throw new HYUIError('invalid-props', 'PlatformView needs a resourceId'); }
|
|
20
|
+
mount() {
|
|
21
|
+
if (this.#disposed || this.#view)
|
|
22
|
+
throw new HYUIError('lifecycle', 'PlatformView disposed or already mounted');
|
|
23
|
+
this.#view = this.factory();
|
|
24
|
+
try {
|
|
25
|
+
this.#view.mount();
|
|
26
|
+
this.#view.update(this.resourceId);
|
|
27
|
+
if (this.#bounds)
|
|
28
|
+
this.#view.layout(this.#bounds);
|
|
29
|
+
this.#view.visible(this.shown && !this.#suspended);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
this.dispose();
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
update(resourceId) {
|
|
37
|
+
if (this.#disposed)
|
|
38
|
+
return;
|
|
39
|
+
this.#validateId(resourceId);
|
|
40
|
+
if (resourceId === this.resourceId)
|
|
41
|
+
return;
|
|
42
|
+
this.resourceId = resourceId;
|
|
43
|
+
this.#view?.update(resourceId);
|
|
44
|
+
}
|
|
45
|
+
layout(bounds) {
|
|
46
|
+
if (this.#disposed)
|
|
47
|
+
return;
|
|
48
|
+
if (![bounds.x, bounds.y, bounds.width, bounds.height, bounds.scale].every(Number.isFinite)
|
|
49
|
+
|| bounds.width < 0 || bounds.height < 0 || bounds.scale <= 0)
|
|
50
|
+
throw new HYUIError('invalid-props', 'Invalid PlatformView bounds');
|
|
51
|
+
if (equalData(bounds, this.#bounds))
|
|
52
|
+
return;
|
|
53
|
+
this.#bounds = snapshot(bounds);
|
|
54
|
+
this.#view?.layout(this.#bounds);
|
|
55
|
+
}
|
|
56
|
+
visible(value) {
|
|
57
|
+
if (this.#disposed || this.shown === value)
|
|
58
|
+
return;
|
|
59
|
+
this.shown = value;
|
|
60
|
+
if (!this.#suspended)
|
|
61
|
+
this.#view?.visible(value);
|
|
62
|
+
}
|
|
63
|
+
focus() { if (!this.#disposed && !this.#suspended && this.shown)
|
|
64
|
+
this.#view?.focus(); }
|
|
65
|
+
suspend() { if (!this.#disposed && !this.#suspended) {
|
|
66
|
+
this.#suspended = true;
|
|
67
|
+
if (this.shown)
|
|
68
|
+
this.#view?.visible(false);
|
|
69
|
+
} }
|
|
70
|
+
resume() { if (!this.#disposed && this.#suspended) {
|
|
71
|
+
this.#suspended = false;
|
|
72
|
+
if (this.shown)
|
|
73
|
+
this.#view?.visible(true);
|
|
74
|
+
} }
|
|
75
|
+
dispose() {
|
|
76
|
+
if (this.#disposed)
|
|
77
|
+
return;
|
|
78
|
+
this.#disposed = true;
|
|
79
|
+
const view = this.#view;
|
|
80
|
+
this.#view = undefined;
|
|
81
|
+
view?.dispose();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=platform-view.js.map
|