@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/LICENSE +27 -0
  3. package/README.md +643 -2
  4. package/dist/adapters/web/controls.d.ts +19 -0
  5. package/dist/adapters/web/controls.js +307 -0
  6. package/dist/adapters/web/dom.d.ts +10 -0
  7. package/dist/adapters/web/dom.js +98 -0
  8. package/dist/adapters/web/index.d.ts +3 -0
  9. package/dist/adapters/web/index.js +3 -0
  10. package/dist/adapters/web/renderer.d.ts +18 -0
  11. package/dist/adapters/web/renderer.js +553 -0
  12. package/dist/adapters/web/styles.d.ts +3 -0
  13. package/dist/adapters/web/styles.js +83 -0
  14. package/dist/adapters/web/types.d.ts +46 -0
  15. package/dist/adapters/web/types.js +2 -0
  16. package/dist/affix.d.ts +35 -0
  17. package/dist/affix.js +137 -0
  18. package/dist/badge.d.ts +45 -0
  19. package/dist/badge.js +168 -0
  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/carousel.d.ts +92 -0
  27. package/dist/carousel.js +509 -0
  28. package/dist/cascader.d.ts +65 -0
  29. package/dist/cascader.js +479 -0
  30. package/dist/collapse.d.ts +68 -0
  31. package/dist/collapse.js +348 -0
  32. package/dist/core/actions.d.ts +32 -0
  33. package/dist/core/actions.js +145 -0
  34. package/dist/core/data.d.ts +4 -0
  35. package/dist/core/data.js +27 -0
  36. package/dist/core/index.d.ts +7 -0
  37. package/dist/core/index.js +6 -0
  38. package/dist/core/platform-view.d.ts +17 -0
  39. package/dist/core/platform-view.js +84 -0
  40. package/dist/core/selection.d.ts +14 -0
  41. package/dist/core/selection.js +93 -0
  42. package/dist/core/text.d.ts +31 -0
  43. package/dist/core/text.js +154 -0
  44. package/dist/core/types.d.ts +269 -0
  45. package/dist/core/types.js +2 -0
  46. package/dist/core/validation.d.ts +24 -0
  47. package/dist/core/validation.js +243 -0
  48. package/dist/date-picker.d.ts +60 -0
  49. package/dist/date-picker.js +355 -0
  50. package/dist/dialog.d.ts +4 -0
  51. package/dist/dialog.js +23 -5
  52. package/dist/divider.d.ts +33 -0
  53. package/dist/divider.js +113 -0
  54. package/dist/draggable.d.ts +78 -0
  55. package/dist/draggable.js +255 -0
  56. package/dist/index.d.ts +53 -1
  57. package/dist/index.js +78 -0
  58. package/dist/input.d.ts +30 -1
  59. package/dist/input.js +166 -54
  60. package/dist/internal/qrcodegen.d.ts +94 -0
  61. package/dist/internal/qrcodegen.js +856 -0
  62. package/dist/masonry.d.ts +36 -0
  63. package/dist/masonry.js +171 -0
  64. package/dist/modal.d.ts +73 -0
  65. package/dist/modal.js +361 -0
  66. package/dist/portable/icons.d.ts +5 -0
  67. package/dist/portable/icons.js +24 -0
  68. package/dist/portable/index.d.ts +7 -0
  69. package/dist/portable/index.js +5 -0
  70. package/dist/portable/page.d.ts +38 -0
  71. package/dist/portable/page.js +361 -0
  72. package/dist/portable/responsive.d.ts +20 -0
  73. package/dist/portable/responsive.js +15 -0
  74. package/dist/portable/theme.d.ts +24 -0
  75. package/dist/portable/theme.js +60 -0
  76. package/dist/portable/tree.d.ts +10 -0
  77. package/dist/portable/tree.js +96 -0
  78. package/dist/qr-code.d.ts +62 -0
  79. package/dist/qr-code.js +224 -0
  80. package/dist/range.d.ts +3 -0
  81. package/dist/range.js +16 -5
  82. package/dist/rate.d.ts +46 -0
  83. package/dist/rate.js +193 -0
  84. package/dist/resizable.d.ts +97 -0
  85. package/dist/resizable.js +426 -0
  86. package/dist/skeleton.d.ts +41 -0
  87. package/dist/skeleton.js +141 -0
  88. package/dist/steps.d.ts +45 -0
  89. package/dist/steps.js +203 -0
  90. package/dist/table.d.ts +106 -0
  91. package/dist/table.js +689 -0
  92. package/dist/tabs.d.ts +3 -0
  93. package/dist/tabs.js +11 -1
  94. package/dist/tag.d.ts +55 -0
  95. package/dist/tag.js +225 -0
  96. package/dist/time-picker.d.ts +62 -0
  97. package/dist/time-picker.js +298 -0
  98. package/dist/timeline.d.ts +48 -0
  99. package/dist/timeline.js +201 -0
  100. package/dist/toast.d.ts +55 -0
  101. package/dist/toast.js +224 -0
  102. package/dist/tour.d.ts +89 -0
  103. package/dist/tour.js +306 -0
  104. package/dist/virtual-keyboard.d.ts +95 -0
  105. package/dist/virtual-keyboard.js +432 -0
  106. package/dist/virtual-mouse.d.ts +82 -0
  107. package/dist/virtual-mouse.js +358 -0
  108. package/dist/watermark.d.ts +44 -0
  109. package/dist/watermark.js +148 -0
  110. package/package.json +132 -4
@@ -0,0 +1,19 @@
1
+ import type { HYNode } from '../../core/types.js';
2
+ import type { HYWebControl, HYWebControlContext } from './types.js';
3
+ /** Registration is explicit and limited to the reused components. */
4
+ export declare function definePortableWebComponents(): void;
5
+ export declare const componentTags: Readonly<{
6
+ Checkbox: "hy-checkbox";
7
+ RadioGroup: "div";
8
+ Rate: "hy-rate";
9
+ Badge: "hy-badge";
10
+ Divider: "hy-divider";
11
+ Button: "hy-button";
12
+ Toggle: "hy-checkbox";
13
+ Select: "hy-select";
14
+ Tabs: "hy-tabs";
15
+ TextInput: "hy-input";
16
+ Slider: "hy-range";
17
+ Dialog: "hy-dialog";
18
+ }>;
19
+ export declare function createControl(node: HYNode, context: HYWebControlContext, revision: number): HYWebControl | undefined;
@@ -0,0 +1,307 @@
1
+ import { hyIconSvg } from '../../portable/icons.js';
2
+ import { HYButtonController, HYToggleController, HYSliderController } from '../../core/actions.js';
3
+ import { HYSelectionController } from '../../core/selection.js';
4
+ import { HYTextController } from '../../core/text.js';
5
+ import { equalData } from '../../core/data.js';
6
+ import { defineButtonComponents } from '../../button.js';
7
+ import { defineCheckboxComponents } from '../../checkbox.js';
8
+ import { HYSelect, defineSelectComponents } from '../../select.js';
9
+ import { HYTabs, defineTabsComponents } from '../../tabs.js';
10
+ import { HYInput, defineInputComponents } from '../../input.js';
11
+ import { defineRangeComponents } from '../../range.js';
12
+ import { defineDialogComponents } from '../../dialog.js';
13
+ import { defineRadioComponents } from '../../radio.js';
14
+ import { defineRateComponents } from '../../rate.js';
15
+ import { defineBadgeComponents } from '../../badge.js';
16
+ import { defineDividerComponents } from '../../divider.js';
17
+ import { attribute, flag, focusable } from './dom.js';
18
+ /** Registration is explicit and limited to the reused components. */
19
+ export function definePortableWebComponents() {
20
+ defineButtonComponents();
21
+ defineCheckboxComponents();
22
+ defineSelectComponents();
23
+ defineTabsComponents();
24
+ defineRadioComponents();
25
+ defineRateComponents();
26
+ defineBadgeComponents();
27
+ defineDividerComponents();
28
+ defineInputComponents();
29
+ defineRangeComponents();
30
+ defineDialogComponents();
31
+ }
32
+ export const componentTags = Object.freeze({ Checkbox: 'hy-checkbox', RadioGroup: 'div', Rate: 'hy-rate', Badge: 'hy-badge', Divider: 'hy-divider', Button: 'hy-button', Toggle: 'hy-checkbox', Select: 'hy-select', Tabs: 'hy-tabs', TextInput: 'hy-input', Slider: 'hy-range', Dialog: 'hy-dialog' });
33
+ const selectionProps = ({ value, options, disabled }) => ({ value, options, ...(disabled === undefined ? {} : { disabled }) });
34
+ let radioGroupSequence = 0;
35
+ function toggleProps(node) { const { checked, disabled, visible } = node.props; return { checked, ...(disabled === undefined ? {} : { disabled }), ...(visible === undefined ? {} : { visible }) }; }
36
+ export function createControl(node, context, revision) {
37
+ const { host, listen, emit } = context;
38
+ let reason = 'accessibility', transaction = 0;
39
+ listen(host, 'pointerdown', () => { reason = 'pointer'; }, true);
40
+ listen(host, 'keydown', () => { reason = 'keyboard'; }, true);
41
+ if (node.kind === 'Badge' || node.kind === 'Divider')
42
+ return { update(next) { if (next.kind === 'Badge') {
43
+ flag(host, 'literal', true);
44
+ attribute(host, 'count', next.props.text);
45
+ } ; if (next.kind === 'Divider')
46
+ attribute(host, 'label', next.props.text); }, focus() { }, dispose() { } };
47
+ if (node.kind === 'RadioGroup') {
48
+ const controller = new HYSelectionController(node.key, selectionProps(node.props), emit);
49
+ let props = node.props;
50
+ const buttons = new Map(), groupName = `hy-portable-radio-${++radioGroupSequence}`;
51
+ host.setAttribute('role', 'radiogroup');
52
+ const render = () => { for (const [value, button] of buttons) {
53
+ flag(button, 'checked', props.value === value);
54
+ flag(button, 'disabled', !!props.disabled || !!props.options.find(o => o.value === value)?.disabled);
55
+ } };
56
+ listen(host, 'checked-change', event => { const detail = event.detail; if (detail.checked)
57
+ controller.choose(detail.value, reason); render(); });
58
+ return { update(next) {
59
+ if (next.kind !== 'RadioGroup')
60
+ return;
61
+ props = next.props;
62
+ controller.update(selectionProps(props));
63
+ for (const [value, button] of buttons)
64
+ if (!props.options.some(o => o.value === value)) {
65
+ button.remove();
66
+ buttons.delete(value);
67
+ }
68
+ for (const option of props.options) {
69
+ let button = buttons.get(option.value);
70
+ if (!button) {
71
+ button = document.createElement('hy-radio');
72
+ buttons.set(option.value, button);
73
+ }
74
+ attribute(button, 'value', option.value);
75
+ attribute(button, 'label', option.label);
76
+ attribute(button, 'name', groupName);
77
+ host.append(button);
78
+ }
79
+ render();
80
+ },
81
+ focus() { focusable(host)[0]?.focus(); }, dispose() { controller.dispose(); buttons.clear(); } };
82
+ }
83
+ if (node.kind === 'Rate') {
84
+ let props = node.props, disposed = false, choosing = false;
85
+ const restore = () => attribute(host, 'value', String(props.value));
86
+ listen(host, 'value-change', event => {
87
+ const value = event.detail.value;
88
+ if (disposed || choosing || props.disabled || props.readOnly || value === props.value)
89
+ return;
90
+ choosing = true;
91
+ const tx = ++transaction;
92
+ try {
93
+ emit({ key: node.key, type: 'valueChange', value, reason, transaction: tx });
94
+ if (!disposed)
95
+ emit({ key: node.key, type: 'commit', value, reason, transaction: tx });
96
+ }
97
+ finally {
98
+ choosing = false;
99
+ if (!disposed)
100
+ restore();
101
+ }
102
+ });
103
+ return { update(next) { if (next.kind !== 'Rate')
104
+ return; props = next.props; attribute(host, 'count', String(props.count)); flag(host, 'disabled', !!props.disabled); flag(host, 'readonly', !!props.readOnly); restore(); }, focus() { focusable(host)[0]?.focus(); }, dispose() { disposed = true; } };
105
+ }
106
+ if (node.kind === 'Button') {
107
+ const button = host;
108
+ const label = document.createElement('span');
109
+ label.dataset.hyButtonLabel = '';
110
+ button.append(label);
111
+ const icon = document.createElement('span');
112
+ icon.slot = 'icon';
113
+ icon.setAttribute('aria-hidden', 'true');
114
+ let lastIcon;
115
+ const controller = new HYButtonController(node.key, node.props, emit);
116
+ listen(button, 'click', () => controller.activate(reason));
117
+ return {
118
+ update(next) {
119
+ if (next.kind !== 'Button')
120
+ return;
121
+ controller.update(next.props);
122
+ if (label.textContent !== next.props.text)
123
+ label.textContent = next.props.text;
124
+ attribute(button, 'data-hy-variant', next.props.variant ?? 'secondary');
125
+ attribute(button, 'data-hy-size', next.props.size ?? 'regular');
126
+ if (lastIcon !== next.props.icon) {
127
+ icon.innerHTML = next.props.icon ? hyIconSvg(next.props.icon) : '';
128
+ lastIcon = next.props.icon;
129
+ }
130
+ if (next.props.icon) {
131
+ if (!icon.parentNode)
132
+ button.append(icon);
133
+ }
134
+ else
135
+ icon.remove();
136
+ flag(button, 'icon-only', !!next.props.icon && !next.props.text);
137
+ flag(button, 'loading', !!next.props.busy);
138
+ flag(button, 'disabled', !!next.props.disabled || !!next.props.busy);
139
+ attribute(button, 'aria-busy', next.props.busy ? 'true' : undefined);
140
+ },
141
+ focus() { button.shadowRoot.querySelector('button').focus(); }, dispose() { controller.dispose(); },
142
+ };
143
+ }
144
+ if (node.kind === 'Toggle' || node.kind === 'Checkbox') {
145
+ const toggle = host;
146
+ const controller = new HYToggleController(node.key, toggleProps(node), emit);
147
+ listen(toggle, 'checked-change', event => {
148
+ controller.choose(event.detail.checked, reason);
149
+ // The app may reject a discrete change; restore its latest accepted value.
150
+ flag(toggle, 'checked', accepted);
151
+ });
152
+ let accepted = node.props.checked;
153
+ return { update(next) {
154
+ if (next.kind !== 'Toggle' && next.kind !== 'Checkbox')
155
+ return;
156
+ accepted = next.props.checked;
157
+ controller.update(toggleProps(next));
158
+ flag(toggle, 'checked', accepted);
159
+ flag(toggle, 'disabled', !!next.props.disabled);
160
+ attribute(toggle, 'label', next.kind === 'Checkbox' ? next.props.text : next.props.a11y?.label);
161
+ }, focus() { toggle.shadowRoot.querySelector('input').focus(); }, dispose() { controller.dispose(); } };
162
+ }
163
+ if (node.kind === 'Select' || node.kind === 'Tabs') {
164
+ const select = host;
165
+ if (select instanceof HYTabs)
166
+ select.controlled = true;
167
+ const controller = new HYSelectionController(node.key, selectionProps(node.props), emit);
168
+ let previous;
169
+ controller.attach({
170
+ apply(props) {
171
+ if (!previous || !!props.disabled !== !!previous.disabled || !equalData(props.options, previous.options))
172
+ select.options = props.options.map(option => ({ ...option, disabled: !!option.disabled || !!props.disabled }));
173
+ if (select.value !== props.value)
174
+ select.value = props.value;
175
+ if (select instanceof HYSelect)
176
+ flag(select, 'disabled', !!props.disabled);
177
+ attribute(select, 'aria-disabled', props.disabled ? 'true' : undefined);
178
+ previous = props;
179
+ },
180
+ listen(callback) { return listen(select, node.kind === 'Select' ? 'value-change' : 'tab-change', event => callback(event.detail.value, reason)); },
181
+ });
182
+ // Keep the existing component's LTR behavior; mirror horizontal navigation only
183
+ // for this adapter's RTL surface. Home/End retain their logical meanings.
184
+ if (select instanceof HYTabs)
185
+ listen(select, 'keydown', event => {
186
+ const key = event;
187
+ if (!['ArrowLeft', 'ArrowRight'].includes(key.key) || getComputedStyle(select).direction !== 'rtl')
188
+ return;
189
+ const button = key.composedPath()[0];
190
+ if (!(button instanceof HTMLButtonElement) || button.getAttribute('role') !== 'tab')
191
+ return;
192
+ const enabled = previous?.options.filter(option => !option.disabled) ?? [];
193
+ if (!enabled.length)
194
+ return;
195
+ key.preventDefault();
196
+ key.stopImmediatePropagation();
197
+ const position = enabled.findIndex(option => option.value === button.dataset.value);
198
+ const next = enabled[(position + (key.key === 'ArrowLeft' ? 1 : -1) + enabled.length) % enabled.length];
199
+ controller.choose(next.value, 'keyboard');
200
+ const target = [...select.shadowRoot.querySelectorAll('button')].find(item => item.dataset.value === select.value);
201
+ target?.focus();
202
+ }, true);
203
+ return { update(next) { if (next.kind === 'Select' || next.kind === 'Tabs') {
204
+ if (next.kind === 'Tabs')
205
+ attribute(host, 'data-hy-variant', next.props.variant ?? 'underline');
206
+ controller.update(selectionProps(next.props));
207
+ } },
208
+ focus() { if (select instanceof HYSelect)
209
+ select.shadowRoot.querySelector('select').focus();
210
+ else
211
+ (select.shadowRoot.querySelector('button[tabindex="0"]') ?? select).focus(); },
212
+ dispose() { controller.dispose(); } };
213
+ }
214
+ if (node.kind === 'TextInput' || node.kind === 'TextArea')
215
+ return textControl(node, context, revision);
216
+ if (node.kind === 'Slider') {
217
+ const range = host;
218
+ range.controlled = true;
219
+ const controller = new HYSliderController(node.key, node.props, emit);
220
+ listen(range, 'value-input', event => controller.change(event.detail.value, reason));
221
+ listen(range, 'value-change', event => { controller.change(event.detail.value, reason); controller.commit(reason); if (range.value !== controller.value)
222
+ range.value = controller.value; });
223
+ listen(range, 'value-cancel', () => { controller.cancel(reason); if (range.value !== controller.value)
224
+ range.value = controller.value; });
225
+ return { update(next) {
226
+ if (next.kind !== 'Slider')
227
+ return;
228
+ controller.update(next.props);
229
+ for (const field of ['min', 'max', 'step'])
230
+ attribute(range, field, String(next.props[field]));
231
+ flag(range, 'disabled', !!next.props.disabled);
232
+ if (range.value !== next.props.value)
233
+ range.value = next.props.value;
234
+ }, focus() { range.focus(); }, dispose() { controller.dispose(); } };
235
+ }
236
+ if (node.kind === 'Dialog') {
237
+ const dialog = host;
238
+ dialog.controlled = true;
239
+ let dismissible = node.props.dismissible !== false;
240
+ listen(dialog, 'dialog-cancel', event => {
241
+ event.preventDefault();
242
+ const detail = event.detail;
243
+ if (!dismissible)
244
+ return;
245
+ emit({ key: node.key, type: 'cancel', reason: detail?.reason === 'escape' ? 'keyboard' : reason, transaction: ++transaction });
246
+ });
247
+ return { update(next) {
248
+ if (next.kind !== 'Dialog')
249
+ return;
250
+ dismissible = next.props.dismissible !== false;
251
+ attribute(dialog, 'heading', next.props.title);
252
+ flag(dialog, 'dismissible', dismissible);
253
+ dialog.shadowRoot.querySelector('button').style.display = dismissible ? '' : 'none';
254
+ },
255
+ focus() { (focusable(dialog.shadowRoot)[0] ?? dialog.shadowRoot.querySelector('dialog')).focus(); }, dispose() { dialog.open = false; } };
256
+ }
257
+ return undefined;
258
+ }
259
+ function textControl(node, context, revision) {
260
+ const { host, listen, emit } = context;
261
+ const input = host instanceof HYInput ? host.shadowRoot.querySelector('input') : host;
262
+ const controller = new HYTextController(node.key, textProps(node), emit, node.kind === 'TextArea', revision);
263
+ let confirming = false, timer;
264
+ controller.attach({ apply(state) {
265
+ // Write the draft before attributes whose legacy sync method reads the value back.
266
+ if (host instanceof HYInput) {
267
+ // Avoid the legacy public setter's unconditional native value assignment:
268
+ // even assigning the same value can split the browser's undo typing group.
269
+ attribute(host, 'value', state.value);
270
+ if (input.value !== state.value)
271
+ input.value = state.value;
272
+ attribute(host, 'type', state.props.secure ? 'password' : 'text');
273
+ flag(host, 'disabled', !!state.props.disabled);
274
+ flag(host, 'readonly', !!state.props.readOnly);
275
+ attribute(host, 'placeholder', state.props.placeholder);
276
+ }
277
+ else {
278
+ if (input.value !== state.value)
279
+ input.value = state.value;
280
+ input.disabled = !!state.props.disabled;
281
+ input.readOnly = !!state.props.readOnly;
282
+ attribute(input, 'placeholder', state.props.placeholder);
283
+ }
284
+ } });
285
+ // Let HYInput synchronize its native draft before our controlled view reads it.
286
+ // A second attribute sync during the same native input breaks Chromium's undo grouping.
287
+ listen(host, 'input', () => controller.input(input.value));
288
+ listen(input, 'compositionstart', () => { if (timer)
289
+ clearTimeout(timer); confirming = false; controller.compositionStart(); });
290
+ listen(input, 'compositionend', () => { confirming = true; controller.compositionEnd(input.value); timer = setTimeout(() => { confirming = false; timer = undefined; }, 0); });
291
+ listen(input, 'keydown', event => { const key = event; if (key.key === 'Enter')
292
+ controller.enter(confirming || key.isComposing || key.keyCode === 229); });
293
+ // change is often dispatched before blur; both funnel through the controller's single active edit.
294
+ listen(input, 'change', () => controller.commit());
295
+ listen(input, 'focus', () => controller.focus());
296
+ listen(input, 'blur', () => controller.blur());
297
+ return { update(next, revision) { if (next.kind === 'TextInput' || next.kind === 'TextArea')
298
+ controller.update(textProps(next), revision); }, focus() { input.focus({ preventScroll: true }); },
299
+ dispose() { if (timer)
300
+ clearTimeout(timer); controller.dispose(); } };
301
+ }
302
+ function textProps(node) {
303
+ const { value, disabled, readOnly, placeholder } = node.props;
304
+ return { value, ...(disabled === undefined ? {} : { disabled }), ...(readOnly === undefined ? {} : { readOnly }), ...(placeholder === undefined ? {} : { placeholder }),
305
+ ...(node.kind === 'TextInput' && node.props.secure !== undefined ? { secure: node.props.secure } : {}) };
306
+ }
307
+ //# sourceMappingURL=controls.js.map
@@ -0,0 +1,10 @@
1
+ import type { HYAccessibility, HYLayout } from '../../core/types.js';
2
+ export declare function attribute(element: Element, name: string, value: string | undefined): void;
3
+ export declare function flag(element: Element, name: string, value: boolean): void;
4
+ export declare function deepActive(document: Document): Element | null;
5
+ export declare function composedParent(element: Element): Element | null;
6
+ export declare function visible(element: HTMLElement): boolean;
7
+ export declare function focusable(root: Element | ShadowRoot): HTMLElement[];
8
+ export declare function captureFocus(document: Document): () => void;
9
+ export declare function applyLayout(element: HTMLElement, layout?: HYLayout, overlayChild?: boolean): void;
10
+ export declare function applyAccessibility(element: HTMLElement, a11y: HYAccessibility | undefined): void;
@@ -0,0 +1,98 @@
1
+ export function attribute(element, name, value) {
2
+ if (value === undefined) {
3
+ if (element.hasAttribute(name))
4
+ element.removeAttribute(name);
5
+ }
6
+ else if (element.getAttribute(name) !== value)
7
+ element.setAttribute(name, value);
8
+ }
9
+ export function flag(element, name, value) {
10
+ if (element.hasAttribute(name) !== value)
11
+ element.toggleAttribute(name, value);
12
+ }
13
+ export function deepActive(document) {
14
+ let active = document.activeElement;
15
+ while (active?.shadowRoot?.activeElement)
16
+ active = active.shadowRoot.activeElement;
17
+ return active;
18
+ }
19
+ export function composedParent(element) {
20
+ return element.assignedSlot ?? element.parentElement ?? (element.getRootNode() instanceof ShadowRoot ? element.getRootNode().host : null);
21
+ }
22
+ export function visible(element) {
23
+ if (!element.isConnected || !element.getClientRects().length)
24
+ return false;
25
+ let current = element;
26
+ while (current) {
27
+ if (current instanceof HTMLElement && (current.hidden || current.inert))
28
+ return false;
29
+ current = composedParent(current);
30
+ }
31
+ return getComputedStyle(element).visibility !== 'hidden';
32
+ }
33
+ export function focusable(root) {
34
+ const result = [];
35
+ const visit = (element) => {
36
+ if (element instanceof HTMLElement && element.tabIndex >= 0 && !element.matches(':disabled') && visible(element))
37
+ result.push(element);
38
+ if (element instanceof HTMLSlotElement) {
39
+ for (const child of element.assignedElements({ flatten: true }))
40
+ visit(child);
41
+ }
42
+ else {
43
+ const children = element.shadowRoot?.children ?? element.children;
44
+ for (const child of children)
45
+ visit(child);
46
+ }
47
+ };
48
+ for (const child of root.children)
49
+ visit(child);
50
+ return result;
51
+ }
52
+ export function captureFocus(document) {
53
+ const active = deepActive(document);
54
+ if (!(active instanceof HTMLElement))
55
+ return () => { };
56
+ const input = active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement ? active : null;
57
+ const start = input?.selectionStart ?? null, end = input?.selectionEnd ?? null, direction = input?.selectionDirection ?? undefined;
58
+ const value = input?.value;
59
+ return () => {
60
+ // Most props-only commits preserve the focused element. Testing visibility
61
+ // in that case forces a synchronous layout after unrelated DOM writes.
62
+ // Only a lost focus needs geometry checks before restoring it.
63
+ if (deepActive(document) !== active) {
64
+ if (!visible(active) || active.matches(':disabled'))
65
+ return;
66
+ active.focus({ preventScroll: true });
67
+ }
68
+ // A new accepted value intentionally owns its new selection.
69
+ if (input && input.value === value && start !== null && end !== null
70
+ && (input.selectionStart !== start || input.selectionEnd !== end || input.selectionDirection !== direction))
71
+ input.setSelectionRange(start, end, direction);
72
+ };
73
+ }
74
+ const logical = (value) => value === undefined ? '' : typeof value === 'number' ? `${value}px` : value;
75
+ export function applyLayout(element, layout = {}, overlayChild = false) {
76
+ const style = element.style;
77
+ for (const name of ['width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight'])
78
+ style[name] = logical(layout[name]);
79
+ style.padding = layout.padding?.map(v => `${v}px`).join(' ') ?? '';
80
+ style.gap = logical(layout.gap);
81
+ style.flexGrow = layout.grow === undefined ? '' : String(layout.grow);
82
+ style.flexBasis = layout.grow ? '0px' : '';
83
+ style.alignItems = layout.align === undefined ? '' : { start: 'flex-start', center: 'center', end: 'flex-end', stretch: 'stretch' }[layout.align];
84
+ style.gridTemplateColumns = layout.columns?.map(track => typeof track === 'object' ? `minmax(0, ${track.grow}fr)` : logical(track)).join(' ') ?? '';
85
+ style.gridTemplateRows = layout.rows?.map(track => typeof track === 'object' ? `minmax(0, ${track.grow}fr)` : logical(track)).join(' ') ?? '';
86
+ style.gridRow = layout.row === undefined ? (overlayChild ? '1' : '') : String(layout.row + 1);
87
+ style.gridColumn = layout.column === undefined ? (overlayChild ? '1' : '') : String(layout.column + 1);
88
+ // Scroll owns its axis-specific overflow, including after insertion/reordering.
89
+ if (element.dataset.hyKind !== 'Scroll')
90
+ style.overflow = layout.clip === true ? 'hidden' : '';
91
+ }
92
+ export function applyAccessibility(element, a11y) {
93
+ attribute(element, 'aria-label', a11y?.label);
94
+ attribute(element, 'aria-description', a11y?.description);
95
+ attribute(element, 'aria-invalid', a11y?.error ? 'true' : undefined);
96
+ // Actual semantic roles of native controls are preserved. A requested extra role belongs on generic containers.
97
+ }
98
+ //# sourceMappingURL=dom.js.map
@@ -0,0 +1,3 @@
1
+ export { HYWebRenderer } from './renderer.js';
2
+ export { definePortableWebComponents } from './controls.js';
3
+ export type { HYWebEnvironment, HYWebRendererOptions, HYWebPlatformContext, HYWebDiagnostics } from './types.js';
@@ -0,0 +1,3 @@
1
+ export { HYWebRenderer } from './renderer.js';
2
+ export { definePortableWebComponents } from './controls.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,18 @@
1
+ import type { HYCapabilities, HYMutation, HYRenderer, HYUIEvent } from '../../core/types.js';
2
+ import type { HYWebDiagnostics, HYWebEnvironment, HYWebRendererOptions } from './types.js';
3
+ /** Browser-only adapter. Explicit registration and an attached, same-realm mount point are required. */
4
+ export declare class HYWebRenderer implements HYRenderer {
5
+ #private;
6
+ private readonly host;
7
+ private readonly options;
8
+ readonly capabilities: HYCapabilities;
9
+ constructor(host: HTMLElement, options?: HYWebRendererOptions);
10
+ get diagnostics(): HYWebDiagnostics;
11
+ mount(emit: (event: HYUIEvent) => void): void;
12
+ commit(revision: number, mutations: readonly HYMutation[]): void;
13
+ focus(key: string): void;
14
+ setEnvironment(environment: HYWebEnvironment): void;
15
+ suspend(): void;
16
+ resume(): void;
17
+ dispose(): void;
18
+ }