@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.
- package/CHANGELOG.md +56 -0
- package/README.md +504 -1
- package/dist/adapters/web/controls.d.ts +19 -0
- package/dist/adapters/web/controls.js +284 -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 +545 -0
- package/dist/adapters/web/styles.d.ts +3 -0
- package/dist/adapters/web/styles.js +62 -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/border-beam.js +3 -1
- 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/cascader.d.ts +65 -0
- package/dist/cascader.js +479 -0
- package/dist/collapse.d.ts +55 -0
- package/dist/collapse.js +261 -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 +250 -0
- package/dist/core/types.js +2 -0
- package/dist/core/validation.d.ts +24 -0
- package/dist/core/validation.js +238 -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/expandable.d.ts +32 -0
- package/dist/expandable.js +136 -0
- package/dist/index.d.ts +47 -1
- package/dist/index.js +69 -0
- package/dist/input.d.ts +30 -1
- package/dist/input.js +166 -54
- package/dist/portable/index.d.ts +4 -0
- package/dist/portable/index.js +3 -0
- package/dist/portable/page.d.ts +38 -0
- package/dist/portable/page.js +361 -0
- package/dist/portable/theme.d.ts +19 -0
- package/dist/portable/theme.js +40 -0
- package/dist/portable/tree.d.ts +10 -0
- package/dist/portable/tree.js +96 -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 +7 -0
- package/dist/tabs.js +90 -16
- 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-list.js +12 -10
- 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 +135 -7
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import { HY_COMPONENTS, HYUIError, hyValidateProps } from '../../core/validation.js';
|
|
2
|
+
import { HYPlatformViewController } from '../../core/platform-view.js';
|
|
3
|
+
import { equalData } from '../../core/data.js';
|
|
4
|
+
import { hyThemeVariables } from '../../portable/theme.js';
|
|
5
|
+
import { HYDialog } from '../../dialog.js';
|
|
6
|
+
import { HYTabs } from '../../tabs.js';
|
|
7
|
+
import { componentTags, createControl } from './controls.js';
|
|
8
|
+
import { applyAccessibility, applyLayout, attribute, captureFocus, deepActive, flag, focusable } from './dom.js';
|
|
9
|
+
import { componentCSS, rootCSS } from './styles.js';
|
|
10
|
+
const tags = { Row: 'div', Column: 'div', Grid: 'div', Overlay: 'div', Scroll: 'div', Text: 'span', Image: 'img', Icon: 'img', TextArea: 'textarea', FormGroup: 'fieldset', PlatformView: 'div', ...componentTags };
|
|
11
|
+
let rendererSequence = 0;
|
|
12
|
+
/** Browser-only adapter. Explicit registration and an attached, same-realm mount point are required. */
|
|
13
|
+
export class HYWebRenderer {
|
|
14
|
+
host;
|
|
15
|
+
options;
|
|
16
|
+
capabilities = Object.freeze({ contract: 1, backend: 'web', components: HY_COMPONENTS,
|
|
17
|
+
features: Object.freeze(['text.composition', 'text.selection', 'text.secure', 'accessibility', 'layout.rtl', 'motion.reduced']) });
|
|
18
|
+
#root;
|
|
19
|
+
#nodes = new Map();
|
|
20
|
+
#cleanup = new Set();
|
|
21
|
+
#identity = ++rendererSequence;
|
|
22
|
+
#emit;
|
|
23
|
+
#mounted = false;
|
|
24
|
+
#disposed = false;
|
|
25
|
+
#suspended = false;
|
|
26
|
+
#applying = false;
|
|
27
|
+
#revision = 0;
|
|
28
|
+
#failure;
|
|
29
|
+
#resize;
|
|
30
|
+
#frame;
|
|
31
|
+
#themeKeys = [];
|
|
32
|
+
#sequence = 0;
|
|
33
|
+
#counts = { commits: 0, created: 0, updated: 0, moved: 0, removed: 0, measurements: 0, activeListeners: 0 };
|
|
34
|
+
constructor(host, options = {}) {
|
|
35
|
+
this.host = host;
|
|
36
|
+
this.options = options;
|
|
37
|
+
if (host.ownerDocument.defaultView !== window)
|
|
38
|
+
throw new HYUIError('capability', 'Web renderer requires a mount point in the current browser realm');
|
|
39
|
+
this.#root = document.createElement('div');
|
|
40
|
+
this.#root.dataset.hyPortableRoot = '';
|
|
41
|
+
const style = document.createElement('style');
|
|
42
|
+
style.textContent = rootCSS;
|
|
43
|
+
this.#root.append(style);
|
|
44
|
+
this.setEnvironment(options.environment ?? {});
|
|
45
|
+
}
|
|
46
|
+
get diagnostics() { return Object.freeze({ ...this.#counts, activeNodes: this.#nodes.size, activeResources: [...this.#nodes.values()].filter(e => e.platformMounted).length }); }
|
|
47
|
+
mount(emit) {
|
|
48
|
+
if (this.#mounted || this.#disposed)
|
|
49
|
+
throw new HYUIError('lifecycle', 'Web renderer already mounted or disposed');
|
|
50
|
+
if (!this.host.isConnected)
|
|
51
|
+
throw new HYUIError('lifecycle', 'Web renderer mount point must be connected');
|
|
52
|
+
this.#mounted = true;
|
|
53
|
+
this.#emit = emit;
|
|
54
|
+
this.host.append(this.#root);
|
|
55
|
+
this.#resize = new ResizeObserver(() => this.#scheduleLayout());
|
|
56
|
+
this.#listen(this.#cleanup, window, 'resize', () => this.#scheduleLayout());
|
|
57
|
+
this.#listen(this.#cleanup, window, 'scroll', () => this.#scheduleLayout(), true);
|
|
58
|
+
if (window.visualViewport)
|
|
59
|
+
for (const type of ['resize', 'scroll'])
|
|
60
|
+
this.#listen(this.#cleanup, window.visualViewport, type, () => this.#scheduleLayout());
|
|
61
|
+
this.#listen(this.#cleanup, this.#root, 'keydown', event => this.#trapTab(event));
|
|
62
|
+
}
|
|
63
|
+
#assertReady() {
|
|
64
|
+
if (this.#failure)
|
|
65
|
+
throw this.#failure;
|
|
66
|
+
if (!this.#mounted || this.#disposed)
|
|
67
|
+
throw new HYUIError('lifecycle', 'Web renderer is not mounted');
|
|
68
|
+
}
|
|
69
|
+
commit(revision, mutations) {
|
|
70
|
+
this.#assertReady();
|
|
71
|
+
if (this.#applying || !Number.isSafeInteger(revision) || revision <= this.#revision)
|
|
72
|
+
throw new HYUIError('lifecycle', 'Non-monotonic or reentrant renderer commit', { revision });
|
|
73
|
+
const restore = captureFocus(document);
|
|
74
|
+
this.#applying = true;
|
|
75
|
+
let current;
|
|
76
|
+
try {
|
|
77
|
+
const local = mutations.every(m => m.type === 'update' && ['Text', 'Button', 'Toggle', 'Select', 'TextInput', 'TextArea', 'Slider', 'Image', 'Icon'].includes(m.node.kind));
|
|
78
|
+
// Leaf value/text updates preserve the relation tree and accessible label.
|
|
79
|
+
// Containers, structural edits and changed accessibility props still sync.
|
|
80
|
+
const relations = local ? mutations.filter(m => {
|
|
81
|
+
const node = m.node;
|
|
82
|
+
return !equalData(this.#get(node.key).node.props.a11y, node.props.a11y);
|
|
83
|
+
}).map(m => this.#get(m.node.key)) : undefined;
|
|
84
|
+
for (const mutation of mutations) {
|
|
85
|
+
current = mutation;
|
|
86
|
+
this.#mutate(mutation, revision);
|
|
87
|
+
}
|
|
88
|
+
current = undefined;
|
|
89
|
+
this.#syncRelations(relations ?? this.#nodes.values());
|
|
90
|
+
this.#syncResources();
|
|
91
|
+
restore();
|
|
92
|
+
this.#revision = revision;
|
|
93
|
+
this.#counts.commits++;
|
|
94
|
+
}
|
|
95
|
+
catch (cause) {
|
|
96
|
+
const key = current ? ('node' in current ? current.node.key : current.key) : '<page>';
|
|
97
|
+
const error = new HYUIError('renderer', 'Web mutation failed', { key, operation: current?.type ?? 'finalize', revision,
|
|
98
|
+
...(cause instanceof HYUIError ? cause.context : {}) }, cause);
|
|
99
|
+
this.#failure = error;
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
this.#applying = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
#mutate(mutation, revision) {
|
|
107
|
+
if (mutation.type === 'create') {
|
|
108
|
+
if (this.#nodes.has(mutation.node.key))
|
|
109
|
+
throw new HYUIError('duplicate-key', 'Duplicate renderer key');
|
|
110
|
+
const entry = this.#create(mutation.node, revision);
|
|
111
|
+
this.#update(entry, mutation.node, revision);
|
|
112
|
+
this.#counts.created++;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (mutation.type === 'update') {
|
|
116
|
+
const entry = this.#get(mutation.node.key);
|
|
117
|
+
if (entry.node.kind !== mutation.node.kind)
|
|
118
|
+
throw new HYUIError('invalid-props', 'Changing kind requires remove/create');
|
|
119
|
+
this.#update(entry, mutation.node, revision);
|
|
120
|
+
this.#counts.updated++;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const entry = this.#get(mutation.key);
|
|
124
|
+
if (mutation.type === 'remove') {
|
|
125
|
+
this.#remove(entry);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (mutation.type === 'insert' && entry.parent !== undefined)
|
|
129
|
+
throw new HYUIError('lifecycle', 'Node already inserted');
|
|
130
|
+
if (mutation.type === 'move' && entry.parent === undefined)
|
|
131
|
+
throw new HYUIError('lifecycle', 'Cannot move a detached node');
|
|
132
|
+
const parent = mutation.parent === null ? undefined : this.#get(mutation.parent);
|
|
133
|
+
const container = parent ? this.#container(parent, mutation.slot) : this.#root;
|
|
134
|
+
if (!parent && mutation.slot !== undefined)
|
|
135
|
+
throw new HYUIError('invalid-props', 'Root cannot have a named slot');
|
|
136
|
+
const before = mutation.before === null ? null : this.#get(mutation.before);
|
|
137
|
+
if (before && (before.parent !== mutation.parent || before.slot !== mutation.slot))
|
|
138
|
+
throw new HYUIError('invalid-props', 'Placement anchor belongs to a different slot');
|
|
139
|
+
let ancestor = parent;
|
|
140
|
+
while (ancestor) {
|
|
141
|
+
if (ancestor === entry)
|
|
142
|
+
throw new HYUIError('invalid-props', 'Cyclic renderer placement');
|
|
143
|
+
ancestor = ancestor.parent ? this.#nodes.get(ancestor.parent) : undefined;
|
|
144
|
+
}
|
|
145
|
+
entry.parent = mutation.parent;
|
|
146
|
+
entry.slot = mutation.slot;
|
|
147
|
+
attribute(entry.element, 'slot', parent && (parent.node.kind === 'Tabs' || parent.node.kind === 'Dialog') ? mutation.slot : undefined);
|
|
148
|
+
const movable = container;
|
|
149
|
+
if (mutation.type === 'move' && entry.element.isConnected && container.isConnected && movable.moveBefore)
|
|
150
|
+
movable.moveBefore(entry.element, before?.element ?? null);
|
|
151
|
+
else
|
|
152
|
+
container.insertBefore(entry.element, before?.element ?? null);
|
|
153
|
+
applyLayout(entry.element, entry.node.props.layout, parent?.node.kind === 'Overlay');
|
|
154
|
+
if (mutation.type === 'move')
|
|
155
|
+
this.#counts.moved++;
|
|
156
|
+
}
|
|
157
|
+
#get(key) { const entry = this.#nodes.get(key); if (!entry)
|
|
158
|
+
throw new HYUIError('invalid-props', 'Unknown renderer key', { key }); return entry; }
|
|
159
|
+
#create(node, revision) {
|
|
160
|
+
const tag = tags[node.kind];
|
|
161
|
+
if (!tag)
|
|
162
|
+
throw new HYUIError('capability', 'Unsupported Web component', { key: node.key });
|
|
163
|
+
if (tag.startsWith('hy-') && !customElements.get(tag))
|
|
164
|
+
throw new HYUIError('lifecycle', 'Call definePortableWebComponents() before mounting', { key: node.key });
|
|
165
|
+
const element = document.createElement(tag);
|
|
166
|
+
element.dataset.hyKey = node.key;
|
|
167
|
+
element.dataset.hyKind = node.kind;
|
|
168
|
+
const entry = { node, element, parent: undefined, slot: undefined, cleanup: new Set(), slots: new Map(), platformMounted: false, id: `hy-portable-${this.#identity}-${++this.#sequence}` };
|
|
169
|
+
// Register immediately so a failed initialization is still owned by dispose().
|
|
170
|
+
this.#nodes.set(node.key, entry);
|
|
171
|
+
if (element.shadowRoot) {
|
|
172
|
+
const style = document.createElement('style');
|
|
173
|
+
style.textContent = componentCSS;
|
|
174
|
+
element.shadowRoot.append(style);
|
|
175
|
+
}
|
|
176
|
+
const control = createControl(node, { host: element, emit: event => this.#event(entry, event), interactive: () => this.#interactive(entry),
|
|
177
|
+
listen: (target, type, listener, capture = false) => this.#listen(entry.cleanup, target, type, event => {
|
|
178
|
+
const owner = event.composedPath().find(item => item instanceof HTMLElement && item.hasAttribute('data-hy-key'));
|
|
179
|
+
const buttonContent = node.kind === 'Button' && event.type === 'click' && owner instanceof Node && element.contains(owner);
|
|
180
|
+
if (this.#interactive(entry) && (!owner || owner === element || buttonContent))
|
|
181
|
+
listener(event);
|
|
182
|
+
}, capture) }, revision);
|
|
183
|
+
if (control)
|
|
184
|
+
entry.control = control;
|
|
185
|
+
if (node.kind !== 'TextInput' && node.kind !== 'TextArea') {
|
|
186
|
+
for (const type of ['focusin', 'focusout'])
|
|
187
|
+
this.#listen(entry.cleanup, element, type, event => {
|
|
188
|
+
const owner = event.composedPath().find(item => item instanceof HTMLElement && item.hasAttribute('data-hy-key'));
|
|
189
|
+
if (owner === element)
|
|
190
|
+
this.#event(entry, { key: node.key, type: type === 'focusin' ? 'focus' : 'blur', reason: 'keyboard', transaction: 0 });
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (node.kind === 'FormGroup') {
|
|
194
|
+
entry.label = document.createElement('legend');
|
|
195
|
+
entry.error = document.createElement('span');
|
|
196
|
+
entry.error.dataset.hyError = '';
|
|
197
|
+
entry.label.id = `${entry.id}-label`;
|
|
198
|
+
entry.error.id = `${entry.id}-error`;
|
|
199
|
+
element.append(entry.label, entry.error);
|
|
200
|
+
}
|
|
201
|
+
if (node.kind === 'PlatformView') {
|
|
202
|
+
const factory = this.options.platformViews?.[node.props.slot];
|
|
203
|
+
if (!factory)
|
|
204
|
+
throw new HYUIError('capability', `No PlatformView factory for ${node.props.slot}`, { key: node.key });
|
|
205
|
+
entry.platform = new HYPlatformViewController(() => factory(element, { key: node.key, slot: node.props.slot }), node.props.resourceId, node.props.visible !== false);
|
|
206
|
+
}
|
|
207
|
+
return entry;
|
|
208
|
+
}
|
|
209
|
+
#update(entry, node, revision) {
|
|
210
|
+
hyValidateProps(node.kind, node.props, { key: node.key, ...(node.source ? { source: node.source } : {}) }, this.capabilities);
|
|
211
|
+
const previous = entry.node;
|
|
212
|
+
if (node.kind === 'PlatformView' && previous.kind === 'PlatformView' && node.props.slot !== previous.props.slot)
|
|
213
|
+
throw new HYUIError('invalid-props', 'PlatformView factory slot is immutable; use a new key', { key: node.key });
|
|
214
|
+
entry.node = node;
|
|
215
|
+
const element = entry.element;
|
|
216
|
+
if (entry.parent === undefined || !equalData(previous.props.layout, node.props.layout))
|
|
217
|
+
applyLayout(element, node.props.layout, entry.parent ? this.#nodes.get(entry.parent)?.node.kind === 'Overlay' : false);
|
|
218
|
+
flag(element, 'hidden', node.props.visible === false);
|
|
219
|
+
entry.control?.update(node, revision);
|
|
220
|
+
if (node.kind === 'Text') {
|
|
221
|
+
if (element.textContent !== node.props.text)
|
|
222
|
+
element.textContent = node.props.text;
|
|
223
|
+
element.style.whiteSpace = node.props.wrap === false ? 'pre' : 'pre-wrap';
|
|
224
|
+
}
|
|
225
|
+
if (node.kind === 'Scroll') {
|
|
226
|
+
element.style.overflowX = node.props.axis === 'horizontal' ? 'auto' : 'hidden';
|
|
227
|
+
element.style.overflowY = node.props.axis === 'vertical' ? 'auto' : 'hidden';
|
|
228
|
+
if (node.props.layout?.clip)
|
|
229
|
+
element.style.overflow = 'hidden';
|
|
230
|
+
element.tabIndex = 0;
|
|
231
|
+
}
|
|
232
|
+
if (node.kind === 'Image' || node.kind === 'Icon') {
|
|
233
|
+
const image = element;
|
|
234
|
+
if (!image.hasAttribute('src') || !('resource' in previous.props) || !equalData(node.props.resource, previous.props.resource)) {
|
|
235
|
+
const url = this.options.resolveResource?.(node.props.resource, { key: node.key, kind: node.kind });
|
|
236
|
+
if (typeof url !== 'string' || !url.trim())
|
|
237
|
+
throw new HYUIError('capability', `Unresolved image resource ${node.props.resource.id}`, { key: node.key });
|
|
238
|
+
image.src = url;
|
|
239
|
+
}
|
|
240
|
+
image.alt = node.props.resource.alt;
|
|
241
|
+
image.style.objectFit = node.kind === 'Image' ? (node.props.fit ?? 'contain') : 'contain';
|
|
242
|
+
}
|
|
243
|
+
if (node.kind === 'FormGroup') {
|
|
244
|
+
entry.label.textContent = node.props.label;
|
|
245
|
+
entry.error.textContent = node.props.error ?? '';
|
|
246
|
+
entry.error.hidden = !node.props.error;
|
|
247
|
+
}
|
|
248
|
+
if (node.kind === 'PlatformView')
|
|
249
|
+
entry.platform.update(node.props.resourceId);
|
|
250
|
+
if (node.kind === 'Tabs')
|
|
251
|
+
for (const child of this.#nodes.values())
|
|
252
|
+
if (child.parent === node.key)
|
|
253
|
+
this.#container(entry, child.slot);
|
|
254
|
+
}
|
|
255
|
+
#container(parent, slot) {
|
|
256
|
+
if (parent.node.kind === 'Tabs') {
|
|
257
|
+
if (slot === undefined || !parent.node.props.options.some(option => option.value === slot))
|
|
258
|
+
throw new HYUIError('capability', 'Tabs children need an option-valued named slot', { key: parent.node.key });
|
|
259
|
+
return parent.element;
|
|
260
|
+
}
|
|
261
|
+
if (parent.node.kind === 'Dialog') {
|
|
262
|
+
if (slot !== undefined && slot !== 'footer')
|
|
263
|
+
throw new HYUIError('capability', 'Dialog supports only the footer named slot', { key: parent.node.key });
|
|
264
|
+
return parent.element;
|
|
265
|
+
}
|
|
266
|
+
if (!['Row', 'Column', 'Grid', 'Overlay', 'Scroll', 'FormGroup', 'Button'].includes(parent.node.kind))
|
|
267
|
+
throw new HYUIError('capability', 'This component cannot contain children', { key: parent.node.key });
|
|
268
|
+
if (slot === undefined)
|
|
269
|
+
return parent.element;
|
|
270
|
+
if (parent.node.kind === 'Button')
|
|
271
|
+
throw new HYUIError('capability', 'Button supports only default content', { key: parent.node.key });
|
|
272
|
+
let group = parent.slots.get(slot);
|
|
273
|
+
if (!group) {
|
|
274
|
+
group = document.createElement('div');
|
|
275
|
+
group.dataset.hySlotGroup = slot;
|
|
276
|
+
parent.element.append(group);
|
|
277
|
+
parent.slots.set(slot, group);
|
|
278
|
+
}
|
|
279
|
+
return group;
|
|
280
|
+
}
|
|
281
|
+
#shown(entry) {
|
|
282
|
+
let current = entry;
|
|
283
|
+
while (current) {
|
|
284
|
+
if (current.node.props.visible === false || (current.node.kind === 'Dialog' && !current.node.props.open))
|
|
285
|
+
return false;
|
|
286
|
+
const parent = current.parent ? this.#nodes.get(current.parent) : undefined;
|
|
287
|
+
if (parent?.node.kind === 'Tabs' && current.slot !== parent.node.props.value)
|
|
288
|
+
return false;
|
|
289
|
+
current = parent;
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
#interactive(entry) {
|
|
294
|
+
if (this.#disposed || this.#suspended || this.#applying || this.#failure || !this.#nodes.has(entry.node.key) || !this.#shown(entry))
|
|
295
|
+
return false;
|
|
296
|
+
let current = entry;
|
|
297
|
+
while (current) {
|
|
298
|
+
const props = current.node.props;
|
|
299
|
+
if (('disabled' in props && props.disabled) || ('busy' in props && props.busy))
|
|
300
|
+
return false;
|
|
301
|
+
current = current.parent ? this.#nodes.get(current.parent) : undefined;
|
|
302
|
+
}
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
#event(entry, event) { if (this.#interactive(entry))
|
|
306
|
+
this.#emit?.(event); }
|
|
307
|
+
#syncRelations(entries = this.#nodes.values()) {
|
|
308
|
+
for (const entry of entries) {
|
|
309
|
+
if (entry.element instanceof HYDialog)
|
|
310
|
+
entry.element.open = !this.#suspended && this.#shown(entry);
|
|
311
|
+
this.#a11y(entry);
|
|
312
|
+
for (const [name, container] of entry.slots)
|
|
313
|
+
if (!container.childElementCount) {
|
|
314
|
+
container.remove();
|
|
315
|
+
entry.slots.delete(name);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
#a11y(entry) {
|
|
320
|
+
const { element, node } = entry;
|
|
321
|
+
let group = entry.parent ? this.#nodes.get(entry.parent) : undefined;
|
|
322
|
+
while (group && group.node.kind !== 'FormGroup')
|
|
323
|
+
group = group.parent ? this.#nodes.get(group.parent) : undefined;
|
|
324
|
+
const groupProps = group?.node.kind === 'FormGroup' ? group.node.props : undefined;
|
|
325
|
+
const label = node.props.a11y?.label ?? (entry.control || node.kind === 'TextArea' ? groupProps?.label : undefined);
|
|
326
|
+
const description = [node.props.a11y?.description, node.props.a11y?.error ?? groupProps?.error].filter(Boolean).join('. ');
|
|
327
|
+
const a11y = label === undefined ? undefined : { ...node.props.a11y, label, ...(groupProps?.error && !node.props.a11y?.error ? { error: groupProps.error } : {}) };
|
|
328
|
+
applyAccessibility(element, a11y);
|
|
329
|
+
const targets = element instanceof HYTabs ? [...element.shadowRoot.querySelectorAll('[role=tablist],button')]
|
|
330
|
+
: element.shadowRoot ? [element.shadowRoot.querySelector(node.kind === 'Slider' ? 'button.upper' : 'input:not([type=search]),select,button,dialog')].filter((el) => !!el) : [element];
|
|
331
|
+
for (const target of targets) {
|
|
332
|
+
// Tabs label belongs to the tablist; each tab keeps its option name.
|
|
333
|
+
if (!(element instanceof HYTabs) || target.getAttribute('role') === 'tablist')
|
|
334
|
+
applyAccessibility(target, a11y);
|
|
335
|
+
attribute(target, 'aria-description', description || undefined);
|
|
336
|
+
attribute(target, 'aria-invalid', (node.props.a11y?.error ?? groupProps?.error) ? 'true' : undefined);
|
|
337
|
+
if (description) {
|
|
338
|
+
const scope = target.getRootNode();
|
|
339
|
+
// References stay in the same tree scope, including inside legacy ShadowRoots.
|
|
340
|
+
if (scope instanceof ShadowRoot) {
|
|
341
|
+
let info = scope.querySelector('[data-hy-a11y-description]');
|
|
342
|
+
if (!info) {
|
|
343
|
+
info = document.createElement('span');
|
|
344
|
+
info.dataset.hyA11yDescription = '';
|
|
345
|
+
info.id = `${entry.id}-description`;
|
|
346
|
+
info.hidden = true;
|
|
347
|
+
scope.append(info);
|
|
348
|
+
}
|
|
349
|
+
if (info.textContent !== description)
|
|
350
|
+
info.textContent = description;
|
|
351
|
+
attribute(target, 'aria-describedby', info.id);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else
|
|
355
|
+
attribute(target, 'aria-describedby', undefined);
|
|
356
|
+
}
|
|
357
|
+
if (!entry.control && node.kind !== 'TextArea') {
|
|
358
|
+
const role = node.props.a11y?.role;
|
|
359
|
+
attribute(element, 'role', role === 'text' ? undefined : role === 'image' ? 'img' : role);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
#syncResources() {
|
|
363
|
+
for (const entry of this.#nodes.values())
|
|
364
|
+
if (entry.platform && entry.element.isConnected) {
|
|
365
|
+
try {
|
|
366
|
+
entry.platform.visible(this.#shown(entry));
|
|
367
|
+
if (!entry.platformMounted) {
|
|
368
|
+
if (this.#suspended)
|
|
369
|
+
entry.platform.suspend();
|
|
370
|
+
entry.platform.mount();
|
|
371
|
+
entry.platformMounted = true;
|
|
372
|
+
this.#resize?.observe(entry.element);
|
|
373
|
+
}
|
|
374
|
+
if (this.#suspended)
|
|
375
|
+
entry.platform.suspend();
|
|
376
|
+
else {
|
|
377
|
+
entry.platform.resume();
|
|
378
|
+
const rect = entry.element.getBoundingClientRect();
|
|
379
|
+
this.#counts.measurements++;
|
|
380
|
+
entry.platform.layout({ x: rect.x, y: rect.y, width: rect.width, height: rect.height, scale: window.devicePixelRatio || 1 });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch (cause) {
|
|
384
|
+
throw new HYUIError('renderer', 'PlatformView lifecycle failed', { key: entry.node.key, ...(entry.node.source ? { source: entry.node.source } : {}) }, cause);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
#scheduleLayout() {
|
|
389
|
+
if (this.#frame !== undefined || this.#disposed || this.#suspended || this.#failure)
|
|
390
|
+
return;
|
|
391
|
+
this.#frame = requestAnimationFrame(() => {
|
|
392
|
+
this.#frame = undefined;
|
|
393
|
+
try {
|
|
394
|
+
this.#syncResources();
|
|
395
|
+
}
|
|
396
|
+
catch (cause) {
|
|
397
|
+
const error = new HYUIError('renderer', 'Web resource layout failed', { operation: 'layout', revision: this.#revision, ...(cause instanceof HYUIError ? cause.context : {}) }, cause);
|
|
398
|
+
this.#failure = error;
|
|
399
|
+
if (this.options.onError)
|
|
400
|
+
this.options.onError(error);
|
|
401
|
+
else
|
|
402
|
+
queueMicrotask(() => { throw error; });
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
#trapTab(event) {
|
|
407
|
+
if (event.key !== 'Tab' || event.defaultPrevented || this.#suspended)
|
|
408
|
+
return;
|
|
409
|
+
const dialog = [...this.#nodes.values()].filter(entry => entry.element instanceof HYDialog && entry.element.open).at(-1);
|
|
410
|
+
if (!dialog)
|
|
411
|
+
return;
|
|
412
|
+
const choices = focusable(dialog.element.shadowRoot), active = deepActive(document);
|
|
413
|
+
const index = choices.findIndex(choice => choice === active);
|
|
414
|
+
if (!choices.length) {
|
|
415
|
+
event.preventDefault();
|
|
416
|
+
dialog.control?.focus();
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if ((event.shiftKey && index <= 0) || (!event.shiftKey && (index === choices.length - 1 || index === -1))) {
|
|
420
|
+
event.preventDefault();
|
|
421
|
+
choices[event.shiftKey ? choices.length - 1 : 0].focus();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
focus(key) {
|
|
425
|
+
this.#assertReady();
|
|
426
|
+
const entry = this.#get(key);
|
|
427
|
+
if (this.#suspended || !this.#shown(entry))
|
|
428
|
+
return;
|
|
429
|
+
if (entry.platform) {
|
|
430
|
+
entry.platform.focus();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (entry.control) {
|
|
434
|
+
entry.control.focus();
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (entry.element.tabIndex < 0)
|
|
438
|
+
entry.element.tabIndex = -1;
|
|
439
|
+
entry.element.focus({ preventScroll: true });
|
|
440
|
+
}
|
|
441
|
+
setEnvironment(environment) {
|
|
442
|
+
if (this.#disposed)
|
|
443
|
+
throw new HYUIError('lifecycle', 'Web renderer disposed');
|
|
444
|
+
const { safeArea = [0, 0, 0, 0], fontScale = 1, direction = 'ltr' } = environment;
|
|
445
|
+
if (!Number.isFinite(fontScale) || fontScale <= 0 || !['ltr', 'rtl'].includes(direction)
|
|
446
|
+
|| (safeArea !== 'environment' && (!Array.isArray(safeArea) || safeArea.length !== 4 || !safeArea.every(v => Number.isFinite(v) && v >= 0))))
|
|
447
|
+
throw new HYUIError('invalid-props', 'Invalid Web environment');
|
|
448
|
+
const theme = environment.theme ? hyThemeVariables(environment.theme, fontScale) : undefined;
|
|
449
|
+
for (const key of this.#themeKeys)
|
|
450
|
+
this.#root.style.removeProperty(key);
|
|
451
|
+
this.#themeKeys = [];
|
|
452
|
+
if (theme) {
|
|
453
|
+
for (const [key, value] of Object.entries(theme)) {
|
|
454
|
+
this.#root.style.setProperty(key, value);
|
|
455
|
+
this.#themeKeys.push(key);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
else if (environment.fontScale !== undefined) {
|
|
459
|
+
const inherited = getComputedStyle(this.host);
|
|
460
|
+
for (const [key, fallback] of [['--hy-font-size', 14], ['--hy-line-height', 22]]) {
|
|
461
|
+
const base = parseFloat(inherited.getPropertyValue(key)) || fallback;
|
|
462
|
+
this.#root.style.setProperty(key, `${base * fontScale}px`);
|
|
463
|
+
this.#themeKeys.push(key);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
this.#root.dir = direction;
|
|
467
|
+
flag(this.#root, 'data-hy-reduced-motion', !!environment.reducedMotion);
|
|
468
|
+
this.#root.style.padding = safeArea === 'environment' ? ['top', 'right', 'bottom', 'left'].map(side => `env(safe-area-inset-${side}, 0px)`).join(' ') : safeArea.map(value => `${value}px`).join(' ');
|
|
469
|
+
this.#scheduleLayout();
|
|
470
|
+
}
|
|
471
|
+
suspend() { this.#assertReady(); if (this.#suspended)
|
|
472
|
+
return; this.#suspended = true; this.#root.inert = true; this.#syncRelations(); for (const entry of this.#nodes.values())
|
|
473
|
+
entry.platform?.suspend(); }
|
|
474
|
+
resume() { this.#assertReady(); if (!this.#suspended)
|
|
475
|
+
return; this.#suspended = false; this.#root.inert = false; this.#applying = true; try {
|
|
476
|
+
this.#syncRelations();
|
|
477
|
+
this.#syncResources();
|
|
478
|
+
}
|
|
479
|
+
finally {
|
|
480
|
+
this.#applying = false;
|
|
481
|
+
} }
|
|
482
|
+
#listen(cleanup, target, type, listener, capture = false) {
|
|
483
|
+
target.addEventListener(type, listener, capture);
|
|
484
|
+
this.#counts.activeListeners++;
|
|
485
|
+
let live = true;
|
|
486
|
+
const off = () => { if (!live)
|
|
487
|
+
return; live = false; target.removeEventListener(type, listener, capture); cleanup.delete(off); this.#counts.activeListeners--; };
|
|
488
|
+
cleanup.add(off);
|
|
489
|
+
return off;
|
|
490
|
+
}
|
|
491
|
+
#remove(entry) {
|
|
492
|
+
const errors = [];
|
|
493
|
+
for (const child of [...this.#nodes.values()])
|
|
494
|
+
if (child.parent === entry.node.key)
|
|
495
|
+
try {
|
|
496
|
+
this.#remove(child);
|
|
497
|
+
}
|
|
498
|
+
catch (cause) {
|
|
499
|
+
errors.push(cause);
|
|
500
|
+
}
|
|
501
|
+
for (const off of entry.cleanup)
|
|
502
|
+
off();
|
|
503
|
+
this.#resize?.unobserve(entry.element);
|
|
504
|
+
try {
|
|
505
|
+
entry.control?.dispose();
|
|
506
|
+
}
|
|
507
|
+
catch (cause) {
|
|
508
|
+
errors.push(cause);
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
entry.platform?.dispose();
|
|
512
|
+
}
|
|
513
|
+
catch (cause) {
|
|
514
|
+
errors.push(cause);
|
|
515
|
+
}
|
|
516
|
+
entry.element.remove();
|
|
517
|
+
this.#nodes.delete(entry.node.key);
|
|
518
|
+
this.#counts.removed++;
|
|
519
|
+
if (errors.length)
|
|
520
|
+
throw new AggregateError(errors, 'Web node cleanup failed');
|
|
521
|
+
}
|
|
522
|
+
dispose() {
|
|
523
|
+
if (this.#disposed)
|
|
524
|
+
return;
|
|
525
|
+
this.#disposed = true;
|
|
526
|
+
if (this.#frame !== undefined)
|
|
527
|
+
cancelAnimationFrame(this.#frame);
|
|
528
|
+
this.#resize?.disconnect();
|
|
529
|
+
const errors = [];
|
|
530
|
+
while (this.#nodes.size)
|
|
531
|
+
try {
|
|
532
|
+
this.#remove(this.#nodes.values().next().value);
|
|
533
|
+
}
|
|
534
|
+
catch (cause) {
|
|
535
|
+
errors.push(cause);
|
|
536
|
+
}
|
|
537
|
+
for (const off of this.#cleanup)
|
|
538
|
+
off();
|
|
539
|
+
this.#root.remove();
|
|
540
|
+
this.#emit = undefined;
|
|
541
|
+
if (errors.length)
|
|
542
|
+
throw new AggregateError(errors, 'Web renderer cleanup failed');
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
//# sourceMappingURL=renderer.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Only the renderer-owned wrapper and owned ShadowRoots receive these rules. */
|
|
2
|
+
export declare const rootCSS = "\n[data-hy-portable-root] { box-sizing:border-box; min-width:0; min-height:0; width:100%; height:100%;\n color:var(--hy-text-color,#d8e2f2); background:var(--hy-surface-color,#171d28);\n font:var(--hy-font-size,14px)/var(--hy-line-height,22px) system-ui,sans-serif; }\n[data-hy-portable-root] [data-hy-key] { box-sizing:border-box; min-width:0; min-height:0; }\n[data-hy-portable-root] [data-hy-key][hidden] { display:none !important; }\n[data-hy-portable-root] [data-hy-kind=Row] { display:flex; flex-direction:row; }\n[data-hy-portable-root] [data-hy-kind=RadioGroup] { display:flex; flex-wrap:wrap; gap:var(--hy-gap,8px); }\n[data-hy-portable-root] [data-hy-kind=RadioGroup] hy-radio { font:inherit; min-height:44px; }\n[data-hy-portable-root] [data-hy-kind=Column] { display:flex; flex-direction:column; }\n[data-hy-portable-root] [data-hy-kind=Grid], [data-hy-portable-root] [data-hy-kind=Overlay] { display:grid; }\n[data-hy-portable-root] [data-hy-kind=Text] { display:block; overflow-wrap:anywhere; white-space:pre-wrap; }\n[data-hy-portable-root] [data-hy-kind=Icon] { width:1.25em; height:1.25em; object-fit:contain; }\n[data-hy-portable-root] [data-hy-kind=FormGroup] { border:0; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--hy-gap,8px); }\n[data-hy-portable-root] [data-hy-kind=FormGroup] > legend { padding:0; }\n[data-hy-portable-root] [data-hy-error] { color:var(--hy-invalid-border-color,#d65c7c); font-size:.9em; }\n[data-hy-portable-root] textarea { box-sizing:border-box; width:100%; min-height:5em; resize:vertical; padding:8px;\n color:var(--hy-text-color,#d8e2f2); background:var(--hy-input-bg-color,#121822); border:1px solid var(--hy-border-color,#303746);\n border-radius:var(--hy-radius,4px); font:inherit; }\n[data-hy-portable-root] textarea:focus-visible { outline:2px solid var(--hy-focus-ring-color,#8177f2); outline-offset:1px; }\n[data-hy-portable-root] [data-hy-slot-group] { display:contents; }\n[data-hy-portable-root][data-hy-reduced-motion] { --hy-portable-transition:none; }\n@media(prefers-reduced-motion:reduce) { [data-hy-portable-root] { --hy-portable-transition:none; } }\n";
|
|
3
|
+
export declare const componentCSS = "\n:host { --hy-input-font:var(--hy-font-size,14px)/var(--hy-line-height,22px) system-ui,sans-serif; font:inherit;\n --hy-panel-bg-color:var(--hy-surface-color,#171d28); --hy-surface-elevated-color:var(--hy-surface-color,#171d28);\n --hy-menu-bg-color:var(--hy-surface-color,#171d28); --hy-dialog-bg-color:var(--hy-surface-color,#171d28);\n --hy-dialog-body-color:var(--hy-text-color,#d8e2f2); --hy-dialog-footer-bg-color:var(--hy-surface-color,#171d28);\n --hy-accent-strong-color:var(--hy-accent-color,#8177f2); --hy-selected-bg-color:var(--hy-accent-color,#8177f2);\n --hy-active-text-color:var(--hy-text-color,#d8e2f2); --hy-hover-bg-color:var(--hy-input-bg-color,#121822);\n --hy-focus-border-color:var(--hy-focus-ring-color,#8177f2); --hy-dialog-radius:var(--hy-radius,8px); }\nbutton, input, select { font:inherit; min-height:2em; border-radius:var(--hy-radius,4px); }\n/* The semantic host owns the touch target. Its actual interactive element\n must fill it too; otherwise a 44px portable Button still paints at 28px. */\n:host(hy-button), :host(hy-input), :host(hy-select) { display:inline-flex; }\n:host(hy-button) button, :host(hy-input) input, :host(hy-select) select {\n box-sizing:border-box; width:100%; min-height:inherit; height:100%; padding-inline:12px;\n}\n:host(hy-select) .wrap, :host(hy-input) .control { width:100%; min-height:inherit; height:100%; box-sizing:border-box; }\n:host(hy-input) input { min-height:0; }\nselect { height:auto; padding-block:4px; }\ninput { height:var(--hy-input-height,auto); padding-block:4px; }\n.box, .box::after, .handle { transition:var(--hy-portable-transition,background .12s ease); }\n:host(hy-checkbox) { width:auto; }\n:host(hy-checkbox) label, :host(hy-checkbox) .label { white-space:normal; }\n:host(hy-checkbox) input { min-height:0; }\n:host(hy-tabs) { height:auto; grid-template-rows:auto minmax(0,1fr); }\n:host(hy-tabs) .tabs { align-items:stretch; }\n:host(hy-tabs) button { height:auto; white-space:normal; }\n:host(hy-range) .handle { min-height:0; }\n:host(hy-dialog) .header { padding:16px 20px; }\n:host(hy-dialog) h2 { font-size:calc(var(--hy-font-size,14px) + 6px); }\n:host(hy-dialog) .body { display:flex; flex-direction:column; gap:var(--hy-gap,8px); padding:20px; }\n:host(hy-dialog) .body > slot { display:contents; }\n:host(hy-dialog) .footer { box-sizing:border-box; min-height:0; padding:12px 20px; }\n\nbutton:focus-visible, select:focus-visible, input:focus-visible { outline:2px solid var(--hy-focus-ring-color,#8177f2); outline-offset:1px; }\n[aria-invalid=true] { border-color:var(--hy-invalid-border-color,#d65c7c); }\n";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Only the renderer-owned wrapper and owned ShadowRoots receive these rules. */
|
|
2
|
+
export const rootCSS = `
|
|
3
|
+
[data-hy-portable-root] { box-sizing:border-box; min-width:0; min-height:0; width:100%; height:100%;
|
|
4
|
+
color:var(--hy-text-color,#d8e2f2); background:var(--hy-surface-color,#171d28);
|
|
5
|
+
font:var(--hy-font-size,14px)/var(--hy-line-height,22px) system-ui,sans-serif; }
|
|
6
|
+
[data-hy-portable-root] [data-hy-key] { box-sizing:border-box; min-width:0; min-height:0; }
|
|
7
|
+
[data-hy-portable-root] [data-hy-key][hidden] { display:none !important; }
|
|
8
|
+
[data-hy-portable-root] [data-hy-kind=Row] { display:flex; flex-direction:row; }
|
|
9
|
+
[data-hy-portable-root] [data-hy-kind=RadioGroup] { display:flex; flex-wrap:wrap; gap:var(--hy-gap,8px); }
|
|
10
|
+
[data-hy-portable-root] [data-hy-kind=RadioGroup] hy-radio { font:inherit; min-height:44px; }
|
|
11
|
+
[data-hy-portable-root] [data-hy-kind=Column] { display:flex; flex-direction:column; }
|
|
12
|
+
[data-hy-portable-root] [data-hy-kind=Grid], [data-hy-portable-root] [data-hy-kind=Overlay] { display:grid; }
|
|
13
|
+
[data-hy-portable-root] [data-hy-kind=Text] { display:block; overflow-wrap:anywhere; white-space:pre-wrap; }
|
|
14
|
+
[data-hy-portable-root] [data-hy-kind=Icon] { width:1.25em; height:1.25em; object-fit:contain; }
|
|
15
|
+
[data-hy-portable-root] [data-hy-kind=FormGroup] { border:0; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--hy-gap,8px); }
|
|
16
|
+
[data-hy-portable-root] [data-hy-kind=FormGroup] > legend { padding:0; }
|
|
17
|
+
[data-hy-portable-root] [data-hy-error] { color:var(--hy-invalid-border-color,#d65c7c); font-size:.9em; }
|
|
18
|
+
[data-hy-portable-root] textarea { box-sizing:border-box; width:100%; min-height:5em; resize:vertical; padding:8px;
|
|
19
|
+
color:var(--hy-text-color,#d8e2f2); background:var(--hy-input-bg-color,#121822); border:1px solid var(--hy-border-color,#303746);
|
|
20
|
+
border-radius:var(--hy-radius,4px); font:inherit; }
|
|
21
|
+
[data-hy-portable-root] textarea:focus-visible { outline:2px solid var(--hy-focus-ring-color,#8177f2); outline-offset:1px; }
|
|
22
|
+
[data-hy-portable-root] [data-hy-slot-group] { display:contents; }
|
|
23
|
+
[data-hy-portable-root][data-hy-reduced-motion] { --hy-portable-transition:none; }
|
|
24
|
+
@media(prefers-reduced-motion:reduce) { [data-hy-portable-root] { --hy-portable-transition:none; } }
|
|
25
|
+
`;
|
|
26
|
+
export const componentCSS = `
|
|
27
|
+
:host { --hy-input-font:var(--hy-font-size,14px)/var(--hy-line-height,22px) system-ui,sans-serif; font:inherit;
|
|
28
|
+
--hy-panel-bg-color:var(--hy-surface-color,#171d28); --hy-surface-elevated-color:var(--hy-surface-color,#171d28);
|
|
29
|
+
--hy-menu-bg-color:var(--hy-surface-color,#171d28); --hy-dialog-bg-color:var(--hy-surface-color,#171d28);
|
|
30
|
+
--hy-dialog-body-color:var(--hy-text-color,#d8e2f2); --hy-dialog-footer-bg-color:var(--hy-surface-color,#171d28);
|
|
31
|
+
--hy-accent-strong-color:var(--hy-accent-color,#8177f2); --hy-selected-bg-color:var(--hy-accent-color,#8177f2);
|
|
32
|
+
--hy-active-text-color:var(--hy-text-color,#d8e2f2); --hy-hover-bg-color:var(--hy-input-bg-color,#121822);
|
|
33
|
+
--hy-focus-border-color:var(--hy-focus-ring-color,#8177f2); --hy-dialog-radius:var(--hy-radius,8px); }
|
|
34
|
+
button, input, select { font:inherit; min-height:2em; border-radius:var(--hy-radius,4px); }
|
|
35
|
+
/* The semantic host owns the touch target. Its actual interactive element
|
|
36
|
+
must fill it too; otherwise a 44px portable Button still paints at 28px. */
|
|
37
|
+
:host(hy-button), :host(hy-input), :host(hy-select) { display:inline-flex; }
|
|
38
|
+
:host(hy-button) button, :host(hy-input) input, :host(hy-select) select {
|
|
39
|
+
box-sizing:border-box; width:100%; min-height:inherit; height:100%; padding-inline:12px;
|
|
40
|
+
}
|
|
41
|
+
:host(hy-select) .wrap, :host(hy-input) .control { width:100%; min-height:inherit; height:100%; box-sizing:border-box; }
|
|
42
|
+
:host(hy-input) input { min-height:0; }
|
|
43
|
+
select { height:auto; padding-block:4px; }
|
|
44
|
+
input { height:var(--hy-input-height,auto); padding-block:4px; }
|
|
45
|
+
.box, .box::after, .handle { transition:var(--hy-portable-transition,background .12s ease); }
|
|
46
|
+
:host(hy-checkbox) { width:auto; }
|
|
47
|
+
:host(hy-checkbox) label, :host(hy-checkbox) .label { white-space:normal; }
|
|
48
|
+
:host(hy-checkbox) input { min-height:0; }
|
|
49
|
+
:host(hy-tabs) { height:auto; grid-template-rows:auto minmax(0,1fr); }
|
|
50
|
+
:host(hy-tabs) .tabs { align-items:stretch; }
|
|
51
|
+
:host(hy-tabs) button { height:auto; white-space:normal; }
|
|
52
|
+
:host(hy-range) .handle { min-height:0; }
|
|
53
|
+
:host(hy-dialog) .header { padding:16px 20px; }
|
|
54
|
+
:host(hy-dialog) h2 { font-size:calc(var(--hy-font-size,14px) + 6px); }
|
|
55
|
+
:host(hy-dialog) .body { display:flex; flex-direction:column; gap:var(--hy-gap,8px); padding:20px; }
|
|
56
|
+
:host(hy-dialog) .body > slot { display:contents; }
|
|
57
|
+
:host(hy-dialog) .footer { box-sizing:border-box; min-height:0; padding:12px 20px; }
|
|
58
|
+
|
|
59
|
+
button:focus-visible, select:focus-visible, input:focus-visible { outline:2px solid var(--hy-focus-ring-color,#8177f2); outline-offset:1px; }
|
|
60
|
+
[aria-invalid=true] { border-color:var(--hy-invalid-border-color,#d65c7c); }
|
|
61
|
+
`;
|
|
62
|
+
//# sourceMappingURL=styles.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { HYPlatformView, HYResource, HYTheme, HYUIEvent } from '../../core/types.js';
|
|
2
|
+
import type { HYUIError } from '../../core/validation.js';
|
|
3
|
+
export interface HYWebEnvironment {
|
|
4
|
+
readonly theme?: HYTheme;
|
|
5
|
+
readonly fontScale?: number;
|
|
6
|
+
readonly direction?: 'ltr' | 'rtl';
|
|
7
|
+
readonly reducedMotion?: boolean;
|
|
8
|
+
/** Logical top/right/bottom/left insets, applied once outside the authored page. */
|
|
9
|
+
readonly safeArea?: readonly [number, number, number, number] | 'environment';
|
|
10
|
+
}
|
|
11
|
+
export interface HYWebPlatformContext {
|
|
12
|
+
readonly key: string;
|
|
13
|
+
readonly slot: string;
|
|
14
|
+
}
|
|
15
|
+
export interface HYWebRendererOptions {
|
|
16
|
+
readonly environment?: HYWebEnvironment;
|
|
17
|
+
readonly resolveResource?: (resource: HYResource, context: {
|
|
18
|
+
readonly key: string;
|
|
19
|
+
readonly kind: 'Image' | 'Icon';
|
|
20
|
+
}) => string;
|
|
21
|
+
readonly platformViews?: Readonly<Record<string, (host: HTMLElement, context: HYWebPlatformContext) => HYPlatformView>>;
|
|
22
|
+
/** Async resource layout failures stop this renderer. The shell disposes/recreates its page. */
|
|
23
|
+
readonly onError?: (error: HYUIError) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface HYWebDiagnostics {
|
|
26
|
+
readonly commits: number;
|
|
27
|
+
readonly created: number;
|
|
28
|
+
readonly updated: number;
|
|
29
|
+
readonly moved: number;
|
|
30
|
+
readonly removed: number;
|
|
31
|
+
readonly measurements: number;
|
|
32
|
+
readonly activeNodes: number;
|
|
33
|
+
readonly activeResources: number;
|
|
34
|
+
readonly activeListeners: number;
|
|
35
|
+
}
|
|
36
|
+
export interface HYWebControl {
|
|
37
|
+
update(node: import('../../core/types.js').HYNode, revision: number): void;
|
|
38
|
+
focus(): void;
|
|
39
|
+
dispose(): void;
|
|
40
|
+
}
|
|
41
|
+
export interface HYWebControlContext {
|
|
42
|
+
readonly host: HTMLElement;
|
|
43
|
+
readonly listen: (target: EventTarget, type: string, listener: (event: Event) => void, capture?: boolean) => () => void;
|
|
44
|
+
readonly emit: (event: HYUIEvent) => void;
|
|
45
|
+
readonly interactive: () => boolean;
|
|
46
|
+
}
|