@haiyue/ui 0.1.1 → 0.2.1

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +504 -1
  3. package/dist/adapters/web/controls.d.ts +19 -0
  4. package/dist/adapters/web/controls.js +284 -0
  5. package/dist/adapters/web/dom.d.ts +10 -0
  6. package/dist/adapters/web/dom.js +98 -0
  7. package/dist/adapters/web/index.d.ts +3 -0
  8. package/dist/adapters/web/index.js +3 -0
  9. package/dist/adapters/web/renderer.d.ts +18 -0
  10. package/dist/adapters/web/renderer.js +545 -0
  11. package/dist/adapters/web/styles.d.ts +3 -0
  12. package/dist/adapters/web/styles.js +62 -0
  13. package/dist/adapters/web/types.d.ts +46 -0
  14. package/dist/adapters/web/types.js +2 -0
  15. package/dist/affix.d.ts +35 -0
  16. package/dist/affix.js +137 -0
  17. package/dist/badge.d.ts +45 -0
  18. package/dist/badge.js +168 -0
  19. package/dist/border-beam.js +3 -1
  20. package/dist/breadcrumb.d.ts +37 -0
  21. package/dist/breadcrumb.js +210 -0
  22. package/dist/button-group.d.ts +12 -0
  23. package/dist/button-group.js +72 -0
  24. package/dist/button.d.ts +15 -1
  25. package/dist/button.js +101 -12
  26. package/dist/cascader.d.ts +65 -0
  27. package/dist/cascader.js +479 -0
  28. package/dist/collapse.d.ts +55 -0
  29. package/dist/collapse.js +261 -0
  30. package/dist/core/actions.d.ts +32 -0
  31. package/dist/core/actions.js +145 -0
  32. package/dist/core/data.d.ts +4 -0
  33. package/dist/core/data.js +27 -0
  34. package/dist/core/index.d.ts +7 -0
  35. package/dist/core/index.js +6 -0
  36. package/dist/core/platform-view.d.ts +17 -0
  37. package/dist/core/platform-view.js +84 -0
  38. package/dist/core/selection.d.ts +14 -0
  39. package/dist/core/selection.js +93 -0
  40. package/dist/core/text.d.ts +31 -0
  41. package/dist/core/text.js +154 -0
  42. package/dist/core/types.d.ts +250 -0
  43. package/dist/core/types.js +2 -0
  44. package/dist/core/validation.d.ts +24 -0
  45. package/dist/core/validation.js +238 -0
  46. package/dist/date-picker.d.ts +60 -0
  47. package/dist/date-picker.js +355 -0
  48. package/dist/dialog.d.ts +4 -0
  49. package/dist/dialog.js +23 -5
  50. package/dist/divider.d.ts +33 -0
  51. package/dist/divider.js +113 -0
  52. package/dist/draggable.d.ts +78 -0
  53. package/dist/draggable.js +255 -0
  54. package/dist/expandable.d.ts +32 -0
  55. package/dist/expandable.js +136 -0
  56. package/dist/index.d.ts +47 -1
  57. package/dist/index.js +69 -0
  58. package/dist/input.d.ts +30 -1
  59. package/dist/input.js +166 -54
  60. package/dist/portable/index.d.ts +4 -0
  61. package/dist/portable/index.js +3 -0
  62. package/dist/portable/page.d.ts +38 -0
  63. package/dist/portable/page.js +361 -0
  64. package/dist/portable/theme.d.ts +19 -0
  65. package/dist/portable/theme.js +40 -0
  66. package/dist/portable/tree.d.ts +10 -0
  67. package/dist/portable/tree.js +96 -0
  68. package/dist/range.d.ts +3 -0
  69. package/dist/range.js +16 -5
  70. package/dist/rate.d.ts +46 -0
  71. package/dist/rate.js +193 -0
  72. package/dist/resizable.d.ts +97 -0
  73. package/dist/resizable.js +426 -0
  74. package/dist/skeleton.d.ts +41 -0
  75. package/dist/skeleton.js +141 -0
  76. package/dist/steps.d.ts +45 -0
  77. package/dist/steps.js +203 -0
  78. package/dist/table.d.ts +106 -0
  79. package/dist/table.js +689 -0
  80. package/dist/tabs.d.ts +7 -0
  81. package/dist/tabs.js +90 -16
  82. package/dist/tag.d.ts +55 -0
  83. package/dist/tag.js +225 -0
  84. package/dist/time-picker.d.ts +62 -0
  85. package/dist/time-picker.js +298 -0
  86. package/dist/timeline.d.ts +48 -0
  87. package/dist/timeline.js +201 -0
  88. package/dist/toast.d.ts +55 -0
  89. package/dist/toast.js +224 -0
  90. package/dist/tour.d.ts +89 -0
  91. package/dist/tour.js +306 -0
  92. package/dist/virtual-keyboard.d.ts +95 -0
  93. package/dist/virtual-keyboard.js +432 -0
  94. package/dist/virtual-list.js +12 -10
  95. package/dist/virtual-mouse.d.ts +82 -0
  96. package/dist/virtual-mouse.js +358 -0
  97. package/dist/watermark.d.ts +44 -0
  98. package/dist/watermark.js +148 -0
  99. package/package.json +135 -7
@@ -0,0 +1,298 @@
1
+ function parseTime(value) {
2
+ const match = /^(\d{2}):(\d{2})(?::(\d{2}))?$/u.exec(String(value ?? ''));
3
+ if (!match)
4
+ return null;
5
+ const hour = Number(match[1]);
6
+ const minute = Number(match[2]);
7
+ const second = Number(match[3] ?? 0);
8
+ return hour < 24 && minute < 60 && second < 60 ? { hour, minute, second } : null;
9
+ }
10
+ function pad(value) { return String(value).padStart(2, '0'); }
11
+ function formatTime(parts, showSeconds) {
12
+ return `${pad(parts.hour)}:${pad(parts.minute)}${showSeconds ? `:${pad(parts.second)}` : ''}`;
13
+ }
14
+ function timeNumber(parts) { return parts.hour * 3600 + parts.minute * 60 + parts.second; }
15
+ function normalizeStep(value) {
16
+ const number = Math.floor(Number(value));
17
+ return Number.isFinite(number) && number > 0 && number < 60 ? number : 1;
18
+ }
19
+ /** Accessible column-based time picker with stable 24-hour values. */
20
+ export class HYTimePicker extends HTMLElement {
21
+ _control = document.createElement('div');
22
+ _trigger = document.createElement('button');
23
+ _label = document.createElement('span');
24
+ _arrow = document.createElement('span');
25
+ _clear = document.createElement('button');
26
+ _panel = document.createElement('div');
27
+ _panelId = `hy-time-picker-${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
28
+ _value = '';
29
+ _draft = { hour: 0, minute: 0, second: 0 };
30
+ _reflecting = false;
31
+ _outsideAbort = null;
32
+ static get observedAttributes() {
33
+ return ['value', 'min', 'max', 'placeholder', 'disabled', 'clearable', 'open', 'show-seconds', 'minute-step', 'second-step', 'aria-label'];
34
+ }
35
+ constructor() {
36
+ super();
37
+ const root = this.attachShadow({ mode: 'open' });
38
+ const style = document.createElement('style');
39
+ style.textContent = `
40
+ :host { position: relative; display: inline-block; min-width: 150px; color: var(--hy-text-color, #eef3ff); font: 12px/1.4 system-ui, sans-serif; }
41
+ .control { display: grid; grid-template-columns: minmax(0, 1fr) auto; height: var(--hy-time-picker-height, 34px); box-sizing: border-box; border: 1px solid var(--hy-border-color, #3d4654); border-radius: var(--hy-time-picker-radius, 6px); background: var(--hy-input-bg-color, #111722); }
42
+ :host(:not([disabled])) .control:hover { border-color: var(--hy-hover-border-color, #596779); }
43
+ :host(:focus-within) .control, :host([open]) .control { border-color: var(--hy-focus-border-color, #79a8ff); box-shadow: 0 0 0 2px var(--hy-focus-ring-color, rgba(121, 168, 255, .22)); }
44
+ .trigger { display: flex; min-width: 0; align-items: center; gap: 8px; padding: 0 8px 0 10px; overflow: hidden; border: 0; color: inherit; background: transparent; cursor: pointer; font: inherit; text-align: left; }
45
+ .label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
46
+ .label.placeholder { color: var(--hy-muted-color, #788697); }
47
+ .arrow { color: var(--hy-muted-color, #788697); font-size: 14px; }
48
+ .clear { width: 28px; padding: 0; border: 0; color: var(--hy-muted-color, #788697); background: transparent; cursor: pointer; font: 15px/1 system-ui, sans-serif; }
49
+ .clear:hover { color: var(--hy-text-color, #eef3ff); } .clear[hidden], .panel[hidden] { display: none; }
50
+ .trigger:focus-visible, .clear:focus-visible { outline: none; }
51
+ :host([disabled]) { opacity: .52; } :host([disabled]) .trigger, :host([disabled]) .clear { cursor: not-allowed; }
52
+ .panel { position: absolute; z-index: var(--hy-time-picker-z-index, 50); top: calc(100% + 6px); left: 0; width: max-content; min-width: 100%; box-sizing: border-box; border: 1px solid var(--hy-border-color, #3d4654); border-radius: var(--hy-time-picker-panel-radius, 8px); background: var(--hy-menu-bg-color, var(--hy-surface-elevated-color, #171d27)); box-shadow: 0 12px 32px var(--hy-menu-shadow-color, rgba(0, 0, 0, .36)); }
53
+ .columns { display: flex; height: var(--hy-time-picker-panel-height, 196px); overflow: hidden; }
54
+ .column { width: var(--hy-time-picker-column-width, 58px); margin: 0; padding: 4px; overflow-y: auto; list-style: none; scrollbar-color: var(--hy-border-strong-color, #596779) transparent; scrollbar-width: thin; }
55
+ .column + .column { border-left: 1px solid var(--hy-border-color, #3d4654); }
56
+ .option { width: 100%; height: 30px; padding: 0; border: 0; border-radius: 4px; color: var(--hy-secondary-text-color, #aebbd0); background: transparent; cursor: pointer; font: inherit; }
57
+ .option:hover, .option:focus-visible { outline: none; color: var(--hy-text-color, #eef3ff); background: var(--hy-hover-bg-color, #273241); }
58
+ .option[aria-selected="true"] { color: var(--hy-selected-text-color, #fff); background: var(--hy-selected-bg-color, #554cc6); }
59
+ .footer { display: flex; justify-content: space-between; gap: 8px; padding: 7px 8px; border-top: 1px solid var(--hy-border-color, #3d4654); }
60
+ .footer button { min-width: 42px; height: 27px; padding: 0 8px; border: 1px solid transparent; border-radius: 4px; color: var(--hy-accent-color, #79a8ff); background: transparent; cursor: pointer; font: inherit; }
61
+ .footer button:hover:not(:disabled) { background: var(--hy-hover-bg-color, #273241); }
62
+ .footer .confirm { color: var(--hy-selected-text-color, #fff); background: var(--hy-selected-bg-color, #554cc6); }
63
+ .footer button:focus-visible { outline: 2px solid var(--hy-focus-border-color, #79a8ff); outline-offset: 1px; }
64
+ .footer button:disabled { cursor: not-allowed; opacity: .35; }
65
+ `;
66
+ this._control.className = 'control';
67
+ this._control.part.add('control');
68
+ this._trigger.type = 'button';
69
+ this._trigger.className = 'trigger';
70
+ this._trigger.part.add('trigger');
71
+ this._trigger.setAttribute('aria-haspopup', 'dialog');
72
+ this._trigger.setAttribute('aria-controls', this._panelId);
73
+ this._label.className = 'label';
74
+ this._label.part.add('label');
75
+ this._arrow.className = 'arrow';
76
+ this._arrow.part.add('arrow');
77
+ this._arrow.setAttribute('aria-hidden', 'true');
78
+ this._arrow.textContent = '◷';
79
+ this._trigger.append(this._label, this._arrow);
80
+ this._clear.type = 'button';
81
+ this._clear.className = 'clear';
82
+ this._clear.part.add('clear');
83
+ this._clear.setAttribute('aria-label', 'Clear time');
84
+ this._clear.textContent = '×';
85
+ this._panel.id = this._panelId;
86
+ this._panel.className = 'panel';
87
+ this._panel.part.add('panel');
88
+ this._panel.setAttribute('role', 'dialog');
89
+ this._control.append(this._trigger, this._clear);
90
+ root.append(style, this._control, this._panel);
91
+ this._trigger.addEventListener('click', () => { if (!this.disabled)
92
+ this.open = !this.open; });
93
+ this._trigger.addEventListener('keydown', event => { if (event.key === 'ArrowDown' && !this.disabled) {
94
+ event.preventDefault();
95
+ this.open = true;
96
+ queueMicrotask(() => this._focusSelected(0));
97
+ }
98
+ else if (event.key === 'Escape' && this.open) {
99
+ event.preventDefault();
100
+ this.open = false;
101
+ } });
102
+ this._clear.addEventListener('click', event => { event.stopPropagation(); if (!this.disabled)
103
+ this._commit(''); });
104
+ }
105
+ connectedCallback() { this._syncAttributes(); this._render(); this._syncOutsideListener(); }
106
+ disconnectedCallback() { this._outsideAbort?.abort(); this._outsideAbort = null; }
107
+ attributeChangedCallback(name, oldValue, newValue) {
108
+ if (oldValue === newValue || this._reflecting)
109
+ return;
110
+ if (['value', 'min', 'max', 'show-seconds', 'minute-step', 'second-step'].includes(name)) {
111
+ this._value = this._normalizeValue(this.getAttribute('value') ?? '');
112
+ this._reflectValue();
113
+ }
114
+ if (name === 'open' && this.open)
115
+ this._setDraft();
116
+ this._render();
117
+ if (name === 'open') {
118
+ this._syncOutsideListener();
119
+ this._dispatchOpenChange();
120
+ }
121
+ }
122
+ get value() { return this._value; }
123
+ set value(value) { this._value = this._normalizeValue(value); this._reflectValue(); this._render(); }
124
+ get open() { return this.hasAttribute('open'); }
125
+ set open(value) { this.toggleAttribute('open', Boolean(value)); }
126
+ get disabled() { return this.hasAttribute('disabled'); }
127
+ set disabled(value) { this.toggleAttribute('disabled', Boolean(value)); }
128
+ get clearable() { return this.getAttribute('clearable') !== 'false'; }
129
+ set clearable(value) { this.setAttribute('clearable', String(Boolean(value))); }
130
+ get placeholder() { return this.getAttribute('placeholder') ?? 'Select time'; }
131
+ set placeholder(value) { this.setAttribute('placeholder', String(value)); }
132
+ get showSeconds() { return this.hasAttribute('show-seconds'); }
133
+ set showSeconds(value) { this.toggleAttribute('show-seconds', Boolean(value)); }
134
+ get minuteStep() { return normalizeStep(this.getAttribute('minute-step')); }
135
+ set minuteStep(value) { this.setAttribute('minute-step', String(normalizeStep(value))); }
136
+ get secondStep() { return normalizeStep(this.getAttribute('second-step')); }
137
+ set secondStep(value) { this.setAttribute('second-step', String(normalizeStep(value))); }
138
+ get min() { return parseTime(this.getAttribute('min')) ? this.getAttribute('min') : ''; }
139
+ set min(value) { if (parseTime(value))
140
+ this.setAttribute('min', value);
141
+ else
142
+ this.removeAttribute('min'); }
143
+ get max() { return parseTime(this.getAttribute('max')) ? this.getAttribute('max') : ''; }
144
+ set max(value) { if (parseTime(value))
145
+ this.setAttribute('max', value);
146
+ else
147
+ this.removeAttribute('max'); }
148
+ _syncAttributes() { this._value = this._normalizeValue(this.getAttribute('value') ?? ''); this._reflectValue(); this._setDraft(); }
149
+ _normalizeValue(value) {
150
+ const parts = parseTime(value);
151
+ if (!parts)
152
+ return '';
153
+ const normalized = formatTime(parts, this.showSeconds);
154
+ return this._isAllowed(parts) ? normalized : '';
155
+ }
156
+ _reflectValue() { if (this.getAttribute('value') === this._value)
157
+ return; this._reflecting = true; if (this._value)
158
+ this.setAttribute('value', this._value);
159
+ else
160
+ this.removeAttribute('value'); this._reflecting = false; }
161
+ _isAllowed(parts) { const value = timeNumber(parts); const min = parseTime(this.min); const max = parseTime(this.max); return (!min || value >= timeNumber(min)) && (!max || value <= timeNumber(max)); }
162
+ _setDraft() {
163
+ const selected = parseTime(this._value);
164
+ if (selected) {
165
+ this._draft = selected;
166
+ return;
167
+ }
168
+ const now = new Date();
169
+ this._draft = { hour: now.getHours(), minute: Math.floor(now.getMinutes() / this.minuteStep) * this.minuteStep, second: Math.floor(now.getSeconds() / this.secondStep) * this.secondStep };
170
+ }
171
+ _render() {
172
+ this._label.textContent = this._value || this.placeholder;
173
+ this._label.classList.toggle('placeholder', !this._value);
174
+ this._trigger.disabled = this.disabled;
175
+ this._trigger.setAttribute('aria-expanded', String(this.open));
176
+ const name = this.getAttribute('aria-label');
177
+ if (name)
178
+ this._trigger.setAttribute('aria-label', name);
179
+ else
180
+ this._trigger.removeAttribute('aria-label');
181
+ this._clear.hidden = !this.clearable || !this._value;
182
+ this._clear.disabled = this.disabled;
183
+ this._panel.hidden = !this.open;
184
+ if (this.open)
185
+ this._renderPanel();
186
+ else
187
+ this._panel.replaceChildren();
188
+ }
189
+ _renderPanel() {
190
+ const columns = document.createElement('div');
191
+ columns.className = 'columns';
192
+ columns.part.add('columns');
193
+ columns.append(this._createColumn('hour', 24, 1, this._draft.hour, 0), this._createColumn('minute', 60, this.minuteStep, this._draft.minute, 1));
194
+ if (this.showSeconds)
195
+ columns.append(this._createColumn('second', 60, this.secondStep, this._draft.second, 2));
196
+ const footer = document.createElement('div');
197
+ footer.className = 'footer';
198
+ footer.part.add('footer');
199
+ const now = document.createElement('button');
200
+ const confirm = document.createElement('button');
201
+ now.type = confirm.type = 'button';
202
+ now.textContent = 'Now';
203
+ confirm.textContent = 'OK';
204
+ confirm.className = 'confirm';
205
+ confirm.part.add('confirm');
206
+ confirm.disabled = !this._isAllowed(this._draft);
207
+ now.addEventListener('click', () => { const date = new Date(); const parts = { hour: date.getHours(), minute: Math.floor(date.getMinutes() / this.minuteStep) * this.minuteStep, second: Math.floor(date.getSeconds() / this.secondStep) * this.secondStep }; if (this._isAllowed(parts))
208
+ this._commit(formatTime(parts, this.showSeconds)); });
209
+ now.disabled = !this._isAllowed({ hour: new Date().getHours(), minute: new Date().getMinutes(), second: new Date().getSeconds() });
210
+ confirm.addEventListener('click', () => this._commit(formatTime(this._draft, this.showSeconds)));
211
+ footer.append(now, confirm);
212
+ this._panel.replaceChildren(columns, footer);
213
+ }
214
+ _createColumn(part, limit, step, selected, columnIndex) {
215
+ const list = document.createElement('ul');
216
+ list.className = 'column';
217
+ list.part.add('column');
218
+ list.setAttribute('role', 'listbox');
219
+ list.setAttribute('aria-label', part);
220
+ for (let value = 0; value < limit; value += step) {
221
+ const item = document.createElement('li');
222
+ const button = document.createElement('button');
223
+ button.type = 'button';
224
+ button.className = 'option';
225
+ button.part.add('option');
226
+ button.textContent = pad(value);
227
+ button.dataset.column = String(columnIndex);
228
+ button.dataset.value = String(value);
229
+ button.setAttribute('role', 'option');
230
+ button.setAttribute('aria-selected', String(value === selected));
231
+ button.tabIndex = value === selected ? 0 : -1;
232
+ button.addEventListener('click', () => { this._draft = { ...this._draft, [part]: value }; this._renderPanel(); queueMicrotask(() => this._focusSelected(columnIndex)); });
233
+ button.addEventListener('keydown', event => this._handleOptionKeydown(event, columnIndex));
234
+ item.append(button);
235
+ list.append(item);
236
+ }
237
+ queueMicrotask(() => list.querySelector('[aria-selected="true"]')?.scrollIntoView({ block: 'center' }));
238
+ return list;
239
+ }
240
+ _handleOptionKeydown(event, columnIndex) {
241
+ const columns = [...this._panel.querySelectorAll('.column')];
242
+ const column = columns[columnIndex];
243
+ if (!column)
244
+ return;
245
+ const options = [...column.querySelectorAll('.option')];
246
+ const current = options.indexOf(event.currentTarget);
247
+ let target;
248
+ if (event.key === 'ArrowDown')
249
+ target = options[(current + 1) % options.length];
250
+ else if (event.key === 'ArrowUp')
251
+ target = options[(current - 1 + options.length) % options.length];
252
+ else if (event.key === 'Home')
253
+ target = options[0];
254
+ else if (event.key === 'End')
255
+ target = options.at(-1);
256
+ else if (event.key === 'ArrowRight') {
257
+ event.preventDefault();
258
+ this._focusSelected(Math.min(columnIndex + 1, columns.length - 1));
259
+ return;
260
+ }
261
+ else if (event.key === 'ArrowLeft') {
262
+ event.preventDefault();
263
+ this._focusSelected(Math.max(columnIndex - 1, 0));
264
+ return;
265
+ }
266
+ else if (event.key === 'Escape') {
267
+ event.preventDefault();
268
+ this.open = false;
269
+ this._trigger.focus();
270
+ return;
271
+ }
272
+ if (target) {
273
+ event.preventDefault();
274
+ target.focus();
275
+ }
276
+ }
277
+ _focusSelected(columnIndex) { this._panel.querySelectorAll('.column')[columnIndex]?.querySelector('[aria-selected="true"]')?.focus(); }
278
+ _commit(value) {
279
+ this._value = this._normalizeValue(value);
280
+ this._reflectValue();
281
+ if (this.open)
282
+ this.open = false;
283
+ else
284
+ this._render();
285
+ const parts = parseTime(this._value);
286
+ this.dispatchEvent(new CustomEvent('value-change', { bubbles: true, composed: true, detail: { value: this._value, hour: parts?.hour ?? null, minute: parts?.minute ?? null, second: parts && this.showSeconds ? parts.second : null } }));
287
+ }
288
+ _syncOutsideListener() { this._outsideAbort?.abort(); this._outsideAbort = null; if (!this.open || !this.isConnected)
289
+ return; const abort = new AbortController(); this._outsideAbort = abort; queueMicrotask(() => { if (!abort.signal.aborted)
290
+ document.addEventListener('pointerdown', event => { if (!event.composedPath().includes(this))
291
+ this.open = false; }, { capture: true, signal: abort.signal }); }); }
292
+ _dispatchOpenChange() { this.dispatchEvent(new CustomEvent('open-change', { bubbles: true, composed: true, detail: { open: this.open } })); }
293
+ }
294
+ export function defineTimePickerComponents() {
295
+ if (!customElements.get('hy-time-picker'))
296
+ customElements.define('hy-time-picker', HYTimePicker);
297
+ }
298
+ //# sourceMappingURL=time-picker.js.map
@@ -0,0 +1,48 @@
1
+ export type HYTimelineMode = 'left' | 'right' | 'alternate';
2
+ export type HYTimelineStatus = 'default' | 'success' | 'info' | 'warning' | 'error';
3
+ export type HYTimelineItemPosition = 'left' | 'right';
4
+ export interface HYTimelineItem {
5
+ readonly key?: string | number;
6
+ readonly label?: string;
7
+ readonly title: string;
8
+ readonly description?: string;
9
+ readonly status?: HYTimelineStatus;
10
+ readonly color?: string;
11
+ readonly dot?: string;
12
+ readonly position?: HYTimelineItemPosition;
13
+ }
14
+ export type HYTimelineItemRenderer = (item: HYTimelineItem, index: number) => Node | string;
15
+ export type HYTimelineDotRenderer = (item: HYTimelineItem, index: number) => Node | string;
16
+ /** Semantic, responsive timeline for ordered events and process history. */
17
+ export declare class HYTimeline extends HTMLElement {
18
+ private readonly _list;
19
+ private _items;
20
+ private _itemsAssigned;
21
+ private _renderItem;
22
+ private _renderDot;
23
+ static get observedAttributes(): string[];
24
+ constructor();
25
+ connectedCallback(): void;
26
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
27
+ get items(): readonly HYTimelineItem[];
28
+ set items(value: readonly HYTimelineItem[]);
29
+ get mode(): HYTimelineMode;
30
+ set mode(value: HYTimelineMode);
31
+ get reverse(): boolean;
32
+ set reverse(value: boolean);
33
+ get pending(): string;
34
+ set pending(value: string);
35
+ get pendingDot(): string;
36
+ set pendingDot(value: string);
37
+ get renderItem(): HYTimelineItemRenderer | null;
38
+ set renderItem(value: HYTimelineItemRenderer | null);
39
+ get renderDot(): HYTimelineDotRenderer | null;
40
+ set renderDot(value: HYTimelineDotRenderer | null);
41
+ private _readItemsAttribute;
42
+ private _render;
43
+ private _createItem;
44
+ private _createPending;
45
+ private _positionFor;
46
+ private _toNode;
47
+ }
48
+ export declare function defineTimelineComponents(): void;
@@ -0,0 +1,201 @@
1
+ function normalizeItems(value) {
2
+ if (!Array.isArray(value))
3
+ return [];
4
+ return value.flatMap(item => {
5
+ if (!item || typeof item !== 'object')
6
+ return [];
7
+ const record = item;
8
+ if (record.title == null)
9
+ return [];
10
+ const status = ['success', 'info', 'warning', 'error'].includes(String(record.status)) ? String(record.status) : 'default';
11
+ const position = record.position === 'left' || record.position === 'right' ? record.position : undefined;
12
+ return [{
13
+ title: String(record.title),
14
+ ...(record.key != null && (typeof record.key === 'string' || typeof record.key === 'number') ? { key: record.key } : {}),
15
+ ...(record.label != null ? { label: String(record.label) } : {}),
16
+ ...(record.description != null ? { description: String(record.description) } : {}),
17
+ ...(record.color != null ? { color: String(record.color) } : {}),
18
+ ...(record.dot != null ? { dot: String(record.dot) } : {}),
19
+ ...(position ? { position } : {}),
20
+ status,
21
+ }];
22
+ });
23
+ }
24
+ /** Semantic, responsive timeline for ordered events and process history. */
25
+ export class HYTimeline extends HTMLElement {
26
+ _list = document.createElement('ol');
27
+ _items = [];
28
+ _itemsAssigned = false;
29
+ _renderItem = null;
30
+ _renderDot = null;
31
+ static get observedAttributes() { return ['items', 'mode', 'reverse', 'pending', 'pending-dot', 'aria-label']; }
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
+ .timeline { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
39
+ .item { display: grid; grid-template-columns: minmax(0, 1fr) var(--hy-timeline-axis-width, 30px) minmax(0, 1fr); min-height: var(--hy-timeline-item-min-height, 72px); margin: 0; padding: 0; }
40
+ .label { grid-column: 1; padding: 1px 14px 22px 0; color: var(--hy-muted-color, #788697); text-align: right; }
41
+ .axis { position: relative; grid-column: 2; grid-row: 1; display: flex; justify-content: center; }
42
+ .axis::after { position: absolute; top: 16px; bottom: 0; width: var(--hy-timeline-line-width, 1px); background: var(--hy-timeline-line-color, var(--hy-border-color, #3d4654)); content: ''; }
43
+ .item:last-child .axis::after { display: none; }
44
+ .dot { position: relative; z-index: 1; display: grid; width: var(--hy-timeline-dot-size, 10px); height: var(--hy-timeline-dot-size, 10px); box-sizing: border-box; place-items: center; margin-top: 4px; border: 2px solid var(--_dot-color, var(--hy-muted-color, #788697)); border-radius: 50%; color: var(--_dot-color, var(--hy-muted-color, #788697)); background: var(--hy-surface-color, #111722); font-size: 9px; line-height: 1; }
45
+ .dot.custom { width: var(--hy-timeline-custom-dot-size, 22px); height: var(--hy-timeline-custom-dot-size, 22px); margin-top: -2px; border-width: 1px; background: var(--hy-surface-elevated-color, #171d27); }
46
+ .status-success { --_dot-color: var(--hy-success-color, #4ade80); }
47
+ .status-info { --_dot-color: var(--hy-info-color, var(--hy-accent-color, #79a8ff)); }
48
+ .status-warning { --_dot-color: var(--hy-warning-color, #fbbf24); }
49
+ .status-error { --_dot-color: var(--hy-error-color, #fb7185); }
50
+ .card { grid-column: 3; min-width: 0; padding: 0 0 22px 14px; }
51
+ .title { color: var(--hy-text-color, #eef3ff); font-weight: 650; }
52
+ .description { margin-top: 4px; color: var(--hy-secondary-text-color, #aebbd0); }
53
+ .content-left .card { grid-column: 1; padding: 0 14px 22px 0; text-align: right; }
54
+ .content-left .label { grid-column: 3; padding: 1px 0 22px 14px; text-align: left; }
55
+ .pending .dot { border-style: dashed; animation: hy-timeline-spin 1.2s linear infinite; }
56
+ .pending .dot.custom { animation: none; }
57
+ @keyframes hy-timeline-spin { to { transform: rotate(360deg); } }
58
+ @media (prefers-reduced-motion: reduce) { .pending .dot { animation: none; } }
59
+ @media (max-width: 560px) {
60
+ .item, .item.content-left { grid-template-columns: var(--hy-timeline-axis-width, 30px) minmax(0, 1fr); }
61
+ .axis, .item.content-left .axis { grid-column: 1; }
62
+ .card, .item.content-left .card { grid-column: 2; padding: 0 0 22px 12px; text-align: left; }
63
+ .label, .item.content-left .label { grid-column: 2; grid-row: 1; justify-self: end; padding: 1px 0 0 12px; text-align: right; }
64
+ .label + .axis + .card { padding-top: 22px; }
65
+ }
66
+ `;
67
+ this._list.className = 'timeline';
68
+ this._list.part.add('list');
69
+ root.append(style, this._list);
70
+ }
71
+ connectedCallback() {
72
+ if (!this._itemsAssigned && this.hasAttribute('items'))
73
+ this._readItemsAttribute();
74
+ this._render();
75
+ }
76
+ attributeChangedCallback(name, oldValue, newValue) {
77
+ if (oldValue === newValue)
78
+ return;
79
+ if (name === 'items' && !this._itemsAssigned)
80
+ this._readItemsAttribute();
81
+ this._render();
82
+ }
83
+ get items() { return this._items; }
84
+ set items(value) { this._itemsAssigned = true; this._items = normalizeItems(value); this._render(); }
85
+ get mode() { const value = this.getAttribute('mode'); return value === 'right' || value === 'alternate' ? value : 'left'; }
86
+ set mode(value) { this.setAttribute('mode', value === 'right' || value === 'alternate' ? value : 'left'); }
87
+ get reverse() { return this.hasAttribute('reverse'); }
88
+ set reverse(value) { this.toggleAttribute('reverse', Boolean(value)); }
89
+ get pending() { return this.getAttribute('pending') ?? ''; }
90
+ set pending(value) { const normalized = String(value); if (normalized)
91
+ this.setAttribute('pending', normalized);
92
+ else
93
+ this.removeAttribute('pending'); }
94
+ get pendingDot() { return this.getAttribute('pending-dot') ?? ''; }
95
+ set pendingDot(value) { const normalized = String(value); if (normalized)
96
+ this.setAttribute('pending-dot', normalized);
97
+ else
98
+ this.removeAttribute('pending-dot'); }
99
+ get renderItem() { return this._renderItem; }
100
+ set renderItem(value) { this._renderItem = typeof value === 'function' ? value : null; this._render(); }
101
+ get renderDot() { return this._renderDot; }
102
+ set renderDot(value) { this._renderDot = typeof value === 'function' ? value : null; this._render(); }
103
+ _readItemsAttribute() {
104
+ try {
105
+ this._items = normalizeItems(JSON.parse(this.getAttribute('items') ?? '[]'));
106
+ }
107
+ catch {
108
+ this._items = [];
109
+ }
110
+ }
111
+ _render() {
112
+ if (!this.isConnected)
113
+ return;
114
+ const name = this.getAttribute('aria-label');
115
+ if (name)
116
+ this._list.setAttribute('aria-label', name);
117
+ else
118
+ this._list.removeAttribute('aria-label');
119
+ const source = this.reverse ? [...this._items].reverse() : [...this._items];
120
+ const nodes = source.map((item, index) => this._createItem(item, index, false));
121
+ if (this.pending) {
122
+ const pending = this._createPending();
123
+ if (this.reverse)
124
+ nodes.unshift(pending);
125
+ else
126
+ nodes.push(pending);
127
+ }
128
+ this._list.replaceChildren(...nodes);
129
+ }
130
+ _createItem(item, index, pending) {
131
+ const element = document.createElement('li');
132
+ element.className = `item status-${item.status ?? 'default'}`;
133
+ element.part.add('item');
134
+ const position = item.position ?? this._positionFor(index);
135
+ element.classList.toggle('content-left', position === 'left');
136
+ if (pending) {
137
+ element.classList.add('pending');
138
+ element.part.add('pending');
139
+ }
140
+ if (item.color)
141
+ element.style.setProperty('--_dot-color', item.color);
142
+ const label = document.createElement('div');
143
+ label.className = 'label';
144
+ label.part.add('label');
145
+ label.textContent = item.label ?? '';
146
+ const axis = document.createElement('div');
147
+ axis.className = 'axis';
148
+ axis.part.add('axis');
149
+ const dot = document.createElement('span');
150
+ dot.className = 'dot';
151
+ dot.part.add('dot');
152
+ const customDot = pending ? this.pendingDot : this._renderDot?.(item, index) ?? item.dot;
153
+ if (customDot) {
154
+ dot.classList.add('custom');
155
+ dot.append(this._toNode(customDot));
156
+ }
157
+ axis.append(dot);
158
+ const card = document.createElement('div');
159
+ card.className = 'card';
160
+ card.part.add('content');
161
+ if (pending) {
162
+ const title = document.createElement('div');
163
+ title.className = 'title';
164
+ title.part.add('title');
165
+ title.textContent = this.pending;
166
+ card.append(title);
167
+ }
168
+ else if (this._renderItem)
169
+ card.append(this._toNode(this._renderItem(item, index)));
170
+ else {
171
+ const title = document.createElement('div');
172
+ title.className = 'title';
173
+ title.part.add('title');
174
+ title.textContent = item.title;
175
+ card.append(title);
176
+ if (item.description) {
177
+ const description = document.createElement('div');
178
+ description.className = 'description';
179
+ description.part.add('description');
180
+ description.textContent = item.description;
181
+ card.append(description);
182
+ }
183
+ }
184
+ element.append(label, axis, card);
185
+ return element;
186
+ }
187
+ _createPending() { return this._createItem({ title: this.pending, status: 'info' }, this._items.length, true); }
188
+ _positionFor(index) {
189
+ if (this.mode === 'right')
190
+ return 'left';
191
+ if (this.mode === 'alternate')
192
+ return index % 2 === 0 ? 'right' : 'left';
193
+ return 'right';
194
+ }
195
+ _toNode(value) { return value instanceof Node ? value : document.createTextNode(String(value)); }
196
+ }
197
+ export function defineTimelineComponents() {
198
+ if (!customElements.get('hy-timeline'))
199
+ customElements.define('hy-timeline', HYTimeline);
200
+ }
201
+ //# sourceMappingURL=timeline.js.map
@@ -0,0 +1,55 @@
1
+ export type HYToastType = 'success' | 'info' | 'warning' | 'error' | 'loading';
2
+ export type HYToastPlacement = 'top' | 'center' | 'bottom';
3
+ export type HYToastCloseReason = 'action' | 'timeout' | 'programmatic' | 'overflow';
4
+ export interface HYToastOptions {
5
+ readonly key?: string;
6
+ readonly type?: HYToastType;
7
+ readonly message: string;
8
+ /** Auto-close delay in seconds. Use 0 for a persistent toast. */
9
+ readonly duration?: number;
10
+ readonly placement?: HYToastPlacement;
11
+ readonly closable?: boolean;
12
+ }
13
+ export type HYToastMethodOptions = Omit<HYToastOptions, 'type'>;
14
+ export interface HYToastOpenDetail {
15
+ readonly key: string;
16
+ readonly type: HYToastType;
17
+ readonly placement: HYToastPlacement;
18
+ }
19
+ export interface HYToastCloseDetail extends HYToastOpenDetail {
20
+ readonly reason: HYToastCloseReason;
21
+ }
22
+ /** Lightweight imperative feedback center for short, transient messages. */
23
+ export declare class HYToast extends HTMLElement {
24
+ private readonly _containers;
25
+ private readonly _toasts;
26
+ private _sequence;
27
+ static get observedAttributes(): string[];
28
+ constructor();
29
+ connectedCallback(): void;
30
+ disconnectedCallback(): void;
31
+ attributeChangedCallback(): void;
32
+ get placement(): HYToastPlacement;
33
+ set placement(value: HYToastPlacement);
34
+ get duration(): number;
35
+ set duration(value: number);
36
+ get maxCount(): number;
37
+ set maxCount(value: number);
38
+ get closable(): boolean;
39
+ set closable(value: boolean);
40
+ get size(): number;
41
+ open(options: string | HYToastOptions): string;
42
+ success(options: string | HYToastMethodOptions): string;
43
+ info(options: string | HYToastMethodOptions): string;
44
+ warning(options: string | HYToastMethodOptions): string;
45
+ error(options: string | HYToastMethodOptions): string;
46
+ loading(options: string | HYToastMethodOptions): string;
47
+ close(key: string): void;
48
+ clear(): void;
49
+ private _openTyped;
50
+ private _createToast;
51
+ private _closeToast;
52
+ private _removeToast;
53
+ private _syncAttributes;
54
+ }
55
+ export declare function defineToastComponents(): void;