@haiyue/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/checkbox.d.ts +30 -0
  4. package/dist/checkbox.js +204 -0
  5. package/dist/context-menu.d.ts +41 -0
  6. package/dist/context-menu.js +225 -0
  7. package/dist/dialog.d.ts +30 -0
  8. package/dist/dialog.js +183 -0
  9. package/dist/dropdown.d.ts +45 -0
  10. package/dist/dropdown.js +228 -0
  11. package/dist/index.d.ts +32 -0
  12. package/dist/index.js +110 -0
  13. package/dist/input.d.ts +30 -0
  14. package/dist/input.js +131 -0
  15. package/dist/menu-shared.d.ts +14 -0
  16. package/dist/menu-shared.js +54 -0
  17. package/dist/radio.d.ts +30 -0
  18. package/dist/radio.js +205 -0
  19. package/dist/select.d.ts +40 -0
  20. package/dist/select.js +253 -0
  21. package/dist/split.d.ts +46 -0
  22. package/dist/split.js +277 -0
  23. package/dist/tabs.d.ts +28 -0
  24. package/dist/tabs.js +161 -0
  25. package/dist/tooltip.d.ts +37 -0
  26. package/dist/tooltip.js +286 -0
  27. package/dist/tree-model.d.ts +35 -0
  28. package/dist/tree-model.js +281 -0
  29. package/dist/tree-node.d.ts +11 -0
  30. package/dist/tree-node.js +63 -0
  31. package/dist/tree-types.d.ts +41 -0
  32. package/dist/tree-types.js +2 -0
  33. package/dist/tree.d.ts +102 -0
  34. package/dist/tree.js +942 -0
  35. package/package.json +46 -0
  36. package/src/checkbox.ts +215 -0
  37. package/src/context-menu.ts +248 -0
  38. package/src/dialog.ts +195 -0
  39. package/src/dropdown.ts +249 -0
  40. package/src/index.ts +192 -0
  41. package/src/input.ts +150 -0
  42. package/src/menu-shared.ts +65 -0
  43. package/src/radio.ts +216 -0
  44. package/src/select.ts +271 -0
  45. package/src/split.ts +306 -0
  46. package/src/tabs.ts +182 -0
  47. package/src/tooltip.ts +321 -0
  48. package/src/tree-model.ts +305 -0
  49. package/src/tree-node.ts +67 -0
  50. package/src/tree-types.ts +47 -0
  51. package/src/tree.ts +1002 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HaiyueStudio Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @haiyue/ui
2
+
3
+ Dependency-free Web Components used by HaiyueStudio tools and products.
4
+
5
+ ```bash
6
+ npm install @haiyue/ui
7
+ ```
8
+
9
+ The package owns presentation and local interaction only. It does not depend on Engine, Editor, or AIStudio.
@@ -0,0 +1,30 @@
1
+ export interface GECheckboxChangeDetail {
2
+ checked: boolean;
3
+ value: string;
4
+ }
5
+ export declare class GECheckbox extends HTMLElement {
6
+ private readonly _input;
7
+ private readonly _box;
8
+ private readonly _label;
9
+ private readonly _slot;
10
+ static get observedAttributes(): string[];
11
+ constructor();
12
+ connectedCallback(): void;
13
+ disconnectedCallback(): void;
14
+ attributeChangedCallback(): void;
15
+ get checked(): boolean;
16
+ set checked(value: boolean);
17
+ get disabled(): boolean;
18
+ set disabled(value: boolean);
19
+ get indeterminate(): boolean;
20
+ set indeterminate(value: boolean);
21
+ get label(): string;
22
+ set label(value: string);
23
+ get value(): string;
24
+ set value(value: string);
25
+ get name(): string;
26
+ set name(value: string);
27
+ private _sync;
28
+ private _onChange;
29
+ }
30
+ export declare function defineCheckboxComponents(): void;
@@ -0,0 +1,204 @@
1
+ export class GECheckbox extends HTMLElement {
2
+ _input = document.createElement('input');
3
+ _box = document.createElement('span');
4
+ _label = document.createElement('span');
5
+ _slot = document.createElement('slot');
6
+ static get observedAttributes() {
7
+ return ['checked', 'disabled', 'indeterminate', 'label', 'value', 'name', 'aria-label'];
8
+ }
9
+ constructor() {
10
+ super();
11
+ const root = this.attachShadow({ mode: 'open' });
12
+ const style = document.createElement('style');
13
+ style.textContent = `
14
+ :host {
15
+ display: inline-block;
16
+ width: max-content;
17
+ min-width: 0;
18
+ color: var(--ge-checkbox-text-color, var(--ge-text-color, #d8e2f2));
19
+ font: 12px system-ui, sans-serif;
20
+ }
21
+ :host([disabled]) {
22
+ opacity: 0.55;
23
+ cursor: default;
24
+ }
25
+ label {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ gap: 8px;
29
+ min-width: 0;
30
+ cursor: pointer;
31
+ user-select: none;
32
+ }
33
+ :host([disabled]) label {
34
+ cursor: default;
35
+ }
36
+ input {
37
+ position: absolute;
38
+ width: 1px;
39
+ height: 1px;
40
+ margin: -1px;
41
+ padding: 0;
42
+ overflow: hidden;
43
+ clip: rect(0 0 0 0);
44
+ white-space: nowrap;
45
+ border: 0;
46
+ }
47
+ .box {
48
+ box-sizing: border-box;
49
+ width: 14px;
50
+ height: 14px;
51
+ flex: 0 0 14px;
52
+ display: inline-grid;
53
+ place-items: center;
54
+ border: 1px solid var(--ge-checkbox-border-color, var(--ge-border-color, #303746));
55
+ border-radius: 3px;
56
+ background: var(--ge-checkbox-bg-color, var(--ge-input-bg-color, #121822));
57
+ color: var(--ge-checkbox-mark-color, var(--ge-selected-text-color, #ffffff));
58
+ transition: background 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease;
59
+ }
60
+ input:focus-visible + .box {
61
+ border-color: var(--ge-focus-border-color, #3d6fa8);
62
+ box-shadow: 0 0 0 2px var(--ge-focus-ring-color, rgba(61, 111, 168, 0.28));
63
+ }
64
+ input:checked + .box,
65
+ input:indeterminate + .box {
66
+ border-color: var(--ge-checkbox-checked-border-color, var(--ge-accent-color, #3d6fa8));
67
+ background: var(--ge-checkbox-checked-bg-color, var(--ge-accent-strong-color, #255a91));
68
+ }
69
+ input:disabled + .box {
70
+ cursor: default;
71
+ }
72
+ .box::after {
73
+ content: "";
74
+ width: 7px;
75
+ height: 4px;
76
+ border: solid currentColor;
77
+ border-width: 0 0 2px 2px;
78
+ transform: rotate(-45deg) scale(0);
79
+ transform-origin: center;
80
+ transition: transform 0.12s ease;
81
+ }
82
+ input:checked + .box::after {
83
+ transform: rotate(-45deg) scale(1);
84
+ }
85
+ input:indeterminate + .box::after {
86
+ width: 7px;
87
+ height: 0;
88
+ border-width: 0 0 2px;
89
+ transform: rotate(0deg) scale(1);
90
+ }
91
+ .label {
92
+ min-width: 0;
93
+ overflow: hidden;
94
+ text-overflow: ellipsis;
95
+ white-space: nowrap;
96
+ }
97
+ `;
98
+ const label = document.createElement('label');
99
+ this._input.type = 'checkbox';
100
+ this._box.className = 'box';
101
+ this._box.setAttribute('aria-hidden', 'true');
102
+ this._label.className = 'label';
103
+ this._label.append(this._slot);
104
+ label.append(this._input, this._box, this._label);
105
+ root.append(style, label);
106
+ }
107
+ connectedCallback() {
108
+ this._input.addEventListener('change', this._onChange);
109
+ this._sync();
110
+ }
111
+ disconnectedCallback() {
112
+ this._input.removeEventListener('change', this._onChange);
113
+ }
114
+ attributeChangedCallback() {
115
+ this._sync();
116
+ }
117
+ get checked() {
118
+ return this.hasAttribute('checked');
119
+ }
120
+ set checked(value) {
121
+ if (value)
122
+ this.setAttribute('checked', '');
123
+ else
124
+ this.removeAttribute('checked');
125
+ }
126
+ get disabled() {
127
+ return this.hasAttribute('disabled');
128
+ }
129
+ set disabled(value) {
130
+ if (value)
131
+ this.setAttribute('disabled', '');
132
+ else
133
+ this.removeAttribute('disabled');
134
+ }
135
+ get indeterminate() {
136
+ return this.hasAttribute('indeterminate');
137
+ }
138
+ set indeterminate(value) {
139
+ if (value)
140
+ this.setAttribute('indeterminate', '');
141
+ else
142
+ this.removeAttribute('indeterminate');
143
+ }
144
+ get label() {
145
+ return this.getAttribute('label') ?? '';
146
+ }
147
+ set label(value) {
148
+ if (value)
149
+ this.setAttribute('label', value);
150
+ else
151
+ this.removeAttribute('label');
152
+ }
153
+ get value() {
154
+ return this.getAttribute('value') ?? 'on';
155
+ }
156
+ set value(value) {
157
+ this.setAttribute('value', value);
158
+ }
159
+ get name() {
160
+ return this.getAttribute('name') ?? '';
161
+ }
162
+ set name(value) {
163
+ if (value)
164
+ this.setAttribute('name', value);
165
+ else
166
+ this.removeAttribute('name');
167
+ }
168
+ _sync() {
169
+ this._input.checked = this.checked;
170
+ this._input.disabled = this.disabled;
171
+ this._input.indeterminate = this.indeterminate;
172
+ this._input.value = this.value;
173
+ this._input.name = this.name;
174
+ const label = this.getAttribute('label');
175
+ if (label !== null)
176
+ this._slot.textContent = label;
177
+ else if (this._slot.textContent !== '')
178
+ this._slot.textContent = '';
179
+ const ariaLabel = this.getAttribute('aria-label') ?? label ?? '';
180
+ if (ariaLabel)
181
+ this._input.setAttribute('aria-label', ariaLabel);
182
+ else
183
+ this._input.removeAttribute('aria-label');
184
+ }
185
+ _onChange = () => {
186
+ if (this._input.checked)
187
+ this.setAttribute('checked', '');
188
+ else
189
+ this.removeAttribute('checked');
190
+ this.removeAttribute('indeterminate');
191
+ this.dispatchEvent(new CustomEvent('checked-change', {
192
+ detail: { checked: this.checked, value: this.value },
193
+ bubbles: true,
194
+ composed: true,
195
+ }));
196
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
197
+ };
198
+ }
199
+ export function defineCheckboxComponents() {
200
+ if (!customElements.get('ge-checkbox')) {
201
+ customElements.define('ge-checkbox', GECheckbox);
202
+ }
203
+ }
204
+ //# sourceMappingURL=checkbox.js.map
@@ -0,0 +1,41 @@
1
+ export interface GEContextMenuItem {
2
+ label?: string;
3
+ value?: string;
4
+ disabled?: boolean;
5
+ separator?: boolean;
6
+ }
7
+ export interface GEContextMenuSelectDetail {
8
+ value: string;
9
+ item: GEContextMenuItem;
10
+ }
11
+ export declare class GEContextMenu extends HTMLElement {
12
+ private readonly _root;
13
+ private readonly _style;
14
+ private readonly _menu;
15
+ private _items;
16
+ private _documentListenerAbort;
17
+ private _activeIndex;
18
+ static get observedAttributes(): string[];
19
+ constructor();
20
+ connectedCallback(): void;
21
+ disconnectedCallback(): void;
22
+ attributeChangedCallback(name: string): void;
23
+ get items(): GEContextMenuItem[];
24
+ set items(value: GEContextMenuItem[]);
25
+ openAt(x: number, y: number): void;
26
+ close(): void;
27
+ private _syncDocumentListeners;
28
+ private _attachDocumentListeners;
29
+ private _detachDocumentListeners;
30
+ private _readItemsAttribute;
31
+ private _render;
32
+ private _onDocumentPointerDown;
33
+ private _onDocumentKeyDown;
34
+ private _selectIndex;
35
+ private _setActiveIndex;
36
+ private _nextEnabledIndex;
37
+ private _firstEnabledIndex;
38
+ private _lastEnabledIndex;
39
+ private _isEnabledItem;
40
+ }
41
+ export declare function defineContextMenuComponents(): void;
@@ -0,0 +1,225 @@
1
+ import { GE_MENU_ITEM_STYLES, createMenuItemButton, createMenuSeparator } from './menu-shared.js';
2
+ export class GEContextMenu extends HTMLElement {
3
+ _root;
4
+ _style = document.createElement('style');
5
+ _menu = document.createElement('div');
6
+ _items = [];
7
+ _documentListenerAbort = null;
8
+ _activeIndex = -1;
9
+ static get observedAttributes() {
10
+ return ['items', 'open'];
11
+ }
12
+ constructor() {
13
+ super();
14
+ this._root = this.attachShadow({ mode: 'open' });
15
+ this._style.textContent = `
16
+ :host {
17
+ position: fixed;
18
+ z-index: 10000;
19
+ display: none;
20
+ min-width: 132px;
21
+ color: var(--ge-text-color, #d8e2f2);
22
+ font: 12px system-ui, sans-serif;
23
+ }
24
+ :host([open]) {
25
+ display: block;
26
+ }
27
+ .menu {
28
+ box-sizing: border-box;
29
+ min-width: 132px;
30
+ padding: 4px;
31
+ border: 1px solid var(--ge-border-color, #303746);
32
+ border-radius: 4px;
33
+ background: var(--ge-menu-bg-color, var(--ge-surface-elevated-color, #1b2230));
34
+ box-shadow: 0 10px 26px var(--ge-menu-shadow-color, rgba(0, 0, 0, 0.36));
35
+ }
36
+ ${GE_MENU_ITEM_STYLES}
37
+ `;
38
+ this._menu.className = 'menu';
39
+ this._menu.setAttribute('role', 'menu');
40
+ this._root.append(this._style, this._menu);
41
+ }
42
+ connectedCallback() {
43
+ this._readItemsAttribute();
44
+ this._render();
45
+ this._syncDocumentListeners();
46
+ }
47
+ disconnectedCallback() {
48
+ this._detachDocumentListeners();
49
+ }
50
+ attributeChangedCallback(name) {
51
+ if (name === 'items') {
52
+ this._readItemsAttribute();
53
+ this._render();
54
+ }
55
+ if (name === 'open')
56
+ this._syncDocumentListeners();
57
+ }
58
+ get items() {
59
+ return this._items;
60
+ }
61
+ set items(value) {
62
+ this._items = Array.isArray(value) ? value : [];
63
+ this._render();
64
+ }
65
+ openAt(x, y) {
66
+ this.setAttribute('open', '');
67
+ this.style.left = '0px';
68
+ this.style.top = '0px';
69
+ const rect = this._menu.getBoundingClientRect();
70
+ const viewport = getVisualViewportRect();
71
+ const left = Math.min(x, viewport.left + viewport.width - rect.width - 4);
72
+ const top = Math.min(y, viewport.top + viewport.height - rect.height - 4);
73
+ this.style.left = `${Math.max(viewport.left + 4, left)}px`;
74
+ this.style.top = `${Math.max(viewport.top + 4, top)}px`;
75
+ this._setActiveIndex(this._firstEnabledIndex(), false);
76
+ }
77
+ close() {
78
+ this.removeAttribute('open');
79
+ this._setActiveIndex(-1, false);
80
+ }
81
+ _syncDocumentListeners() {
82
+ if (this.hasAttribute('open') && this.isConnected)
83
+ this._attachDocumentListeners();
84
+ else
85
+ this._detachDocumentListeners();
86
+ }
87
+ _attachDocumentListeners() {
88
+ if (this._documentListenerAbort)
89
+ return;
90
+ this._documentListenerAbort = new AbortController();
91
+ const { signal } = this._documentListenerAbort;
92
+ document.addEventListener('pointerdown', this._onDocumentPointerDown, { capture: true, signal });
93
+ document.addEventListener('keydown', this._onDocumentKeyDown, { capture: true, signal });
94
+ }
95
+ _detachDocumentListeners() {
96
+ this._documentListenerAbort?.abort();
97
+ this._documentListenerAbort = null;
98
+ }
99
+ _readItemsAttribute() {
100
+ const raw = this.getAttribute('items');
101
+ if (!raw) {
102
+ this._items = [];
103
+ return;
104
+ }
105
+ try {
106
+ const parsed = JSON.parse(raw);
107
+ this._items = Array.isArray(parsed) ? parsed : [];
108
+ }
109
+ catch (error) {
110
+ console.warn('Invalid ge-context-menu items attribute.', error);
111
+ }
112
+ }
113
+ _render() {
114
+ this._menu.replaceChildren();
115
+ this._activeIndex = -1;
116
+ for (const [index, item] of this._items.entries()) {
117
+ if (item.separator) {
118
+ this._menu.append(createMenuSeparator());
119
+ continue;
120
+ }
121
+ const button = createMenuItemButton(item, { index, role: 'menuitem', tabIndex: -1 });
122
+ button.addEventListener('click', () => {
123
+ this._selectIndex(index);
124
+ });
125
+ this._menu.append(button);
126
+ }
127
+ }
128
+ _onDocumentPointerDown = (event) => {
129
+ if (!this.hasAttribute('open'))
130
+ return;
131
+ const path = event.composedPath();
132
+ if (!path.includes(this))
133
+ this.close();
134
+ };
135
+ _onDocumentKeyDown = (event) => {
136
+ if (!this.hasAttribute('open'))
137
+ return;
138
+ if (event.key === 'Escape') {
139
+ event.preventDefault();
140
+ this.close();
141
+ return;
142
+ }
143
+ if (event.key === 'ArrowDown') {
144
+ event.preventDefault();
145
+ this._setActiveIndex(this._nextEnabledIndex(1), true);
146
+ return;
147
+ }
148
+ if (event.key === 'ArrowUp') {
149
+ event.preventDefault();
150
+ this._setActiveIndex(this._nextEnabledIndex(-1), true);
151
+ return;
152
+ }
153
+ if (event.key === 'Home') {
154
+ event.preventDefault();
155
+ this._setActiveIndex(this._firstEnabledIndex(), true);
156
+ return;
157
+ }
158
+ if (event.key === 'End') {
159
+ event.preventDefault();
160
+ this._setActiveIndex(this._lastEnabledIndex(), true);
161
+ return;
162
+ }
163
+ if (event.key === 'Enter' || event.key === ' ') {
164
+ event.preventDefault();
165
+ this._selectIndex(this._activeIndex);
166
+ }
167
+ };
168
+ _selectIndex(index) {
169
+ const item = this._items[index];
170
+ if (!item || item.disabled || item.separator || !item.value)
171
+ return;
172
+ this.dispatchEvent(new CustomEvent('item-select', {
173
+ detail: { value: item.value, item },
174
+ bubbles: true,
175
+ composed: true,
176
+ }));
177
+ this.close();
178
+ }
179
+ _setActiveIndex(index, focus) {
180
+ this._activeIndex = index;
181
+ for (const button of this._menu.querySelectorAll('button')) {
182
+ const active = Number(button.dataset.index) === index;
183
+ button.classList.toggle('active', active);
184
+ if (active && focus)
185
+ button.focus();
186
+ }
187
+ }
188
+ _nextEnabledIndex(direction) {
189
+ if (!this._items.length)
190
+ return -1;
191
+ const start = this._activeIndex >= 0 ? this._activeIndex : direction > 0 ? -1 : this._items.length;
192
+ for (let step = 1; step <= this._items.length; step++) {
193
+ const index = (start + direction * step + this._items.length) % this._items.length;
194
+ if (this._isEnabledItem(index))
195
+ return index;
196
+ }
197
+ return -1;
198
+ }
199
+ _firstEnabledIndex() {
200
+ return this._items.findIndex((_, index) => this._isEnabledItem(index));
201
+ }
202
+ _lastEnabledIndex() {
203
+ for (let index = this._items.length - 1; index >= 0; index--) {
204
+ if (this._isEnabledItem(index))
205
+ return index;
206
+ }
207
+ return -1;
208
+ }
209
+ _isEnabledItem(index) {
210
+ const item = this._items[index];
211
+ return Boolean(item && !item.separator && !item.disabled && item.value);
212
+ }
213
+ }
214
+ function getVisualViewportRect() {
215
+ const viewport = window.visualViewport;
216
+ return viewport
217
+ ? { left: viewport.offsetLeft, top: viewport.offsetTop, width: viewport.width, height: viewport.height }
218
+ : { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight };
219
+ }
220
+ export function defineContextMenuComponents() {
221
+ if (!customElements.get('ge-context-menu')) {
222
+ customElements.define('ge-context-menu', GEContextMenu);
223
+ }
224
+ }
225
+ //# sourceMappingURL=context-menu.js.map
@@ -0,0 +1,30 @@
1
+ export type GEDialogCloseReason = 'action' | 'backdrop' | 'escape' | 'programmatic';
2
+ export interface GEDialogCloseDetail {
3
+ readonly reason: GEDialogCloseReason | string;
4
+ }
5
+ /** Accessible modal surface with slotted body/footer content and reflected open state. */
6
+ export declare class GEDialog extends HTMLElement {
7
+ private readonly _dialog;
8
+ private readonly _title;
9
+ private readonly _closeButton;
10
+ private _returnFocus;
11
+ private _closeReason;
12
+ static get observedAttributes(): string[];
13
+ constructor();
14
+ connectedCallback(): void;
15
+ attributeChangedCallback(name: string): void;
16
+ get open(): boolean;
17
+ set open(value: boolean);
18
+ get dismissible(): boolean;
19
+ set dismissible(value: boolean);
20
+ get heading(): string;
21
+ set heading(value: string);
22
+ showModal(): void;
23
+ close(reason?: GEDialogCloseDetail['reason']): void;
24
+ private _syncHeading;
25
+ private _syncOpen;
26
+ private _onCancel;
27
+ private _onDialogClick;
28
+ private _onNativeClose;
29
+ }
30
+ export declare function defineDialogComponents(): void;