@haiyue/ui 0.1.2 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/LICENSE +27 -0
- package/README.md +643 -2
- package/dist/adapters/web/controls.d.ts +19 -0
- package/dist/adapters/web/controls.js +307 -0
- package/dist/adapters/web/dom.d.ts +10 -0
- package/dist/adapters/web/dom.js +98 -0
- package/dist/adapters/web/index.d.ts +3 -0
- package/dist/adapters/web/index.js +3 -0
- package/dist/adapters/web/renderer.d.ts +18 -0
- package/dist/adapters/web/renderer.js +553 -0
- package/dist/adapters/web/styles.d.ts +3 -0
- package/dist/adapters/web/styles.js +83 -0
- package/dist/adapters/web/types.d.ts +46 -0
- package/dist/adapters/web/types.js +2 -0
- package/dist/affix.d.ts +35 -0
- package/dist/affix.js +137 -0
- package/dist/badge.d.ts +45 -0
- package/dist/badge.js +168 -0
- package/dist/breadcrumb.d.ts +37 -0
- package/dist/breadcrumb.js +210 -0
- package/dist/button-group.d.ts +12 -0
- package/dist/button-group.js +72 -0
- package/dist/button.d.ts +15 -1
- package/dist/button.js +101 -12
- package/dist/carousel.d.ts +92 -0
- package/dist/carousel.js +509 -0
- package/dist/cascader.d.ts +65 -0
- package/dist/cascader.js +479 -0
- package/dist/collapse.d.ts +68 -0
- package/dist/collapse.js +348 -0
- package/dist/core/actions.d.ts +32 -0
- package/dist/core/actions.js +145 -0
- package/dist/core/data.d.ts +4 -0
- package/dist/core/data.js +27 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +6 -0
- package/dist/core/platform-view.d.ts +17 -0
- package/dist/core/platform-view.js +84 -0
- package/dist/core/selection.d.ts +14 -0
- package/dist/core/selection.js +93 -0
- package/dist/core/text.d.ts +31 -0
- package/dist/core/text.js +154 -0
- package/dist/core/types.d.ts +269 -0
- package/dist/core/types.js +2 -0
- package/dist/core/validation.d.ts +24 -0
- package/dist/core/validation.js +243 -0
- package/dist/date-picker.d.ts +60 -0
- package/dist/date-picker.js +355 -0
- package/dist/dialog.d.ts +4 -0
- package/dist/dialog.js +23 -5
- package/dist/divider.d.ts +33 -0
- package/dist/divider.js +113 -0
- package/dist/draggable.d.ts +78 -0
- package/dist/draggable.js +255 -0
- package/dist/index.d.ts +53 -1
- package/dist/index.js +78 -0
- package/dist/input.d.ts +30 -1
- package/dist/input.js +166 -54
- package/dist/internal/qrcodegen.d.ts +94 -0
- package/dist/internal/qrcodegen.js +856 -0
- package/dist/masonry.d.ts +36 -0
- package/dist/masonry.js +171 -0
- package/dist/modal.d.ts +73 -0
- package/dist/modal.js +361 -0
- package/dist/portable/icons.d.ts +5 -0
- package/dist/portable/icons.js +24 -0
- package/dist/portable/index.d.ts +7 -0
- package/dist/portable/index.js +5 -0
- package/dist/portable/page.d.ts +38 -0
- package/dist/portable/page.js +361 -0
- package/dist/portable/responsive.d.ts +20 -0
- package/dist/portable/responsive.js +15 -0
- package/dist/portable/theme.d.ts +24 -0
- package/dist/portable/theme.js +60 -0
- package/dist/portable/tree.d.ts +10 -0
- package/dist/portable/tree.js +96 -0
- package/dist/qr-code.d.ts +62 -0
- package/dist/qr-code.js +224 -0
- package/dist/range.d.ts +3 -0
- package/dist/range.js +16 -5
- package/dist/rate.d.ts +46 -0
- package/dist/rate.js +193 -0
- package/dist/resizable.d.ts +97 -0
- package/dist/resizable.js +426 -0
- package/dist/skeleton.d.ts +41 -0
- package/dist/skeleton.js +141 -0
- package/dist/steps.d.ts +45 -0
- package/dist/steps.js +203 -0
- package/dist/table.d.ts +106 -0
- package/dist/table.js +689 -0
- package/dist/tabs.d.ts +3 -0
- package/dist/tabs.js +11 -1
- package/dist/tag.d.ts +55 -0
- package/dist/tag.js +225 -0
- package/dist/time-picker.d.ts +62 -0
- package/dist/time-picker.js +298 -0
- package/dist/timeline.d.ts +48 -0
- package/dist/timeline.js +201 -0
- package/dist/toast.d.ts +55 -0
- package/dist/toast.js +224 -0
- package/dist/tour.d.ts +89 -0
- package/dist/tour.js +306 -0
- package/dist/virtual-keyboard.d.ts +95 -0
- package/dist/virtual-keyboard.js +432 -0
- package/dist/virtual-mouse.d.ts +82 -0
- package/dist/virtual-mouse.js +358 -0
- package/dist/watermark.d.ts +44 -0
- package/dist/watermark.js +148 -0
- package/package.json +132 -4
|
@@ -0,0 +1,553 @@
|
|
|
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.help = document.createElement('span');
|
|
196
|
+
entry.help.dataset.hyHelp = '';
|
|
197
|
+
element.append(entry.help);
|
|
198
|
+
entry.error = document.createElement('span');
|
|
199
|
+
entry.error.dataset.hyError = '';
|
|
200
|
+
entry.label.id = `${entry.id}-label`;
|
|
201
|
+
entry.error.id = `${entry.id}-error`;
|
|
202
|
+
element.prepend(entry.label);
|
|
203
|
+
element.append(entry.error);
|
|
204
|
+
}
|
|
205
|
+
if (node.kind === 'PlatformView') {
|
|
206
|
+
const factory = this.options.platformViews?.[node.props.slot];
|
|
207
|
+
if (!factory)
|
|
208
|
+
throw new HYUIError('capability', `No PlatformView factory for ${node.props.slot}`, { key: node.key });
|
|
209
|
+
entry.platform = new HYPlatformViewController(() => factory(element, { key: node.key, slot: node.props.slot }), node.props.resourceId, node.props.visible !== false);
|
|
210
|
+
}
|
|
211
|
+
return entry;
|
|
212
|
+
}
|
|
213
|
+
#update(entry, node, revision) {
|
|
214
|
+
hyValidateProps(node.kind, node.props, { key: node.key, ...(node.source ? { source: node.source } : {}) }, this.capabilities);
|
|
215
|
+
const previous = entry.node;
|
|
216
|
+
if (node.kind === 'PlatformView' && previous.kind === 'PlatformView' && node.props.slot !== previous.props.slot)
|
|
217
|
+
throw new HYUIError('invalid-props', 'PlatformView factory slot is immutable; use a new key', { key: node.key });
|
|
218
|
+
entry.node = node;
|
|
219
|
+
const element = entry.element;
|
|
220
|
+
if (entry.parent === undefined || !equalData(previous.props.layout, node.props.layout))
|
|
221
|
+
applyLayout(element, node.props.layout, entry.parent ? this.#nodes.get(entry.parent)?.node.kind === 'Overlay' : false);
|
|
222
|
+
flag(element, 'hidden', node.props.visible === false);
|
|
223
|
+
entry.control?.update(node, revision);
|
|
224
|
+
if (node.kind === 'Text') {
|
|
225
|
+
attribute(element, 'data-hy-text-style', node.props.textStyle ?? 'body');
|
|
226
|
+
attribute(element, 'data-hy-tone', node.props.tone ?? 'default');
|
|
227
|
+
if (element.textContent !== node.props.text)
|
|
228
|
+
element.textContent = node.props.text;
|
|
229
|
+
element.style.whiteSpace = node.props.wrap === false ? 'pre' : 'pre-wrap';
|
|
230
|
+
}
|
|
231
|
+
if (node.kind === 'Scroll') {
|
|
232
|
+
element.style.overflowX = node.props.axis === 'horizontal' ? 'auto' : 'hidden';
|
|
233
|
+
element.style.overflowY = node.props.axis === 'vertical' ? 'auto' : 'hidden';
|
|
234
|
+
if (node.props.layout?.clip)
|
|
235
|
+
element.style.overflow = 'hidden';
|
|
236
|
+
element.tabIndex = 0;
|
|
237
|
+
}
|
|
238
|
+
if (node.kind === 'Image' || node.kind === 'Icon') {
|
|
239
|
+
const image = element;
|
|
240
|
+
if (!image.hasAttribute('src') || !('resource' in previous.props) || !equalData(node.props.resource, previous.props.resource)) {
|
|
241
|
+
const url = this.options.resolveResource?.(node.props.resource, { key: node.key, kind: node.kind });
|
|
242
|
+
if (typeof url !== 'string' || !url.trim())
|
|
243
|
+
throw new HYUIError('capability', `Unresolved image resource ${node.props.resource.id}`, { key: node.key });
|
|
244
|
+
image.src = url;
|
|
245
|
+
}
|
|
246
|
+
image.alt = node.props.resource.alt;
|
|
247
|
+
image.style.objectFit = node.kind === 'Image' ? (node.props.fit ?? 'contain') : 'contain';
|
|
248
|
+
}
|
|
249
|
+
if (node.kind === 'FormGroup') {
|
|
250
|
+
entry.label.textContent = node.props.label;
|
|
251
|
+
entry.help.textContent = node.props.description ?? '';
|
|
252
|
+
entry.help.hidden = !node.props.description;
|
|
253
|
+
entry.error.textContent = node.props.error ?? '';
|
|
254
|
+
entry.error.hidden = !node.props.error;
|
|
255
|
+
}
|
|
256
|
+
if (node.kind === 'PlatformView')
|
|
257
|
+
entry.platform.update(node.props.resourceId);
|
|
258
|
+
if (node.kind === 'Tabs')
|
|
259
|
+
for (const child of this.#nodes.values())
|
|
260
|
+
if (child.parent === node.key)
|
|
261
|
+
this.#container(entry, child.slot);
|
|
262
|
+
}
|
|
263
|
+
#container(parent, slot) {
|
|
264
|
+
if (parent.node.kind === 'Tabs') {
|
|
265
|
+
if (slot === undefined || !parent.node.props.options.some(option => option.value === slot))
|
|
266
|
+
throw new HYUIError('capability', 'Tabs children need an option-valued named slot', { key: parent.node.key });
|
|
267
|
+
return parent.element;
|
|
268
|
+
}
|
|
269
|
+
if (parent.node.kind === 'Dialog') {
|
|
270
|
+
if (slot !== undefined && slot !== 'footer')
|
|
271
|
+
throw new HYUIError('capability', 'Dialog supports only the footer named slot', { key: parent.node.key });
|
|
272
|
+
return parent.element;
|
|
273
|
+
}
|
|
274
|
+
if (!['Row', 'Column', 'Grid', 'Overlay', 'Scroll', 'FormGroup', 'Button'].includes(parent.node.kind))
|
|
275
|
+
throw new HYUIError('capability', 'This component cannot contain children', { key: parent.node.key });
|
|
276
|
+
if (slot === undefined)
|
|
277
|
+
return parent.element;
|
|
278
|
+
if (parent.node.kind === 'Button')
|
|
279
|
+
throw new HYUIError('capability', 'Button supports only default content', { key: parent.node.key });
|
|
280
|
+
let group = parent.slots.get(slot);
|
|
281
|
+
if (!group) {
|
|
282
|
+
group = document.createElement('div');
|
|
283
|
+
group.dataset.hySlotGroup = slot;
|
|
284
|
+
parent.element.append(group);
|
|
285
|
+
parent.slots.set(slot, group);
|
|
286
|
+
}
|
|
287
|
+
return group;
|
|
288
|
+
}
|
|
289
|
+
#shown(entry) {
|
|
290
|
+
let current = entry;
|
|
291
|
+
while (current) {
|
|
292
|
+
if (current.node.props.visible === false || (current.node.kind === 'Dialog' && !current.node.props.open))
|
|
293
|
+
return false;
|
|
294
|
+
const parent = current.parent ? this.#nodes.get(current.parent) : undefined;
|
|
295
|
+
if (parent?.node.kind === 'Tabs' && current.slot !== parent.node.props.value)
|
|
296
|
+
return false;
|
|
297
|
+
current = parent;
|
|
298
|
+
}
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
#interactive(entry) {
|
|
302
|
+
if (this.#disposed || this.#suspended || this.#applying || this.#failure || !this.#nodes.has(entry.node.key) || !this.#shown(entry))
|
|
303
|
+
return false;
|
|
304
|
+
let current = entry;
|
|
305
|
+
while (current) {
|
|
306
|
+
const props = current.node.props;
|
|
307
|
+
if (('disabled' in props && props.disabled) || ('busy' in props && props.busy))
|
|
308
|
+
return false;
|
|
309
|
+
current = current.parent ? this.#nodes.get(current.parent) : undefined;
|
|
310
|
+
}
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
#event(entry, event) { if (this.#interactive(entry))
|
|
314
|
+
this.#emit?.(event); }
|
|
315
|
+
#syncRelations(entries = this.#nodes.values()) {
|
|
316
|
+
for (const entry of entries) {
|
|
317
|
+
if (entry.element instanceof HYDialog)
|
|
318
|
+
entry.element.open = !this.#suspended && this.#shown(entry);
|
|
319
|
+
this.#a11y(entry);
|
|
320
|
+
for (const [name, container] of entry.slots)
|
|
321
|
+
if (!container.childElementCount) {
|
|
322
|
+
container.remove();
|
|
323
|
+
entry.slots.delete(name);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
#a11y(entry) {
|
|
328
|
+
const { element, node } = entry;
|
|
329
|
+
let group = entry.parent ? this.#nodes.get(entry.parent) : undefined;
|
|
330
|
+
while (group && group.node.kind !== 'FormGroup')
|
|
331
|
+
group = group.parent ? this.#nodes.get(group.parent) : undefined;
|
|
332
|
+
const groupProps = group?.node.kind === 'FormGroup' ? group.node.props : undefined;
|
|
333
|
+
const label = node.props.a11y?.label ?? (entry.control || node.kind === 'TextArea' ? groupProps?.label : undefined);
|
|
334
|
+
const description = [node.props.a11y?.description, groupProps?.description, node.props.a11y?.error ?? groupProps?.error].filter(Boolean).join('. ');
|
|
335
|
+
const a11y = label === undefined ? undefined : { ...node.props.a11y, label, ...(groupProps?.error && !node.props.a11y?.error ? { error: groupProps.error } : {}) };
|
|
336
|
+
applyAccessibility(element, a11y);
|
|
337
|
+
const targets = element instanceof HYTabs ? [...element.shadowRoot.querySelectorAll('[role=tablist],button')]
|
|
338
|
+
: element.shadowRoot ? [element.shadowRoot.querySelector(node.kind === 'Slider' ? 'button.upper' : 'input:not([type=search]),select,button,dialog')].filter((el) => !!el) : [element];
|
|
339
|
+
for (const target of targets) {
|
|
340
|
+
// Tabs label belongs to the tablist; each tab keeps its option name.
|
|
341
|
+
if (!(element instanceof HYTabs) || target.getAttribute('role') === 'tablist')
|
|
342
|
+
applyAccessibility(target, a11y);
|
|
343
|
+
attribute(target, 'aria-description', description || undefined);
|
|
344
|
+
attribute(target, 'aria-invalid', (node.props.a11y?.error ?? groupProps?.error) ? 'true' : undefined);
|
|
345
|
+
if (description) {
|
|
346
|
+
const scope = target.getRootNode();
|
|
347
|
+
// References stay in the same tree scope, including inside legacy ShadowRoots.
|
|
348
|
+
if (scope instanceof ShadowRoot) {
|
|
349
|
+
let info = scope.querySelector('[data-hy-a11y-description]');
|
|
350
|
+
if (!info) {
|
|
351
|
+
info = document.createElement('span');
|
|
352
|
+
info.dataset.hyA11yDescription = '';
|
|
353
|
+
info.id = `${entry.id}-description`;
|
|
354
|
+
info.hidden = true;
|
|
355
|
+
scope.append(info);
|
|
356
|
+
}
|
|
357
|
+
if (info.textContent !== description)
|
|
358
|
+
info.textContent = description;
|
|
359
|
+
attribute(target, 'aria-describedby', info.id);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else
|
|
363
|
+
attribute(target, 'aria-describedby', undefined);
|
|
364
|
+
}
|
|
365
|
+
if (!entry.control && node.kind !== 'TextArea') {
|
|
366
|
+
const role = node.props.a11y?.role;
|
|
367
|
+
attribute(element, 'role', role === 'text' ? undefined : role === 'image' ? 'img' : role);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
#syncResources() {
|
|
371
|
+
for (const entry of this.#nodes.values())
|
|
372
|
+
if (entry.platform && entry.element.isConnected) {
|
|
373
|
+
try {
|
|
374
|
+
entry.platform.visible(this.#shown(entry));
|
|
375
|
+
if (!entry.platformMounted) {
|
|
376
|
+
if (this.#suspended)
|
|
377
|
+
entry.platform.suspend();
|
|
378
|
+
entry.platform.mount();
|
|
379
|
+
entry.platformMounted = true;
|
|
380
|
+
this.#resize?.observe(entry.element);
|
|
381
|
+
}
|
|
382
|
+
if (this.#suspended)
|
|
383
|
+
entry.platform.suspend();
|
|
384
|
+
else {
|
|
385
|
+
entry.platform.resume();
|
|
386
|
+
const rect = entry.element.getBoundingClientRect();
|
|
387
|
+
this.#counts.measurements++;
|
|
388
|
+
entry.platform.layout({ x: rect.x, y: rect.y, width: rect.width, height: rect.height, scale: window.devicePixelRatio || 1 });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (cause) {
|
|
392
|
+
throw new HYUIError('renderer', 'PlatformView lifecycle failed', { key: entry.node.key, ...(entry.node.source ? { source: entry.node.source } : {}) }, cause);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
#scheduleLayout() {
|
|
397
|
+
if (this.#frame !== undefined || this.#disposed || this.#suspended || this.#failure)
|
|
398
|
+
return;
|
|
399
|
+
this.#frame = requestAnimationFrame(() => {
|
|
400
|
+
this.#frame = undefined;
|
|
401
|
+
try {
|
|
402
|
+
this.#syncResources();
|
|
403
|
+
}
|
|
404
|
+
catch (cause) {
|
|
405
|
+
const error = new HYUIError('renderer', 'Web resource layout failed', { operation: 'layout', revision: this.#revision, ...(cause instanceof HYUIError ? cause.context : {}) }, cause);
|
|
406
|
+
this.#failure = error;
|
|
407
|
+
if (this.options.onError)
|
|
408
|
+
this.options.onError(error);
|
|
409
|
+
else
|
|
410
|
+
queueMicrotask(() => { throw error; });
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
#trapTab(event) {
|
|
415
|
+
if (event.key !== 'Tab' || event.defaultPrevented || this.#suspended)
|
|
416
|
+
return;
|
|
417
|
+
const dialog = [...this.#nodes.values()].filter(entry => entry.element instanceof HYDialog && entry.element.open).at(-1);
|
|
418
|
+
if (!dialog)
|
|
419
|
+
return;
|
|
420
|
+
const choices = focusable(dialog.element.shadowRoot), active = deepActive(document);
|
|
421
|
+
const index = choices.findIndex(choice => choice === active);
|
|
422
|
+
if (!choices.length) {
|
|
423
|
+
event.preventDefault();
|
|
424
|
+
dialog.control?.focus();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if ((event.shiftKey && index <= 0) || (!event.shiftKey && (index === choices.length - 1 || index === -1))) {
|
|
428
|
+
event.preventDefault();
|
|
429
|
+
choices[event.shiftKey ? choices.length - 1 : 0].focus();
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
focus(key) {
|
|
433
|
+
this.#assertReady();
|
|
434
|
+
const entry = this.#get(key);
|
|
435
|
+
if (this.#suspended || !this.#shown(entry))
|
|
436
|
+
return;
|
|
437
|
+
if (entry.platform) {
|
|
438
|
+
entry.platform.focus();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (entry.control) {
|
|
442
|
+
entry.control.focus();
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (entry.element.tabIndex < 0)
|
|
446
|
+
entry.element.tabIndex = -1;
|
|
447
|
+
entry.element.focus({ preventScroll: true });
|
|
448
|
+
}
|
|
449
|
+
setEnvironment(environment) {
|
|
450
|
+
if (this.#disposed)
|
|
451
|
+
throw new HYUIError('lifecycle', 'Web renderer disposed');
|
|
452
|
+
const { safeArea = [0, 0, 0, 0], fontScale = 1, direction = 'ltr' } = environment;
|
|
453
|
+
if (!Number.isFinite(fontScale) || fontScale <= 0 || !['ltr', 'rtl'].includes(direction)
|
|
454
|
+
|| (safeArea !== 'environment' && (!Array.isArray(safeArea) || safeArea.length !== 4 || !safeArea.every(v => Number.isFinite(v) && v >= 0))))
|
|
455
|
+
throw new HYUIError('invalid-props', 'Invalid Web environment');
|
|
456
|
+
const theme = environment.theme ? hyThemeVariables(environment.theme, fontScale) : undefined;
|
|
457
|
+
for (const key of this.#themeKeys)
|
|
458
|
+
this.#root.style.removeProperty(key);
|
|
459
|
+
this.#themeKeys = [];
|
|
460
|
+
if (theme) {
|
|
461
|
+
for (const [key, value] of Object.entries(theme)) {
|
|
462
|
+
this.#root.style.setProperty(key, value);
|
|
463
|
+
this.#themeKeys.push(key);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
else if (environment.fontScale !== undefined) {
|
|
467
|
+
const inherited = getComputedStyle(this.host);
|
|
468
|
+
for (const [key, fallback] of [['--hy-font-size', 14], ['--hy-line-height', 22]]) {
|
|
469
|
+
const base = parseFloat(inherited.getPropertyValue(key)) || fallback;
|
|
470
|
+
this.#root.style.setProperty(key, `${base * fontScale}px`);
|
|
471
|
+
this.#themeKeys.push(key);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
this.#root.dir = direction;
|
|
475
|
+
flag(this.#root, 'data-hy-reduced-motion', !!environment.reducedMotion);
|
|
476
|
+
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(' ');
|
|
477
|
+
this.#scheduleLayout();
|
|
478
|
+
}
|
|
479
|
+
suspend() { this.#assertReady(); if (this.#suspended)
|
|
480
|
+
return; this.#suspended = true; this.#root.inert = true; this.#syncRelations(); for (const entry of this.#nodes.values())
|
|
481
|
+
entry.platform?.suspend(); }
|
|
482
|
+
resume() { this.#assertReady(); if (!this.#suspended)
|
|
483
|
+
return; this.#suspended = false; this.#root.inert = false; this.#applying = true; try {
|
|
484
|
+
this.#syncRelations();
|
|
485
|
+
this.#syncResources();
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
this.#applying = false;
|
|
489
|
+
} }
|
|
490
|
+
#listen(cleanup, target, type, listener, capture = false) {
|
|
491
|
+
target.addEventListener(type, listener, capture);
|
|
492
|
+
this.#counts.activeListeners++;
|
|
493
|
+
let live = true;
|
|
494
|
+
const off = () => { if (!live)
|
|
495
|
+
return; live = false; target.removeEventListener(type, listener, capture); cleanup.delete(off); this.#counts.activeListeners--; };
|
|
496
|
+
cleanup.add(off);
|
|
497
|
+
return off;
|
|
498
|
+
}
|
|
499
|
+
#remove(entry) {
|
|
500
|
+
const errors = [];
|
|
501
|
+
for (const child of [...this.#nodes.values()])
|
|
502
|
+
if (child.parent === entry.node.key)
|
|
503
|
+
try {
|
|
504
|
+
this.#remove(child);
|
|
505
|
+
}
|
|
506
|
+
catch (cause) {
|
|
507
|
+
errors.push(cause);
|
|
508
|
+
}
|
|
509
|
+
for (const off of entry.cleanup)
|
|
510
|
+
off();
|
|
511
|
+
this.#resize?.unobserve(entry.element);
|
|
512
|
+
try {
|
|
513
|
+
entry.control?.dispose();
|
|
514
|
+
}
|
|
515
|
+
catch (cause) {
|
|
516
|
+
errors.push(cause);
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
entry.platform?.dispose();
|
|
520
|
+
}
|
|
521
|
+
catch (cause) {
|
|
522
|
+
errors.push(cause);
|
|
523
|
+
}
|
|
524
|
+
entry.element.remove();
|
|
525
|
+
this.#nodes.delete(entry.node.key);
|
|
526
|
+
this.#counts.removed++;
|
|
527
|
+
if (errors.length)
|
|
528
|
+
throw new AggregateError(errors, 'Web node cleanup failed');
|
|
529
|
+
}
|
|
530
|
+
dispose() {
|
|
531
|
+
if (this.#disposed)
|
|
532
|
+
return;
|
|
533
|
+
this.#disposed = true;
|
|
534
|
+
if (this.#frame !== undefined)
|
|
535
|
+
cancelAnimationFrame(this.#frame);
|
|
536
|
+
this.#resize?.disconnect();
|
|
537
|
+
const errors = [];
|
|
538
|
+
while (this.#nodes.size)
|
|
539
|
+
try {
|
|
540
|
+
this.#remove(this.#nodes.values().next().value);
|
|
541
|
+
}
|
|
542
|
+
catch (cause) {
|
|
543
|
+
errors.push(cause);
|
|
544
|
+
}
|
|
545
|
+
for (const off of this.#cleanup)
|
|
546
|
+
off();
|
|
547
|
+
this.#root.remove();
|
|
548
|
+
this.#emit = undefined;
|
|
549
|
+
if (errors.length)
|
|
550
|
+
throw new AggregateError(errors, 'Web renderer cleanup failed');
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
//# 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-help] { color:var(--hy-secondary-text-color); font-size:var(--hy-caption-font-size,12px); line-height:var(--hy-caption-line-height,18px); }\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-text-style=title] { font-size:var(--hy-title-font-size,22px); line-height:var(--hy-title-line-height,30px); font-weight:var(--hy-title-font-weight,700); }\n[data-hy-portable-root] [data-hy-text-style=caption] { font-size:var(--hy-caption-font-size,12px); line-height:var(--hy-caption-line-height,18px); font-weight:var(--hy-caption-font-weight,400); }\n[data-hy-portable-root] [data-hy-text-style=body] { font-size:var(--hy-body-font-size,var(--hy-font-size,14px)); line-height:var(--hy-body-line-height,var(--hy-line-height,22px)); font-weight:var(--hy-body-font-weight,400); }\n[data-hy-portable-root] [data-hy-tone=muted] { color:var(--hy-secondary-text-color); }\n[data-hy-portable-root] [data-hy-tone=danger] { color:var(--hy-invalid-border-color); }\n[data-hy-portable-root] [data-hy-kind=Button] [slot=icon] svg { display:block; width:20px; height:20px; }\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; --hy-input-radius:var(--hy-radius,8px); font:inherit;\n --hy-panel-bg-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);\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; }\n:host(hy-input) input { height:auto; align-self:stretch; padding-block:0; border-radius:0; outline:none; }\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\n/* Portable variants leave individual Web Component APIs unchanged. */\n:host(hy-button) button { border:1px solid var(--hy-border-color); background:var(--hy-surface-color); color:var(--hy-text-color); border-radius:var(--hy-radius,8px); transition:background .12s ease; }\n:host(hy-button[data-hy-variant=primary]) button { background:var(--hy-accent-color); border-color:transparent; color:var(--hy-on-accent-color,#fff); }\n:host(hy-button[data-hy-variant=danger]) button { background:var(--hy-invalid-border-color); border-color:transparent; color:var(--hy-on-accent-color,#fff); }\n:host(hy-button[data-hy-variant=quiet]) button { background:transparent; border-color:transparent; }\n:host(hy-button) button:hover:not(:disabled), :host(hy-button) button:active:not(:disabled) { background:var(--hy-pressed-bg-color,var(--hy-selected-bg-color)); }\n:host(hy-button) button:disabled { opacity:.45; }\n:host(hy-button[data-hy-size=compact]) button { padding-inline:8px; }\n:host(hy-button[icon-only]) button { padding:0; width:100%; }\n:host(hy-tabs) button { min-height:44px; padding:8px 14px; border:0; border-radius:0; background:transparent; color:var(--hy-secondary-text-color); border-bottom:2px solid transparent; }\n:host(hy-tabs) button[aria-selected=true] { color:var(--hy-text-color); border-bottom-color:var(--hy-accent-color); }\n:host(hy-tabs[data-hy-variant=segmented]) .tabs { padding:4px; gap:4px; border-radius:var(--hy-radius,8px); background:var(--hy-input-bg-color); }\n:host(hy-tabs[data-hy-variant=segmented]) button { border:0; border-radius:var(--hy-radius,8px); }\n:host(hy-tabs[data-hy-variant=segmented]) button[aria-selected=true] { background:var(--hy-selected-bg-color,var(--hy-accent-color)); }\nbutton:focus-visible, select: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,83 @@
|
|
|
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-help] { color:var(--hy-secondary-text-color); font-size:var(--hy-caption-font-size,12px); line-height:var(--hy-caption-line-height,18px); }
|
|
18
|
+
[data-hy-portable-root] [data-hy-error] { color:var(--hy-invalid-border-color,#d65c7c); font-size:.9em; }
|
|
19
|
+
[data-hy-portable-root] textarea { box-sizing:border-box; width:100%; min-height:5em; resize:vertical; padding:8px;
|
|
20
|
+
color:var(--hy-text-color,#d8e2f2); background:var(--hy-input-bg-color,#121822); border:1px solid var(--hy-border-color,#303746);
|
|
21
|
+
border-radius:var(--hy-radius,4px); font:inherit; }
|
|
22
|
+
[data-hy-portable-root] textarea:focus-visible { outline:2px solid var(--hy-focus-ring-color,#8177f2); outline-offset:1px; }
|
|
23
|
+
[data-hy-portable-root] [data-hy-text-style=title] { font-size:var(--hy-title-font-size,22px); line-height:var(--hy-title-line-height,30px); font-weight:var(--hy-title-font-weight,700); }
|
|
24
|
+
[data-hy-portable-root] [data-hy-text-style=caption] { font-size:var(--hy-caption-font-size,12px); line-height:var(--hy-caption-line-height,18px); font-weight:var(--hy-caption-font-weight,400); }
|
|
25
|
+
[data-hy-portable-root] [data-hy-text-style=body] { font-size:var(--hy-body-font-size,var(--hy-font-size,14px)); line-height:var(--hy-body-line-height,var(--hy-line-height,22px)); font-weight:var(--hy-body-font-weight,400); }
|
|
26
|
+
[data-hy-portable-root] [data-hy-tone=muted] { color:var(--hy-secondary-text-color); }
|
|
27
|
+
[data-hy-portable-root] [data-hy-tone=danger] { color:var(--hy-invalid-border-color); }
|
|
28
|
+
[data-hy-portable-root] [data-hy-kind=Button] [slot=icon] svg { display:block; width:20px; height:20px; }
|
|
29
|
+
[data-hy-portable-root] [data-hy-slot-group] { display:contents; }
|
|
30
|
+
[data-hy-portable-root][data-hy-reduced-motion] { --hy-portable-transition:none; }
|
|
31
|
+
@media(prefers-reduced-motion:reduce) { [data-hy-portable-root] { --hy-portable-transition:none; } }
|
|
32
|
+
`;
|
|
33
|
+
export const componentCSS = `
|
|
34
|
+
:host { --hy-input-font:var(--hy-font-size,14px)/var(--hy-line-height,22px) system-ui,sans-serif; --hy-input-radius:var(--hy-radius,8px); font:inherit;
|
|
35
|
+
--hy-panel-bg-color:var(--hy-surface-color,#171d28);
|
|
36
|
+
--hy-menu-bg-color:var(--hy-surface-color,#171d28); --hy-dialog-bg-color:var(--hy-surface-color,#171d28);
|
|
37
|
+
--hy-dialog-body-color:var(--hy-text-color,#d8e2f2); --hy-dialog-footer-bg-color:var(--hy-surface-color,#171d28);
|
|
38
|
+
--hy-accent-strong-color:var(--hy-accent-color,#8177f2);
|
|
39
|
+
--hy-active-text-color:var(--hy-text-color,#d8e2f2); --hy-hover-bg-color:var(--hy-input-bg-color,#121822);
|
|
40
|
+
--hy-focus-border-color:var(--hy-focus-ring-color,#8177f2); --hy-dialog-radius:var(--hy-radius,8px); }
|
|
41
|
+
button, input, select { font:inherit; min-height:2em; border-radius:var(--hy-radius,4px); }
|
|
42
|
+
/* The semantic host owns the touch target. Its actual interactive element
|
|
43
|
+
must fill it too; otherwise a 44px portable Button still paints at 28px. */
|
|
44
|
+
:host(hy-button), :host(hy-input), :host(hy-select) { display:inline-flex; }
|
|
45
|
+
:host(hy-button) button, :host(hy-input) input, :host(hy-select) select {
|
|
46
|
+
box-sizing:border-box; width:100%; min-height:inherit; height:100%; padding-inline:12px;
|
|
47
|
+
}
|
|
48
|
+
:host(hy-select) .wrap, :host(hy-input) .control { width:100%; min-height:inherit; height:100%; box-sizing:border-box; }
|
|
49
|
+
:host(hy-input) input { min-height:0; }
|
|
50
|
+
select { height:auto; padding-block:4px; }
|
|
51
|
+
:host(hy-input) input { height:auto; align-self:stretch; padding-block:0; border-radius:0; outline:none; }
|
|
52
|
+
.box, .box::after, .handle { transition:var(--hy-portable-transition,background .12s ease); }
|
|
53
|
+
:host(hy-checkbox) { width:auto; }
|
|
54
|
+
:host(hy-checkbox) label, :host(hy-checkbox) .label { white-space:normal; }
|
|
55
|
+
:host(hy-checkbox) input { min-height:0; }
|
|
56
|
+
:host(hy-tabs) { height:auto; grid-template-rows:auto minmax(0,1fr); }
|
|
57
|
+
:host(hy-tabs) .tabs { align-items:stretch; }
|
|
58
|
+
:host(hy-tabs) button { height:auto; white-space:normal; }
|
|
59
|
+
:host(hy-range) .handle { min-height:0; }
|
|
60
|
+
:host(hy-dialog) .header { padding:16px 20px; }
|
|
61
|
+
:host(hy-dialog) h2 { font-size:calc(var(--hy-font-size,14px) + 6px); }
|
|
62
|
+
:host(hy-dialog) .body { display:flex; flex-direction:column; gap:var(--hy-gap,8px); padding:20px; }
|
|
63
|
+
:host(hy-dialog) .body > slot { display:contents; }
|
|
64
|
+
:host(hy-dialog) .footer { box-sizing:border-box; min-height:0; padding:12px 20px; }
|
|
65
|
+
|
|
66
|
+
/* Portable variants leave individual Web Component APIs unchanged. */
|
|
67
|
+
:host(hy-button) button { border:1px solid var(--hy-border-color); background:var(--hy-surface-color); color:var(--hy-text-color); border-radius:var(--hy-radius,8px); transition:background .12s ease; }
|
|
68
|
+
:host(hy-button[data-hy-variant=primary]) button { background:var(--hy-accent-color); border-color:transparent; color:var(--hy-on-accent-color,#fff); }
|
|
69
|
+
:host(hy-button[data-hy-variant=danger]) button { background:var(--hy-invalid-border-color); border-color:transparent; color:var(--hy-on-accent-color,#fff); }
|
|
70
|
+
:host(hy-button[data-hy-variant=quiet]) button { background:transparent; border-color:transparent; }
|
|
71
|
+
:host(hy-button) button:hover:not(:disabled), :host(hy-button) button:active:not(:disabled) { background:var(--hy-pressed-bg-color,var(--hy-selected-bg-color)); }
|
|
72
|
+
:host(hy-button) button:disabled { opacity:.45; }
|
|
73
|
+
:host(hy-button[data-hy-size=compact]) button { padding-inline:8px; }
|
|
74
|
+
:host(hy-button[icon-only]) button { padding:0; width:100%; }
|
|
75
|
+
:host(hy-tabs) button { min-height:44px; padding:8px 14px; border:0; border-radius:0; background:transparent; color:var(--hy-secondary-text-color); border-bottom:2px solid transparent; }
|
|
76
|
+
:host(hy-tabs) button[aria-selected=true] { color:var(--hy-text-color); border-bottom-color:var(--hy-accent-color); }
|
|
77
|
+
:host(hy-tabs[data-hy-variant=segmented]) .tabs { padding:4px; gap:4px; border-radius:var(--hy-radius,8px); background:var(--hy-input-bg-color); }
|
|
78
|
+
:host(hy-tabs[data-hy-variant=segmented]) button { border:0; border-radius:var(--hy-radius,8px); }
|
|
79
|
+
:host(hy-tabs[data-hy-variant=segmented]) button[aria-selected=true] { background:var(--hy-selected-bg-color,var(--hy-accent-color)); }
|
|
80
|
+
button:focus-visible, select:focus-visible { outline:2px solid var(--hy-focus-ring-color,#8177f2); outline-offset:1px; }
|
|
81
|
+
[aria-invalid=true] { border-color:var(--hy-invalid-border-color,#d65c7c); }
|
|
82
|
+
`;
|
|
83
|
+
//# sourceMappingURL=styles.js.map
|