@haiyue/ui 0.1.0 → 0.1.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 +29 -0
- package/LICENSE +1 -1
- package/README.md +226 -2
- package/dist/border-beam.d.ts +26 -0
- package/dist/border-beam.js +174 -0
- package/dist/button.d.ts +10 -0
- package/dist/button.js +85 -0
- package/dist/checkbox.d.ts +2 -2
- package/dist/checkbox.js +11 -11
- package/dist/context-menu.d.ts +6 -6
- package/dist/context-menu.js +11 -9
- package/dist/dialog.d.ts +5 -5
- package/dist/dialog.js +18 -18
- package/dist/drawer.d.ts +49 -0
- package/dist/drawer.js +377 -0
- package/dist/dropdown.d.ts +9 -9
- package/dist/dropdown.js +9 -9
- package/dist/expandable.d.ts +32 -0
- package/dist/expandable.js +136 -0
- package/dist/history-controls.d.ts +40 -0
- package/dist/history-controls.js +123 -0
- package/dist/index.d.ts +36 -31
- package/dist/index.js +35 -85
- package/dist/input.d.ts +5 -5
- package/dist/input.js +13 -13
- package/dist/menu-shared.d.ts +4 -4
- package/dist/menu-shared.js +4 -4
- package/dist/notification.d.ts +55 -0
- package/dist/notification.js +357 -0
- package/dist/radio.d.ts +2 -2
- package/dist/radio.js +12 -12
- package/dist/range.d.ts +74 -0
- package/dist/range.js +393 -0
- package/dist/select.d.ts +6 -6
- package/dist/select.js +20 -20
- package/dist/split.d.ts +6 -6
- package/dist/split.js +14 -14
- package/dist/tabs.d.ts +10 -6
- package/dist/tabs.js +94 -30
- package/dist/tooltip.d.ts +4 -4
- package/dist/tooltip.js +9 -9
- package/dist/tree-model.d.ts +15 -7
- package/dist/tree-model.js +93 -80
- package/dist/tree-node.d.ts +3 -3
- package/dist/tree-node.js +3 -3
- package/dist/tree-types.d.ts +14 -14
- package/dist/tree.d.ts +9 -7
- package/dist/tree.js +68 -66
- package/dist/virtual-list.d.ts +61 -0
- package/dist/virtual-list.js +324 -0
- package/package.json +100 -7
- package/themes/haiyue-dark.css +46 -0
- package/themes/haiyue-light.css +46 -0
- package/src/checkbox.ts +0 -215
- package/src/context-menu.ts +0 -248
- package/src/dialog.ts +0 -195
- package/src/dropdown.ts +0 -249
- package/src/index.ts +0 -192
- package/src/input.ts +0 -150
- package/src/menu-shared.ts +0 -65
- package/src/radio.ts +0 -216
- package/src/select.ts +0 -271
- package/src/split.ts +0 -306
- package/src/tabs.ts +0 -182
- package/src/tooltip.ts +0 -321
- package/src/tree-model.ts +0 -305
- package/src/tree-node.ts +0 -67
- package/src/tree-types.ts +0 -47
- package/src/tree.ts +0 -1002
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.1.2 - 2026-09-03
|
|
6
|
+
|
|
7
|
+
- 修复 `<hy-virtual-list>` 快速滚动时 light DOM slot 异步分发旧行的问题;虚拟窗口现在完全在 Shadow DOM 内同步替换,避免上一范围的条目再次闪现。
|
|
8
|
+
- 新增 `HYExpandable` / `<hy-expandable>`:保持内容挂载的非模态展开容器,提供独立子入口、尺寸与按钮位置配置、自定义图标、事件和主题变量。
|
|
9
|
+
- 修复展开容器位于嵌套 Shadow DOM 时 Escape 无法恢复的问题;内部菜单可优先消费 Escape,嵌套展开容器每次只关闭内层。
|
|
10
|
+
- Tabs 支持横向溢出滚动,并保持标签文字不换行。
|
|
11
|
+
- 补充展开容器示例和浏览器交互回归,覆盖键盘、焦点、事件、内容保留及断开重连。
|
|
12
|
+
- API 基线、包体积与消费者验证由 UI 仓库独立维护,不再由 Engine 发布门禁管理。
|
|
13
|
+
|
|
14
|
+
## 0.1.1 - 2026-08-22
|
|
15
|
+
|
|
16
|
+
- 新增可按需导入的 `HYRange` / `<hy-range>` 数值滑块,支持单值和双拖拽点区间、步进与键盘操作、实时/提交事件,以及拖拽点和轨道样式定制。
|
|
17
|
+
- 新增可按需导入的 `HYBorderBeam` / `<hy-border-beam>` 响应式边框流光组件,支持粗细、速度倍率、颜色和多流光错相配置。
|
|
18
|
+
- 新增可按需导入的 `HYNotification` / `<hy-notification>` 通知中心,提供 success/info/warning/error 语义配色和图标、六向 placement、自动关闭、Z 轴堆叠及可选倒计时进度条。
|
|
19
|
+
- 新增可按需导入的 `HYDrawer` / `<hy-drawer>` 抽屉组件,支持四向 placement、可选蒙层、Esc/蒙层关闭、焦点管理及 `destroyOnHidden` 内容卸载。
|
|
20
|
+
- 新增可按需导入的 `HYVirtualList` / `<hy-virtual-list>` 固定行高虚拟列表,仅渲染可视区与 overscan 缓冲项,并提供内部滚动条、范围事件、点击事件和 `scrollToIndex()`。
|
|
21
|
+
- 组件品牌前缀从 `GE/ge` 统一迁移为 `HY/hy`,不保留旧标签、类名或 CSS token alias;新增可独立导入的 Moonlight 淡蓝亮色和 Nightfall 深蓝紫暗色皮肤。
|
|
22
|
+
- 独立 UI 仓库移除重复的 `ui/` workspace 层级,并为全部组件新增 `@haiyue/ui/<component>` 独立、无副作用的公开入口;根入口继续提供显式全量注册。
|
|
23
|
+
|
|
24
|
+
## 0.1.0 - 2026-07-13
|
|
25
|
+
|
|
26
|
+
- 建立海月 3D 优先的稳定包边界、生命周期、编辑器工作流、可观测性、资产管线和脚本 runtime。
|
|
27
|
+
- 用 `simple` / `batched` / `gpu-driven` / `diagnostic` RenderProfile 替换公开渲染布尔开关,并加入显式设备降级报告。
|
|
28
|
+
- 加入原生 glTF metallic-roughness PBR、directional shadow map、environment IBL、material variants 和可扩展材质 shader contract。
|
|
29
|
+
- examples/games 改为 manifest 驱动,统一 build policy、能力覆盖矩阵、真实 WebGPU 像素基线和 release gate。
|
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 Haiyue Engine contributors
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,9 +1,233 @@
|
|
|
1
1
|
# @haiyue/ui
|
|
2
2
|
|
|
3
|
-
Dependency-free Web Components
|
|
3
|
+
Dependency-free Web Components for HaiyueStudio products. Components own presentation and local interaction;
|
|
4
|
+
they do not depend on Engine, Editor, Games, or AIStudio state.
|
|
5
|
+
|
|
6
|
+
**Online component gallery:** <https://haiyuestudio.github.io/UI/>
|
|
7
|
+
|
|
8
|
+
Public classes use the `HY` prefix and custom-element tags use `hy-`:
|
|
9
|
+
|
|
10
|
+
```html
|
|
11
|
+
<hy-button label="Publish"></hy-button>
|
|
12
|
+
<hy-tree allow-drag></hy-tree>
|
|
13
|
+
```
|
|
4
14
|
|
|
5
15
|
```bash
|
|
6
16
|
npm install @haiyue/ui
|
|
7
17
|
```
|
|
8
18
|
|
|
9
|
-
|
|
19
|
+
## Import only what a product uses
|
|
20
|
+
|
|
21
|
+
Each component has an isolated package subpath. Importing a subpath loads only that component and its direct
|
|
22
|
+
helpers, and registration stays explicit:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { defineButtonComponents } from '@haiyue/ui/button';
|
|
26
|
+
import { defineTreeComponents } from '@haiyue/ui/tree';
|
|
27
|
+
import { defineVirtualListComponents } from '@haiyue/ui/virtual-list';
|
|
28
|
+
|
|
29
|
+
defineButtonComponents();
|
|
30
|
+
defineTreeComponents();
|
|
31
|
+
defineVirtualListComponents();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Component JavaScript does not register custom elements during module evaluation, and every component subpath
|
|
35
|
+
publishes matching JavaScript and type declarations. Only `themes/*.css` is marked as a package side effect so
|
|
36
|
+
bundlers retain an imported skin while excluding component JavaScript that is not reachable from selected imports.
|
|
37
|
+
|
|
38
|
+
The root entry remains available when an application intentionally wants every component:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { defineHaiyueUI } from '@haiyue/ui';
|
|
42
|
+
|
|
43
|
+
defineHaiyueUI();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Because `defineHaiyueUI()` references the complete component set, prefer focused subpaths in bundle-sensitive
|
|
47
|
+
product entry points.
|
|
48
|
+
|
|
49
|
+
## Tabs
|
|
50
|
+
|
|
51
|
+
`@haiyue/ui/tabs` retains its existing `HYTabs`, `HYTabOption`, `HYTabChangeDetail`, and
|
|
52
|
+
`defineTabsComponents` exports; the 0.1.3 fix adds no new public exports. Assign `options`
|
|
53
|
+
and `value`, provide children with matching `slot` names, and listen for `tab-change`.
|
|
54
|
+
Property-assigned options and the selected value survive reconnection. Set `aria-label`
|
|
55
|
+
to name the tablist. Arrow keys, Home and End both move focus and activate the next
|
|
56
|
+
enabled tab with roving focus; the active tab labels its panel within the component's
|
|
57
|
+
shadow tree. The generated `aria-controls` and tabpanel ids are per-instance opaque
|
|
58
|
+
values and should not be treated as stable application selectors.
|
|
59
|
+
|
|
60
|
+
The existing `@haiyue/ui/border-beam` exports are also unchanged. Its constructor only
|
|
61
|
+
builds shadow content; host appearance is applied after creation, avoiding browser
|
|
62
|
+
custom-element construction errors during repeated `document.createElement` calls.
|
|
63
|
+
|
|
64
|
+
## Virtual list
|
|
65
|
+
|
|
66
|
+
`<hy-virtual-list>` renders only the visible fixed-height rows plus a configurable overscan buffer. The full
|
|
67
|
+
collection stays in the `items` property, while `renderItem` is called only for rows that currently need DOM:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { defineVirtualListComponents } from '@haiyue/ui/virtual-list';
|
|
71
|
+
|
|
72
|
+
defineVirtualListComponents();
|
|
73
|
+
const list = document.querySelector('hy-virtual-list');
|
|
74
|
+
list.items = records;
|
|
75
|
+
list.itemHeight = 40;
|
|
76
|
+
list.overscan = 3;
|
|
77
|
+
list.renderItem = (record, index) => `${index + 1}. ${record.name}`;
|
|
78
|
+
list.scrollToIndex(50_000, 'center');
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use the `height` property (or `height` attribute) for the internal viewport. `visible-range-change` reports
|
|
82
|
+
exclusive `endIndex` / `visibleEndIndex` values, and `item-click` reports the source item and absolute index.
|
|
83
|
+
The scrollbar can be themed with inherited semantic tokens and `--hy-virtual-list-scrollbar-size`.
|
|
84
|
+
|
|
85
|
+
## Drawer
|
|
86
|
+
|
|
87
|
+
Import and register the drawer independently when a product needs an off-canvas panel:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { defineDrawerComponents } from '@haiyue/ui/drawer';
|
|
91
|
+
|
|
92
|
+
defineDrawerComponents();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`<hy-drawer>` supports `top`, `right`, `bottom`, and `left` placement. The mask defaults to enabled; set
|
|
96
|
+
`mask="false"` or `drawer.mask = false` to keep the underlying page visible and interactive. Escape, the close
|
|
97
|
+
button, and mask clicks close the drawer and emit `drawer-close` with an `action`, `mask`, `escape`, or
|
|
98
|
+
`programmatic` reason. With `destroy-on-hidden`, slotted children are unmounted after the exit transition and
|
|
99
|
+
restored when the drawer opens again, allowing nested custom elements to run their disconnect/connect cleanup.
|
|
100
|
+
|
|
101
|
+
## Notification
|
|
102
|
+
|
|
103
|
+
`<hy-notification>` is an imperative notification center with isolated package entry `@haiyue/ui/notification`:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { defineNotificationComponents } from '@haiyue/ui/notification';
|
|
107
|
+
|
|
108
|
+
defineNotificationComponents();
|
|
109
|
+
const center = document.querySelector('hy-notification');
|
|
110
|
+
center.success({
|
|
111
|
+
message: 'Export complete',
|
|
112
|
+
description: 'The project package is ready.',
|
|
113
|
+
placement: 'topRight',
|
|
114
|
+
duration: 4,
|
|
115
|
+
showProgress: true,
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Types are `success`, `info`, `warning`, and `error`. Placements are `topLeft`, `top`, `topRight`, `bottomLeft`,
|
|
120
|
+
`bottom`, and `bottomRight`. Each notice may override the host defaults for placement, duration in seconds, and
|
|
121
|
+
progress visibility; duration `0` disables automatic closing. Notices at the same placement form a perspective
|
|
122
|
+
stack and expand on hover or keyboard focus. Use `close(key)` for one notice or `destroy()` for all notices.
|
|
123
|
+
|
|
124
|
+
## Border beam
|
|
125
|
+
|
|
126
|
+
`<hy-border-beam>` wraps arbitrary content with an animated SVG beam that stays aligned to the element's live
|
|
127
|
+
rounded border. Import it from `@haiyue/ui/border-beam` and register it explicitly:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { defineBorderBeamComponents } from '@haiyue/ui/border-beam';
|
|
131
|
+
|
|
132
|
+
defineBorderBeamComponents();
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Use `thickness` for stroke width, `color` for any valid CSS color, and `count` for 1–12 evenly phased beams.
|
|
136
|
+
`speed` is a multiplier: `1` completes the default loop in six seconds, while `2` runs twice as fast. The
|
|
137
|
+
component observes its own size and keeps the SVG view box and corner radius synchronized automatically.
|
|
138
|
+
Consumers may override `--hy-border-beam-radius` and `--hy-border-beam-track-color`.
|
|
139
|
+
|
|
140
|
+
## Range
|
|
141
|
+
|
|
142
|
+
Import the slider independently and opt into two-handle interval mode with the `range` attribute:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { defineRangeComponents } from '@haiyue/ui/range';
|
|
146
|
+
|
|
147
|
+
defineRangeComponents();
|
|
148
|
+
const interval = document.querySelector('hy-range[range]');
|
|
149
|
+
interval.values = [20, 80];
|
|
150
|
+
interval.addEventListener('value-change', event => console.log(event.detail.value));
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Single-value mode uses `value`; interval mode exposes `lowerValue`, `upperValue`, and `values`. Both modes honor
|
|
154
|
+
`min`, `max`, and `step`, and support pointer dragging, track clicks, arrows, Page Up/Down, Home, and End. Use
|
|
155
|
+
`--hy-range-handle-*`, `--hy-range-rail-*`, and `--hy-range-track-*` tokens for styling, or target the exported
|
|
156
|
+
`rail`, `track`, `handle`, `handle-lower`, and `handle-upper` shadow parts. `value-input` reports live movement and
|
|
157
|
+
`value-change` reports a committed pointer or keyboard change.
|
|
158
|
+
|
|
159
|
+
## Themes
|
|
160
|
+
|
|
161
|
+
Two optional CSS theme entries are provided. Import only the skin a product needs, or import both when the
|
|
162
|
+
product offers runtime switching:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import '@haiyue/ui/themes/light.css';
|
|
166
|
+
import '@haiyue/ui/themes/dark.css';
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Apply a theme to the document or to a component subtree. Tokens inherit through component shadow roots:
|
|
170
|
+
|
|
171
|
+
```html
|
|
172
|
+
<html data-hy-theme="light">…</html>
|
|
173
|
+
<section data-hy-theme="dark">…</section>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- `light` / Haiyue Moonlight: moon white, pale ice blue, cyan and a soft violet highlight.
|
|
177
|
+
- `dark` / Haiyue Nightfall: deep navy, blue violet, cool white and a restrained gold highlight.
|
|
178
|
+
|
|
179
|
+
All theme tokens use the `--hy-` prefix and may be overridden by consuming products.
|
|
180
|
+
|
|
181
|
+
## Component examples
|
|
182
|
+
|
|
183
|
+
Browse the published gallery at <https://haiyuestudio.github.io/UI/>.
|
|
184
|
+
|
|
185
|
+
Run the interactive component gallery locally:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm run examples
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Then open <http://localhost:4173/>. The gallery provides a left-side component index and live examples for
|
|
192
|
+
every exported component, including editable parameters and event output.
|
|
193
|
+
|
|
194
|
+
## Expandable container
|
|
195
|
+
|
|
196
|
+
Explicit export: `@haiyue/ui/expandable` → `HYExpandable`, `defineExpandableComponents`,
|
|
197
|
+
`HYExpandableButtonPosition`, `HYExpandableChangeDetail`. Also included in `defineHaiyueUI()`.
|
|
198
|
+
|
|
199
|
+
```html
|
|
200
|
+
<hy-expandable expanded-width="1200px" expanded-height="90dvh"
|
|
201
|
+
button-position="top-right" expand-label="放大" restore-label="还原">
|
|
202
|
+
<div>Your graph, editor, or other stateful content</div>
|
|
203
|
+
<!-- Optional SVG elements: slot="expand-icon" / slot="restore-icon" -->
|
|
204
|
+
</hy-expandable>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`expanded` is a reflected boolean attribute/property; `toggle()` switches it.
|
|
208
|
+
`expandedWidth` / `expandedHeight` accept CSS lengths (defaults `90vw` / `90dvh`).
|
|
209
|
+
Sizes are clamped to the viewport with a margin; the normal size belongs to the host's layout/CSS.
|
|
210
|
+
`buttonPosition` supports `top-right`, `top-left`, `bottom-right`, `bottom-left`.
|
|
211
|
+
`expanded-change` bubbles with `{ expanded: boolean }`. `expand-icon` / `restore-icon` slots
|
|
212
|
+
replace the built-in SVG icons without accepting HTML strings. Labels also become tooltips.
|
|
213
|
+
Style through inherited `--hy-surface-color`, `--hy-border-color`, `--hy-text-color`,
|
|
214
|
+
`--hy-accent-color` and `--hy-expandable-{margin,padding,radius,shadow,button-size,button-offset}`;
|
|
215
|
+
`panel` and `toggle` are CSS parts.
|
|
216
|
+
|
|
217
|
+
Requires a browser supporting the Popover API. Expansion is non-modal and uses a fixed top-layer
|
|
218
|
+
panel so transformed/overflow ancestors cannot clip it. The same slotted DOM stays mounted;
|
|
219
|
+
normal-flow height is preserved while expanded. Keyboard activation uses the native button;
|
|
220
|
+
Escape restores the focused container. Disconnect closes the panel and releases event listeners.
|
|
221
|
+
|
|
222
|
+
## Independent release checks
|
|
223
|
+
|
|
224
|
+
UI releases independently of Engine.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npm ci
|
|
228
|
+
npx playwright install chromium
|
|
229
|
+
npm run release:check
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Candidates: `.artifacts/packages/`. Review API changes before `npm run api:update`.
|
|
233
|
+
See [release review](review/release-0.1.2.md).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Decorative animated border that follows the host's live rounded rectangle. */
|
|
2
|
+
export declare class HYBorderBeam extends HTMLElement {
|
|
3
|
+
private readonly _svg;
|
|
4
|
+
private readonly _track;
|
|
5
|
+
private readonly _beamLayer;
|
|
6
|
+
private _resizeObserver;
|
|
7
|
+
private _connected;
|
|
8
|
+
static get observedAttributes(): string[];
|
|
9
|
+
constructor();
|
|
10
|
+
connectedCallback(): void;
|
|
11
|
+
disconnectedCallback(): void;
|
|
12
|
+
attributeChangedCallback(name: string): void;
|
|
13
|
+
get thickness(): number;
|
|
14
|
+
set thickness(value: number);
|
|
15
|
+
/** Animation speed multiplier. 1 is normal speed and 2 is twice as fast. */
|
|
16
|
+
get speed(): number;
|
|
17
|
+
set speed(value: number);
|
|
18
|
+
get color(): string;
|
|
19
|
+
set color(value: string);
|
|
20
|
+
get count(): number;
|
|
21
|
+
set count(value: number);
|
|
22
|
+
refresh(): void;
|
|
23
|
+
private _renderBeams;
|
|
24
|
+
private _updateAppearance;
|
|
25
|
+
}
|
|
26
|
+
export declare function defineBorderBeamComponents(): void;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
|
2
|
+
const DEFAULT_THICKNESS = 2;
|
|
3
|
+
const DEFAULT_SPEED = 1;
|
|
4
|
+
const DEFAULT_COUNT = 1;
|
|
5
|
+
const BASE_DURATION_SECONDS = 6;
|
|
6
|
+
function finiteNumber(value, fallback) {
|
|
7
|
+
const number = Number(value);
|
|
8
|
+
return Number.isFinite(number) ? number : fallback;
|
|
9
|
+
}
|
|
10
|
+
/** Decorative animated border that follows the host's live rounded rectangle. */
|
|
11
|
+
export class HYBorderBeam extends HTMLElement {
|
|
12
|
+
_svg = document.createElementNS(SVG_NAMESPACE, 'svg');
|
|
13
|
+
_track = document.createElementNS(SVG_NAMESPACE, 'rect');
|
|
14
|
+
_beamLayer = document.createElementNS(SVG_NAMESPACE, 'g');
|
|
15
|
+
_resizeObserver = null;
|
|
16
|
+
_connected = false;
|
|
17
|
+
static get observedAttributes() {
|
|
18
|
+
return ['thickness', 'speed', 'color', 'count'];
|
|
19
|
+
}
|
|
20
|
+
constructor() {
|
|
21
|
+
super();
|
|
22
|
+
const root = this.attachShadow({ mode: 'open' });
|
|
23
|
+
const style = document.createElement('style');
|
|
24
|
+
style.textContent = `
|
|
25
|
+
:host {
|
|
26
|
+
position: relative;
|
|
27
|
+
display: block;
|
|
28
|
+
min-width: 0;
|
|
29
|
+
border-radius: var(--hy-border-beam-radius, 10px);
|
|
30
|
+
isolation: isolate;
|
|
31
|
+
--_beam-color: var(--hy-accent-color, #7dd3fc);
|
|
32
|
+
}
|
|
33
|
+
svg {
|
|
34
|
+
position: absolute;
|
|
35
|
+
z-index: 1;
|
|
36
|
+
inset: 0;
|
|
37
|
+
width: 100%;
|
|
38
|
+
height: 100%;
|
|
39
|
+
overflow: visible;
|
|
40
|
+
pointer-events: none;
|
|
41
|
+
}
|
|
42
|
+
.track {
|
|
43
|
+
fill: none;
|
|
44
|
+
stroke: var(--hy-border-beam-track-color, var(--hy-border-color, rgba(125, 211, 252, 0.2)));
|
|
45
|
+
vector-effect: non-scaling-stroke;
|
|
46
|
+
}
|
|
47
|
+
.beam {
|
|
48
|
+
fill: none;
|
|
49
|
+
stroke: var(--_beam-color);
|
|
50
|
+
stroke-linecap: round;
|
|
51
|
+
vector-effect: non-scaling-stroke;
|
|
52
|
+
filter: drop-shadow(0 0 var(--_beam-glow, 5px) var(--_beam-color));
|
|
53
|
+
animation: hy-border-beam-flow linear infinite;
|
|
54
|
+
}
|
|
55
|
+
@keyframes hy-border-beam-flow {
|
|
56
|
+
to { stroke-dashoffset: -100; }
|
|
57
|
+
}
|
|
58
|
+
@media (prefers-reduced-motion: reduce) {
|
|
59
|
+
.beam { animation-play-state: paused; }
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
const slot = document.createElement('slot');
|
|
63
|
+
this._svg.setAttribute('aria-hidden', 'true');
|
|
64
|
+
this._svg.setAttribute('preserveAspectRatio', 'none');
|
|
65
|
+
this._track.classList.add('track');
|
|
66
|
+
this._track.part.add('track');
|
|
67
|
+
this._track.setAttribute('pathLength', '100');
|
|
68
|
+
this._svg.append(this._track, this._beamLayer);
|
|
69
|
+
root.append(style, slot, this._svg);
|
|
70
|
+
this._renderBeams();
|
|
71
|
+
}
|
|
72
|
+
connectedCallback() {
|
|
73
|
+
if (this._connected)
|
|
74
|
+
return;
|
|
75
|
+
this._connected = true;
|
|
76
|
+
this._updateAppearance();
|
|
77
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
78
|
+
this._resizeObserver = new ResizeObserver(() => this.refresh());
|
|
79
|
+
this._resizeObserver.observe(this);
|
|
80
|
+
}
|
|
81
|
+
this.refresh();
|
|
82
|
+
}
|
|
83
|
+
disconnectedCallback() {
|
|
84
|
+
if (!this._connected)
|
|
85
|
+
return;
|
|
86
|
+
this._connected = false;
|
|
87
|
+
this._resizeObserver?.disconnect();
|
|
88
|
+
this._resizeObserver = null;
|
|
89
|
+
}
|
|
90
|
+
attributeChangedCallback(name) {
|
|
91
|
+
if (name === 'count')
|
|
92
|
+
this._renderBeams();
|
|
93
|
+
this._updateAppearance();
|
|
94
|
+
this.refresh();
|
|
95
|
+
}
|
|
96
|
+
get thickness() {
|
|
97
|
+
return Math.min(20, Math.max(0.5, finiteNumber(this.getAttribute('thickness'), DEFAULT_THICKNESS)));
|
|
98
|
+
}
|
|
99
|
+
set thickness(value) {
|
|
100
|
+
this.setAttribute('thickness', String(Math.min(20, Math.max(0.5, finiteNumber(value, DEFAULT_THICKNESS)))));
|
|
101
|
+
}
|
|
102
|
+
/** Animation speed multiplier. 1 is normal speed and 2 is twice as fast. */
|
|
103
|
+
get speed() {
|
|
104
|
+
return Math.min(10, Math.max(0.1, finiteNumber(this.getAttribute('speed'), DEFAULT_SPEED)));
|
|
105
|
+
}
|
|
106
|
+
set speed(value) {
|
|
107
|
+
this.setAttribute('speed', String(Math.min(10, Math.max(0.1, finiteNumber(value, DEFAULT_SPEED)))));
|
|
108
|
+
}
|
|
109
|
+
get color() { return this.getAttribute('color') ?? ''; }
|
|
110
|
+
set color(value) {
|
|
111
|
+
const normalized = String(value).trim();
|
|
112
|
+
if (normalized)
|
|
113
|
+
this.setAttribute('color', normalized);
|
|
114
|
+
else
|
|
115
|
+
this.removeAttribute('color');
|
|
116
|
+
}
|
|
117
|
+
get count() {
|
|
118
|
+
return Math.min(12, Math.max(1, Math.floor(finiteNumber(this.getAttribute('count'), DEFAULT_COUNT))));
|
|
119
|
+
}
|
|
120
|
+
set count(value) {
|
|
121
|
+
this.setAttribute('count', String(Math.min(12, Math.max(1, Math.floor(finiteNumber(value, DEFAULT_COUNT))))));
|
|
122
|
+
}
|
|
123
|
+
refresh() {
|
|
124
|
+
const rect = this.getBoundingClientRect();
|
|
125
|
+
const width = Math.max(1, rect.width);
|
|
126
|
+
const height = Math.max(1, rect.height);
|
|
127
|
+
const inset = this.thickness / 2;
|
|
128
|
+
const radius = Math.min(Math.max(0, Number.parseFloat(getComputedStyle(this).borderTopLeftRadius) || 10), width / 2, height / 2);
|
|
129
|
+
this._svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
|
130
|
+
for (const element of [this._track, ...this._beamLayer.querySelectorAll('.beam')]) {
|
|
131
|
+
element.setAttribute('x', String(inset));
|
|
132
|
+
element.setAttribute('y', String(inset));
|
|
133
|
+
element.setAttribute('width', String(Math.max(0, width - this.thickness)));
|
|
134
|
+
element.setAttribute('height', String(Math.max(0, height - this.thickness)));
|
|
135
|
+
element.setAttribute('rx', String(Math.max(0, radius - inset)));
|
|
136
|
+
element.setAttribute('ry', String(Math.max(0, radius - inset)));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
_renderBeams() {
|
|
140
|
+
const beams = [];
|
|
141
|
+
for (let index = 0; index < this.count; index += 1) {
|
|
142
|
+
const beam = document.createElementNS(SVG_NAMESPACE, 'rect');
|
|
143
|
+
beam.classList.add('beam');
|
|
144
|
+
beam.part.add('beam');
|
|
145
|
+
beam.setAttribute('pathLength', '100');
|
|
146
|
+
beams.push(beam);
|
|
147
|
+
}
|
|
148
|
+
this._beamLayer.replaceChildren(...beams);
|
|
149
|
+
// Construction may populate the shadow tree, but must not add host attributes.
|
|
150
|
+
// Host styles are applied by the connection/attribute lifecycle instead.
|
|
151
|
+
}
|
|
152
|
+
_updateAppearance() {
|
|
153
|
+
const duration = BASE_DURATION_SECONDS / this.speed;
|
|
154
|
+
const beamLength = Math.min(14, 60 / this.count);
|
|
155
|
+
this.style.setProperty('--_beam-glow', `${Math.max(3, this.thickness * 2.5)}px`);
|
|
156
|
+
if (this.color)
|
|
157
|
+
this.style.setProperty('--_beam-color', this.color);
|
|
158
|
+
else
|
|
159
|
+
this.style.removeProperty('--_beam-color');
|
|
160
|
+
this._track.setAttribute('stroke-width', String(Math.max(1, this.thickness * 0.5)));
|
|
161
|
+
const beams = [...this._beamLayer.querySelectorAll('.beam')];
|
|
162
|
+
beams.forEach((beam, index) => {
|
|
163
|
+
beam.setAttribute('stroke-width', String(this.thickness));
|
|
164
|
+
beam.setAttribute('stroke-dasharray', `${beamLength} ${100 - beamLength}`);
|
|
165
|
+
beam.style.animationDuration = `${duration}s`;
|
|
166
|
+
beam.style.animationDelay = `${-(duration * index) / Math.max(1, beams.length)}s`;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export function defineBorderBeamComponents() {
|
|
171
|
+
if (!customElements.get('hy-border-beam'))
|
|
172
|
+
customElements.define('hy-border-beam', HYBorderBeam);
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=border-beam.js.map
|
package/dist/button.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class HYButton extends HTMLElement {
|
|
2
|
+
private readonly _button;
|
|
3
|
+
private readonly _slot;
|
|
4
|
+
static get observedAttributes(): string[];
|
|
5
|
+
constructor();
|
|
6
|
+
connectedCallback(): void;
|
|
7
|
+
attributeChangedCallback(): void;
|
|
8
|
+
private _sync;
|
|
9
|
+
}
|
|
10
|
+
export declare function defineButtonComponents(): void;
|
package/dist/button.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export class HYButton extends HTMLElement {
|
|
2
|
+
_button = document.createElement('button');
|
|
3
|
+
_slot = document.createElement('slot');
|
|
4
|
+
static get observedAttributes() {
|
|
5
|
+
return ['label', 'disabled', 'aria-label', 'title', 'icon-only'];
|
|
6
|
+
}
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
const root = this.attachShadow({ mode: 'open' });
|
|
10
|
+
const style = document.createElement('style');
|
|
11
|
+
style.textContent = `
|
|
12
|
+
:host { display: inline-block; }
|
|
13
|
+
button {
|
|
14
|
+
min-height: 28px;
|
|
15
|
+
padding: 0 10px;
|
|
16
|
+
border: 1px solid var(--hy-border-color, #3d4654);
|
|
17
|
+
border-radius: 4px;
|
|
18
|
+
background: var(--hy-surface-elevated-color, #202733);
|
|
19
|
+
color: var(--hy-text-color, #eef3ff);
|
|
20
|
+
font: 12px system-ui, sans-serif;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
}
|
|
23
|
+
button:hover:not(:disabled) {
|
|
24
|
+
border-color: var(--hy-hover-border-color, #536477);
|
|
25
|
+
background: var(--hy-hover-bg-color, #273241);
|
|
26
|
+
}
|
|
27
|
+
button:focus-visible {
|
|
28
|
+
outline: 2px solid var(--hy-focus-border-color, #79a8ff);
|
|
29
|
+
outline-offset: 1px;
|
|
30
|
+
}
|
|
31
|
+
button.icon-only {
|
|
32
|
+
width: 28px;
|
|
33
|
+
padding: 0;
|
|
34
|
+
display: inline-grid;
|
|
35
|
+
place-items: center;
|
|
36
|
+
}
|
|
37
|
+
::slotted(svg) {
|
|
38
|
+
width: 16px;
|
|
39
|
+
height: 16px;
|
|
40
|
+
display: block;
|
|
41
|
+
fill: none;
|
|
42
|
+
stroke: currentColor;
|
|
43
|
+
stroke-width: 2;
|
|
44
|
+
stroke-linecap: round;
|
|
45
|
+
stroke-linejoin: round;
|
|
46
|
+
}
|
|
47
|
+
button:disabled {
|
|
48
|
+
opacity: 0.5;
|
|
49
|
+
cursor: default;
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
root.append(style, this._button);
|
|
53
|
+
}
|
|
54
|
+
connectedCallback() {
|
|
55
|
+
this._sync();
|
|
56
|
+
}
|
|
57
|
+
attributeChangedCallback() {
|
|
58
|
+
this._sync();
|
|
59
|
+
}
|
|
60
|
+
_sync() {
|
|
61
|
+
const label = this.getAttribute('label');
|
|
62
|
+
if (label !== null) {
|
|
63
|
+
this._button.textContent = label;
|
|
64
|
+
this._button.classList.remove('icon-only');
|
|
65
|
+
this._button.removeAttribute('aria-label');
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (this._button.firstChild !== this._slot)
|
|
69
|
+
this._button.replaceChildren(this._slot);
|
|
70
|
+
this._button.classList.toggle('icon-only', this.hasAttribute('icon-only'));
|
|
71
|
+
const accessibleLabel = this.getAttribute('aria-label') ?? this.getAttribute('title') ?? '';
|
|
72
|
+
if (accessibleLabel)
|
|
73
|
+
this._button.setAttribute('aria-label', accessibleLabel);
|
|
74
|
+
else
|
|
75
|
+
this._button.removeAttribute('aria-label');
|
|
76
|
+
}
|
|
77
|
+
this._button.disabled = this.hasAttribute('disabled');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function defineButtonComponents() {
|
|
81
|
+
if (!customElements.get('hy-button')) {
|
|
82
|
+
customElements.define('hy-button', HYButton);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=button.js.map
|
package/dist/checkbox.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface HYCheckboxChangeDetail {
|
|
2
2
|
checked: boolean;
|
|
3
3
|
value: string;
|
|
4
4
|
}
|
|
5
|
-
export declare class
|
|
5
|
+
export declare class HYCheckbox extends HTMLElement {
|
|
6
6
|
private readonly _input;
|
|
7
7
|
private readonly _box;
|
|
8
8
|
private readonly _label;
|
package/dist/checkbox.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export class
|
|
1
|
+
export class HYCheckbox extends HTMLElement {
|
|
2
2
|
_input = document.createElement('input');
|
|
3
3
|
_box = document.createElement('span');
|
|
4
4
|
_label = document.createElement('span');
|
|
@@ -15,7 +15,7 @@ export class GECheckbox extends HTMLElement {
|
|
|
15
15
|
display: inline-block;
|
|
16
16
|
width: max-content;
|
|
17
17
|
min-width: 0;
|
|
18
|
-
color: var(--
|
|
18
|
+
color: var(--hy-checkbox-text-color, var(--hy-text-color, #d8e2f2));
|
|
19
19
|
font: 12px system-ui, sans-serif;
|
|
20
20
|
}
|
|
21
21
|
:host([disabled]) {
|
|
@@ -51,20 +51,20 @@ export class GECheckbox extends HTMLElement {
|
|
|
51
51
|
flex: 0 0 14px;
|
|
52
52
|
display: inline-grid;
|
|
53
53
|
place-items: center;
|
|
54
|
-
border: 1px solid var(--
|
|
54
|
+
border: 1px solid var(--hy-checkbox-border-color, var(--hy-border-color, #303746));
|
|
55
55
|
border-radius: 3px;
|
|
56
|
-
background: var(--
|
|
57
|
-
color: var(--
|
|
56
|
+
background: var(--hy-checkbox-bg-color, var(--hy-input-bg-color, #121822));
|
|
57
|
+
color: var(--hy-checkbox-mark-color, var(--hy-selected-text-color, #ffffff));
|
|
58
58
|
transition: background 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease;
|
|
59
59
|
}
|
|
60
60
|
input:focus-visible + .box {
|
|
61
|
-
border-color: var(--
|
|
62
|
-
box-shadow: 0 0 0 2px var(--
|
|
61
|
+
border-color: var(--hy-focus-border-color, #3d6fa8);
|
|
62
|
+
box-shadow: 0 0 0 2px var(--hy-focus-ring-color, rgba(61, 111, 168, 0.28));
|
|
63
63
|
}
|
|
64
64
|
input:checked + .box,
|
|
65
65
|
input:indeterminate + .box {
|
|
66
|
-
border-color: var(--
|
|
67
|
-
background: var(--
|
|
66
|
+
border-color: var(--hy-checkbox-checked-border-color, var(--hy-accent-color, #3d6fa8));
|
|
67
|
+
background: var(--hy-checkbox-checked-bg-color, var(--hy-accent-strong-color, #255a91));
|
|
68
68
|
}
|
|
69
69
|
input:disabled + .box {
|
|
70
70
|
cursor: default;
|
|
@@ -197,8 +197,8 @@ export class GECheckbox extends HTMLElement {
|
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
export function defineCheckboxComponents() {
|
|
200
|
-
if (!customElements.get('
|
|
201
|
-
customElements.define('
|
|
200
|
+
if (!customElements.get('hy-checkbox')) {
|
|
201
|
+
customElements.define('hy-checkbox', HYCheckbox);
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
//# sourceMappingURL=checkbox.js.map
|
package/dist/context-menu.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface HYContextMenuItem {
|
|
2
2
|
label?: string;
|
|
3
3
|
value?: string;
|
|
4
4
|
disabled?: boolean;
|
|
5
5
|
separator?: boolean;
|
|
6
6
|
}
|
|
7
|
-
export interface
|
|
7
|
+
export interface HYContextMenuSelectDetail {
|
|
8
8
|
value: string;
|
|
9
|
-
item:
|
|
9
|
+
item: HYContextMenuItem;
|
|
10
10
|
}
|
|
11
|
-
export declare class
|
|
11
|
+
export declare class HYContextMenu extends HTMLElement {
|
|
12
12
|
private readonly _root;
|
|
13
13
|
private readonly _style;
|
|
14
14
|
private readonly _menu;
|
|
@@ -20,8 +20,8 @@ export declare class GEContextMenu extends HTMLElement {
|
|
|
20
20
|
connectedCallback(): void;
|
|
21
21
|
disconnectedCallback(): void;
|
|
22
22
|
attributeChangedCallback(name: string): void;
|
|
23
|
-
get items():
|
|
24
|
-
set items(value:
|
|
23
|
+
get items(): HYContextMenuItem[];
|
|
24
|
+
set items(value: HYContextMenuItem[]);
|
|
25
25
|
openAt(x: number, y: number): void;
|
|
26
26
|
close(): void;
|
|
27
27
|
private _syncDocumentListeners;
|