@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/dist/tabs.js ADDED
@@ -0,0 +1,161 @@
1
+ export class GETabs extends HTMLElement {
2
+ _style = document.createElement('style');
3
+ _tabs = document.createElement('div');
4
+ _panels = document.createElement('div');
5
+ _options = [];
6
+ static get observedAttributes() {
7
+ return ['options', 'value'];
8
+ }
9
+ constructor() {
10
+ super();
11
+ const root = this.attachShadow({ mode: 'open' });
12
+ this._style.textContent = `
13
+ :host {
14
+ box-sizing: border-box;
15
+ display: grid;
16
+ grid-template-rows: 32px minmax(0, 1fr);
17
+ width: 100%;
18
+ height: 100%;
19
+ min-width: 0;
20
+ min-height: 0;
21
+ color: var(--ge-text-color, #d8e2f2);
22
+ background: var(--ge-panel-bg-color, #171d28);
23
+ font: 12px system-ui, sans-serif;
24
+ }
25
+ .tabs {
26
+ display: flex;
27
+ align-items: end;
28
+ gap: 2px;
29
+ min-width: 0;
30
+ padding: 4px 6px 0;
31
+ border-bottom: 1px solid var(--ge-border-color, #303746);
32
+ background: var(--ge-tabs-bar-bg-color, var(--ge-menu-bg-color, var(--ge-surface-elevated-color, #1b2230)));
33
+ }
34
+ button {
35
+ height: 28px;
36
+ min-width: 0;
37
+ padding: 0 10px;
38
+ border: 1px solid transparent;
39
+ border-bottom: 0;
40
+ border-radius: 4px 4px 0 0;
41
+ color: var(--ge-secondary-text-color, #8fa7c8);
42
+ background: transparent;
43
+ font: inherit;
44
+ cursor: pointer;
45
+ }
46
+ button:hover {
47
+ color: var(--ge-text-color, #d8e2f2);
48
+ background: var(--ge-hover-bg-color, #222b3a);
49
+ }
50
+ button.active {
51
+ color: var(--ge-active-text-color, #eef3ff);
52
+ border-color: var(--ge-border-color, #303746);
53
+ background: var(--ge-panel-bg-color, #171d28);
54
+ }
55
+ button:disabled {
56
+ opacity: 0.45;
57
+ cursor: default;
58
+ }
59
+ .panels {
60
+ min-width: 0;
61
+ min-height: 0;
62
+ overflow: auto;
63
+ }
64
+ ::slotted(*) {
65
+ box-sizing: border-box;
66
+ }
67
+ `;
68
+ this._tabs.className = 'tabs';
69
+ this._panels.className = 'panels';
70
+ root.append(this._style, this._tabs, this._panels);
71
+ }
72
+ connectedCallback() {
73
+ this._syncFromAttributes();
74
+ this._render();
75
+ }
76
+ attributeChangedCallback(name, _oldValue, newValue) {
77
+ if (name === 'options')
78
+ this._readOptionsAttribute();
79
+ if (name === 'value' && newValue === null && this._options[0]) {
80
+ this.setAttribute('value', this._options[0].value);
81
+ return;
82
+ }
83
+ this._render();
84
+ }
85
+ get options() {
86
+ return this._options;
87
+ }
88
+ set options(value) {
89
+ this._options = Array.isArray(value) ? value : [];
90
+ if (!this._options.some(item => item.value === this.value)) {
91
+ this.value = this._options[0]?.value ?? '';
92
+ }
93
+ this._render();
94
+ }
95
+ get value() {
96
+ return this.getAttribute('value') ?? '';
97
+ }
98
+ set value(value) {
99
+ this.setAttribute('value', value);
100
+ this._render();
101
+ }
102
+ _syncFromAttributes() {
103
+ this._readOptionsAttribute();
104
+ if (!this.value && this._options[0])
105
+ this.setAttribute('value', this._options[0].value);
106
+ }
107
+ _readOptionsAttribute() {
108
+ const raw = this.getAttribute('options');
109
+ if (!raw) {
110
+ this._options = [];
111
+ return;
112
+ }
113
+ try {
114
+ const parsed = JSON.parse(raw);
115
+ this._options = Array.isArray(parsed)
116
+ ? parsed.map(item => ({
117
+ label: String(item.label ?? item.value ?? ''),
118
+ value: String(item.value ?? item.label ?? ''),
119
+ disabled: Boolean(item.disabled),
120
+ }))
121
+ : [];
122
+ }
123
+ catch (error) {
124
+ console.warn('Invalid ge-tabs options attribute.', error);
125
+ }
126
+ }
127
+ _render() {
128
+ this._tabs.replaceChildren();
129
+ this._panels.replaceChildren();
130
+ const value = this.value || this._options[0]?.value || '';
131
+ for (const option of this._options) {
132
+ const button = document.createElement('button');
133
+ button.type = 'button';
134
+ button.textContent = option.label;
135
+ button.disabled = Boolean(option.disabled);
136
+ button.classList.toggle('active', option.value === value);
137
+ button.addEventListener('click', () => this._select(option.value));
138
+ this._tabs.append(button);
139
+ }
140
+ const slot = document.createElement('slot');
141
+ slot.name = value;
142
+ this._panels.append(slot);
143
+ }
144
+ _select(value) {
145
+ if (value === this.value)
146
+ return;
147
+ this.setAttribute('value', value);
148
+ const option = this._options.find(item => item.value === value) ?? null;
149
+ this.dispatchEvent(new CustomEvent('tab-change', {
150
+ detail: { value, option },
151
+ bubbles: true,
152
+ composed: true,
153
+ }));
154
+ }
155
+ }
156
+ export function defineTabsComponents() {
157
+ if (!customElements.get('ge-tabs')) {
158
+ customElements.define('ge-tabs', GETabs);
159
+ }
160
+ }
161
+ //# sourceMappingURL=tabs.js.map
@@ -0,0 +1,37 @@
1
+ export type GETooltipPlacement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight' | 'left' | 'leftTop' | 'leftBottom' | 'right' | 'rightTop' | 'rightBottom';
2
+ export declare class GETooltip extends HTMLElement {
3
+ private readonly _root;
4
+ private readonly _style;
5
+ private readonly _slot;
6
+ private readonly _tooltip;
7
+ private readonly _content;
8
+ private readonly _arrow;
9
+ private _showTimer;
10
+ private _visible;
11
+ static get observedAttributes(): string[];
12
+ constructor();
13
+ connectedCallback(): void;
14
+ disconnectedCallback(): void;
15
+ attributeChangedCallback(name: string): void;
16
+ get label(): string;
17
+ set label(value: string);
18
+ get placement(): GETooltipPlacement;
19
+ set placement(value: GETooltipPlacement);
20
+ get delay(): number;
21
+ set delay(value: number);
22
+ get arrow(): boolean;
23
+ set arrow(value: boolean);
24
+ private _onEnter;
25
+ private _onLeave;
26
+ private _onKeyDown;
27
+ private _show;
28
+ private _hide;
29
+ private _onViewportChange;
30
+ private _syncLabel;
31
+ private _syncArrow;
32
+ private _syncPosition;
33
+ private _syncArrowPosition;
34
+ private _measureTooltip;
35
+ private _clearShowTimer;
36
+ }
37
+ export declare function defineTooltipComponents(): void;
@@ -0,0 +1,286 @@
1
+ const DEFAULT_DELAY = 1000;
2
+ const VIEWPORT_PADDING = 8;
3
+ const TRIGGER_GAP = 8;
4
+ const ARROW_SIZE = 8;
5
+ const ARROW_OFFSET = 16;
6
+ const TOOLTIP_PLACEMENTS = new Set([
7
+ 'top',
8
+ 'topLeft',
9
+ 'topRight',
10
+ 'bottom',
11
+ 'bottomLeft',
12
+ 'bottomRight',
13
+ 'left',
14
+ 'leftTop',
15
+ 'leftBottom',
16
+ 'right',
17
+ 'rightTop',
18
+ 'rightBottom',
19
+ ]);
20
+ export class GETooltip extends HTMLElement {
21
+ _root;
22
+ _style = document.createElement('style');
23
+ _slot = document.createElement('slot');
24
+ _tooltip = document.createElement('div');
25
+ _content = document.createElement('div');
26
+ _arrow = document.createElement('span');
27
+ _showTimer = null;
28
+ _visible = false;
29
+ static get observedAttributes() {
30
+ return ['label', 'placement', 'delay', 'disabled', 'arrow', 'no-arrow'];
31
+ }
32
+ constructor() {
33
+ super();
34
+ this._root = this.attachShadow({ mode: 'open' });
35
+ this._style.textContent = `
36
+ :host {
37
+ display: inline-block;
38
+ width: max-content;
39
+ min-width: 0;
40
+ }
41
+ .tooltip {
42
+ position: fixed;
43
+ z-index: 10001;
44
+ box-sizing: border-box;
45
+ max-width: min(280px, calc(100vw - 16px));
46
+ padding: 5px 8px;
47
+ border-radius: 4px;
48
+ color: var(--ge-tooltip-text-color, #f4f7fb);
49
+ background: var(--ge-tooltip-bg-color, #0f141c);
50
+ border: 1px solid var(--ge-tooltip-border-color, rgba(255, 255, 255, 0.08));
51
+ box-shadow: 0 8px 24px var(--ge-tooltip-shadow-color, rgba(0, 0, 0, 0.35));
52
+ font: 12px system-ui, sans-serif;
53
+ line-height: 1.35;
54
+ width: max-content;
55
+ white-space: normal;
56
+ overflow-wrap: anywhere;
57
+ pointer-events: none;
58
+ opacity: 0;
59
+ transform: translate3d(0, -2px, 0);
60
+ transition: opacity 0.12s ease, transform 0.12s ease;
61
+ }
62
+ .arrow {
63
+ position: absolute;
64
+ width: ${ARROW_SIZE}px;
65
+ height: ${ARROW_SIZE}px;
66
+ background: var(--ge-tooltip-bg-color, #0f141c);
67
+ border: solid var(--ge-tooltip-border-color, rgba(255, 255, 255, 0.08));
68
+ border-width: 0 1px 1px 0;
69
+ transform: rotate(45deg);
70
+ }
71
+ .tooltip.no-arrow .arrow {
72
+ display: none;
73
+ }
74
+ .tooltip.visible {
75
+ opacity: 1;
76
+ transform: translate3d(0, 0, 0);
77
+ }
78
+ `;
79
+ this._tooltip.className = 'tooltip';
80
+ this._tooltip.setAttribute('role', 'tooltip');
81
+ this._content.className = 'content';
82
+ this._arrow.className = 'arrow';
83
+ this._tooltip.append(this._content, this._arrow);
84
+ this._root.append(this._style, this._slot, this._tooltip);
85
+ }
86
+ connectedCallback() {
87
+ this.addEventListener('pointerenter', this._onEnter);
88
+ this.addEventListener('pointerleave', this._onLeave);
89
+ this.addEventListener('focusin', this._onEnter);
90
+ this.addEventListener('focusout', this._onLeave);
91
+ this.addEventListener('keydown', this._onKeyDown);
92
+ this._syncLabel();
93
+ }
94
+ disconnectedCallback() {
95
+ this.removeEventListener('pointerenter', this._onEnter);
96
+ this.removeEventListener('pointerleave', this._onLeave);
97
+ this.removeEventListener('focusin', this._onEnter);
98
+ this.removeEventListener('focusout', this._onLeave);
99
+ this.removeEventListener('keydown', this._onKeyDown);
100
+ this._clearShowTimer();
101
+ this._hide();
102
+ }
103
+ attributeChangedCallback(name) {
104
+ if (name === 'label')
105
+ this._syncLabel();
106
+ if (name === 'arrow' || name === 'no-arrow')
107
+ this._syncArrow();
108
+ if ((name === 'placement' || name === 'disabled' || name === 'arrow' || name === 'no-arrow') && this._visible) {
109
+ this._syncPosition();
110
+ }
111
+ }
112
+ get label() {
113
+ return this.getAttribute('label') ?? '';
114
+ }
115
+ set label(value) {
116
+ if (value)
117
+ this.setAttribute('label', value);
118
+ else
119
+ this.removeAttribute('label');
120
+ }
121
+ get placement() {
122
+ return normalizePlacement(this.getAttribute('placement'));
123
+ }
124
+ set placement(value) {
125
+ this.setAttribute('placement', value);
126
+ }
127
+ get delay() {
128
+ const value = Number(this.getAttribute('delay'));
129
+ return Number.isFinite(value) && value >= 0 ? value : DEFAULT_DELAY;
130
+ }
131
+ set delay(value) {
132
+ this.setAttribute('delay', String(Math.max(0, value)));
133
+ }
134
+ get arrow() {
135
+ if (this.hasAttribute('no-arrow'))
136
+ return false;
137
+ const value = this.getAttribute('arrow');
138
+ return value === null || value !== 'false';
139
+ }
140
+ set arrow(value) {
141
+ if (value) {
142
+ this.removeAttribute('no-arrow');
143
+ this.setAttribute('arrow', '');
144
+ }
145
+ else {
146
+ this.setAttribute('arrow', 'false');
147
+ }
148
+ }
149
+ _onEnter = () => {
150
+ if (this.hasAttribute('disabled') || !this.label)
151
+ return;
152
+ this._clearShowTimer();
153
+ this._showTimer = window.setTimeout(() => this._show(), this.delay);
154
+ };
155
+ _onLeave = () => {
156
+ this._clearShowTimer();
157
+ this._hide();
158
+ };
159
+ _onKeyDown = (event) => {
160
+ if (event.key === 'Escape')
161
+ this._hide();
162
+ };
163
+ _show() {
164
+ this._showTimer = null;
165
+ if (!this.isConnected || this.hasAttribute('disabled') || !this.label)
166
+ return;
167
+ this._visible = true;
168
+ this._syncArrow();
169
+ this._syncPosition();
170
+ this._tooltip.classList.add('visible');
171
+ window.addEventListener('scroll', this._onViewportChange, { capture: true, passive: true });
172
+ window.addEventListener('resize', this._onViewportChange);
173
+ }
174
+ _hide() {
175
+ if (!this._visible)
176
+ return;
177
+ this._visible = false;
178
+ this._tooltip.classList.remove('visible');
179
+ window.removeEventListener('scroll', this._onViewportChange, { capture: true });
180
+ window.removeEventListener('resize', this._onViewportChange);
181
+ }
182
+ _onViewportChange = () => {
183
+ if (this._visible)
184
+ this._syncPosition();
185
+ };
186
+ _syncLabel() {
187
+ this._content.textContent = this.label;
188
+ }
189
+ _syncArrow() {
190
+ this._tooltip.classList.toggle('no-arrow', !this.arrow);
191
+ }
192
+ _syncPosition() {
193
+ const hostRect = this.getBoundingClientRect();
194
+ const tooltipRect = this._measureTooltip();
195
+ const placement = this.placement;
196
+ let top = getAlignedTop(hostRect, tooltipRect, placement);
197
+ let left = getAlignedLeft(hostRect, tooltipRect, placement);
198
+ if (placement.startsWith('top'))
199
+ top = hostRect.top - tooltipRect.height - TRIGGER_GAP;
200
+ else if (placement.startsWith('bottom'))
201
+ top = hostRect.bottom + TRIGGER_GAP;
202
+ else if (placement.startsWith('left'))
203
+ left = hostRect.left - tooltipRect.width - TRIGGER_GAP;
204
+ else if (placement.startsWith('right'))
205
+ left = hostRect.right + TRIGGER_GAP;
206
+ top = clamp(top, VIEWPORT_PADDING, window.innerHeight - tooltipRect.height - VIEWPORT_PADDING);
207
+ left = clamp(left, VIEWPORT_PADDING, window.innerWidth - tooltipRect.width - VIEWPORT_PADDING);
208
+ this._tooltip.style.top = `${top}px`;
209
+ this._tooltip.style.left = `${left}px`;
210
+ this._syncArrowPosition(hostRect, tooltipRect, top, left, placement);
211
+ }
212
+ _syncArrowPosition(hostRect, tooltipRect, top, left, placement) {
213
+ const x = clamp(hostRect.left + hostRect.width / 2 - left - ARROW_SIZE / 2, ARROW_OFFSET, tooltipRect.width - ARROW_OFFSET - ARROW_SIZE);
214
+ const y = clamp(hostRect.top + hostRect.height / 2 - top - ARROW_SIZE / 2, ARROW_OFFSET, tooltipRect.height - ARROW_OFFSET - ARROW_SIZE);
215
+ this._arrow.style.inset = 'auto';
216
+ if (placement.startsWith('top')) {
217
+ this._arrow.style.left = `${x}px`;
218
+ this._arrow.style.bottom = `${-ARROW_SIZE / 2}px`;
219
+ this._arrow.style.transform = 'rotate(45deg)';
220
+ return;
221
+ }
222
+ if (placement.startsWith('bottom')) {
223
+ this._arrow.style.left = `${x}px`;
224
+ this._arrow.style.top = `${-ARROW_SIZE / 2}px`;
225
+ this._arrow.style.transform = 'rotate(225deg)';
226
+ return;
227
+ }
228
+ if (placement.startsWith('left')) {
229
+ this._arrow.style.top = `${y}px`;
230
+ this._arrow.style.right = `${-ARROW_SIZE / 2}px`;
231
+ this._arrow.style.transform = 'rotate(315deg)';
232
+ return;
233
+ }
234
+ this._arrow.style.top = `${y}px`;
235
+ this._arrow.style.left = `${-ARROW_SIZE / 2}px`;
236
+ this._arrow.style.transform = 'rotate(135deg)';
237
+ }
238
+ _measureTooltip() {
239
+ const previousVisibility = this._tooltip.style.visibility;
240
+ const previousOpacity = this._tooltip.style.opacity;
241
+ this._tooltip.style.visibility = 'hidden';
242
+ this._tooltip.style.opacity = '0';
243
+ this._tooltip.classList.add('visible');
244
+ const rect = this._tooltip.getBoundingClientRect();
245
+ this._tooltip.classList.toggle('visible', this._visible);
246
+ this._tooltip.style.visibility = previousVisibility;
247
+ this._tooltip.style.opacity = previousOpacity;
248
+ return rect;
249
+ }
250
+ _clearShowTimer() {
251
+ if (this._showTimer === null)
252
+ return;
253
+ window.clearTimeout(this._showTimer);
254
+ this._showTimer = null;
255
+ }
256
+ }
257
+ function clamp(value, min, max) {
258
+ if (max < min)
259
+ return min;
260
+ return Math.min(Math.max(value, min), max);
261
+ }
262
+ function normalizePlacement(value) {
263
+ return value && TOOLTIP_PLACEMENTS.has(value)
264
+ ? value
265
+ : 'top';
266
+ }
267
+ function getAlignedTop(hostRect, tooltipRect, placement) {
268
+ if (placement.endsWith('Top'))
269
+ return hostRect.top;
270
+ if (placement.endsWith('Bottom'))
271
+ return hostRect.bottom - tooltipRect.height;
272
+ return hostRect.top + (hostRect.height - tooltipRect.height) / 2;
273
+ }
274
+ function getAlignedLeft(hostRect, tooltipRect, placement) {
275
+ if (placement.endsWith('Left'))
276
+ return hostRect.left;
277
+ if (placement.endsWith('Right'))
278
+ return hostRect.right - tooltipRect.width;
279
+ return hostRect.left + (hostRect.width - tooltipRect.width) / 2;
280
+ }
281
+ export function defineTooltipComponents() {
282
+ if (!customElements.get('ge-tooltip')) {
283
+ customElements.define('ge-tooltip', GETooltip);
284
+ }
285
+ }
286
+ //# sourceMappingURL=tooltip.js.map
@@ -0,0 +1,35 @@
1
+ import type { FlatTreeNode, GETreeDropPosition, GETreeNodeData, IndexedTreeNode } from './tree-types.js';
2
+ /** Mutable hierarchy model owned by ge-tree. It has no DOM dependency and is safe to unit test. */
3
+ export declare class GETreeModel {
4
+ private _data;
5
+ private readonly _expandedIds;
6
+ private readonly _nodeIndex;
7
+ private readonly _visibleNodes;
8
+ private readonly _visibleIds;
9
+ private readonly _visibleIndex;
10
+ private _indexDirty;
11
+ private _visibleDirty;
12
+ constructor(expandedIds: Set<string>);
13
+ get index(): ReadonlyMap<string, IndexedTreeNode>;
14
+ get visibleIndex(): ReadonlyMap<string, number>;
15
+ get isVisibilityDirty(): boolean;
16
+ setData(data: GETreeNodeData[]): void;
17
+ invalidateVisibility(): void;
18
+ getVisibleNodes(): readonly FlatTreeNode[];
19
+ getVisibleIds(): readonly string[];
20
+ getIndexedNode(id: string): IndexedTreeNode | null;
21
+ findNode(id: string): {
22
+ node: GETreeNodeData;
23
+ parent: GETreeNodeData | null;
24
+ } | null;
25
+ topLevelSelection(ids: Iterable<string>): string[];
26
+ removeNode(id: string): boolean;
27
+ moveNode(sourceId: string, targetId: string, position: GETreeDropPosition): boolean;
28
+ private _refreshSiblingIndexes;
29
+ private _refreshSubtreeIndex;
30
+ private _moveVisibleSubtree;
31
+ private _collectVisibleSubtree;
32
+ private _visibleSubtreeEnd;
33
+ private _refreshVisibleIndexes;
34
+ private _ensureIndex;
35
+ }