@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +1 -1
  3. package/README.md +226 -2
  4. package/dist/border-beam.d.ts +26 -0
  5. package/dist/border-beam.js +174 -0
  6. package/dist/button.d.ts +10 -0
  7. package/dist/button.js +85 -0
  8. package/dist/checkbox.d.ts +2 -2
  9. package/dist/checkbox.js +11 -11
  10. package/dist/context-menu.d.ts +6 -6
  11. package/dist/context-menu.js +11 -9
  12. package/dist/dialog.d.ts +5 -5
  13. package/dist/dialog.js +18 -18
  14. package/dist/drawer.d.ts +49 -0
  15. package/dist/drawer.js +377 -0
  16. package/dist/dropdown.d.ts +9 -9
  17. package/dist/dropdown.js +9 -9
  18. package/dist/expandable.d.ts +32 -0
  19. package/dist/expandable.js +136 -0
  20. package/dist/history-controls.d.ts +40 -0
  21. package/dist/history-controls.js +123 -0
  22. package/dist/index.d.ts +36 -31
  23. package/dist/index.js +35 -85
  24. package/dist/input.d.ts +5 -5
  25. package/dist/input.js +13 -13
  26. package/dist/menu-shared.d.ts +4 -4
  27. package/dist/menu-shared.js +4 -4
  28. package/dist/notification.d.ts +55 -0
  29. package/dist/notification.js +357 -0
  30. package/dist/radio.d.ts +2 -2
  31. package/dist/radio.js +12 -12
  32. package/dist/range.d.ts +74 -0
  33. package/dist/range.js +393 -0
  34. package/dist/select.d.ts +6 -6
  35. package/dist/select.js +20 -20
  36. package/dist/split.d.ts +6 -6
  37. package/dist/split.js +14 -14
  38. package/dist/tabs.d.ts +10 -6
  39. package/dist/tabs.js +94 -30
  40. package/dist/tooltip.d.ts +4 -4
  41. package/dist/tooltip.js +9 -9
  42. package/dist/tree-model.d.ts +15 -7
  43. package/dist/tree-model.js +93 -80
  44. package/dist/tree-node.d.ts +3 -3
  45. package/dist/tree-node.js +3 -3
  46. package/dist/tree-types.d.ts +14 -14
  47. package/dist/tree.d.ts +9 -7
  48. package/dist/tree.js +68 -66
  49. package/dist/virtual-list.d.ts +61 -0
  50. package/dist/virtual-list.js +324 -0
  51. package/package.json +100 -7
  52. package/themes/haiyue-dark.css +46 -0
  53. package/themes/haiyue-light.css +46 -0
  54. package/src/checkbox.ts +0 -215
  55. package/src/context-menu.ts +0 -248
  56. package/src/dialog.ts +0 -195
  57. package/src/dropdown.ts +0 -249
  58. package/src/index.ts +0 -192
  59. package/src/input.ts +0 -150
  60. package/src/menu-shared.ts +0 -65
  61. package/src/radio.ts +0 -216
  62. package/src/select.ts +0 -271
  63. package/src/split.ts +0 -306
  64. package/src/tabs.ts +0 -182
  65. package/src/tooltip.ts +0 -321
  66. package/src/tree-model.ts +0 -305
  67. package/src/tree-node.ts +0 -67
  68. package/src/tree-types.ts +0 -47
  69. package/src/tree.ts +0 -1002
package/src/dropdown.ts DELETED
@@ -1,249 +0,0 @@
1
- import { GE_MENU_ITEM_STYLES, createMenuItemButton, createMenuSeparator } from './menu-shared.js';
2
-
3
- export interface GEDropdownItem {
4
- label?: string;
5
- value?: string;
6
- disabled?: boolean;
7
- separator?: boolean;
8
- }
9
-
10
- export interface GEDropdownSelectDetail {
11
- value: string;
12
- item: GEDropdownItem;
13
- }
14
-
15
- export type GEDropdownPlacement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
16
-
17
- export class GEDropdown extends HTMLElement {
18
- private readonly _root: ShadowRoot;
19
- private readonly _style = document.createElement('style');
20
- private readonly _triggerSlot = document.createElement('slot');
21
- private readonly _menu = document.createElement('div');
22
- private _items: GEDropdownItem[] = [];
23
- private _documentListenerAbort: AbortController | null = null;
24
-
25
- static get observedAttributes(): string[] {
26
- return ['items', 'open', 'disabled', 'placement'];
27
- }
28
-
29
- constructor() {
30
- super();
31
- this._root = this.attachShadow({ mode: 'open' });
32
- this._style.textContent = `
33
- :host {
34
- display: inline-block;
35
- width: max-content;
36
- min-width: 0;
37
- color: var(--ge-text-color, #d8e2f2);
38
- font: 12px system-ui, sans-serif;
39
- }
40
- :host([disabled]) {
41
- pointer-events: none;
42
- opacity: 0.55;
43
- }
44
- .menu {
45
- position: fixed;
46
- z-index: 10000;
47
- box-sizing: border-box;
48
- min-width: 148px;
49
- max-height: min(320px, calc(100vh - 12px));
50
- overflow: auto;
51
- display: none;
52
- padding: 4px;
53
- border: 1px solid var(--ge-border-color, #303746);
54
- border-radius: 4px;
55
- color: var(--ge-text-color, #d8e2f2);
56
- background: var(--ge-menu-bg-color, var(--ge-surface-elevated-color, #1b2230));
57
- box-shadow: 0 10px 26px var(--ge-menu-shadow-color, rgba(0, 0, 0, 0.36));
58
- }
59
- :host([open]) .menu {
60
- display: block;
61
- }
62
- ${GE_MENU_ITEM_STYLES}
63
- `;
64
- this._triggerSlot.name = 'trigger';
65
- this._menu.className = 'menu';
66
- this._root.append(this._style, this._triggerSlot, this._menu);
67
- }
68
-
69
- connectedCallback(): void {
70
- this._readItemsAttribute();
71
- this._render();
72
- this.addEventListener('click', this._onHostClick);
73
- this._syncDocumentListeners();
74
- }
75
-
76
- disconnectedCallback(): void {
77
- this.removeEventListener('click', this._onHostClick);
78
- this._detachDocumentListeners();
79
- }
80
-
81
- attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void {
82
- if (name === 'items') {
83
- this._readItemsAttribute();
84
- this._render();
85
- }
86
- if (name === 'open') {
87
- this._syncDocumentListeners();
88
- if (newValue !== null) this._syncPosition();
89
- }
90
- if (name === 'placement' && this.open) this._syncPosition();
91
- }
92
-
93
- get items(): GEDropdownItem[] {
94
- return this._items;
95
- }
96
-
97
- set items(value: GEDropdownItem[]) {
98
- this._items = Array.isArray(value) ? value : [];
99
- this._render();
100
- }
101
-
102
- get open(): boolean {
103
- return this.hasAttribute('open');
104
- }
105
-
106
- set open(value: boolean) {
107
- if (value) this.setAttribute('open', '');
108
- else this.removeAttribute('open');
109
- }
110
-
111
- get disabled(): boolean {
112
- return this.hasAttribute('disabled');
113
- }
114
-
115
- set disabled(value: boolean) {
116
- if (value) this.setAttribute('disabled', '');
117
- else this.removeAttribute('disabled');
118
- }
119
-
120
- get placement(): GEDropdownPlacement {
121
- return (this.getAttribute('placement') as GEDropdownPlacement | null) ?? 'bottom-start';
122
- }
123
-
124
- set placement(value: GEDropdownPlacement) {
125
- this.setAttribute('placement', value);
126
- }
127
-
128
- show(): void {
129
- if (this.disabled) return;
130
- this.open = true;
131
- this._syncPosition();
132
- }
133
-
134
- close(): void {
135
- this.open = false;
136
- }
137
-
138
- toggle(): void {
139
- if (this.open) this.close();
140
- else this.show();
141
- }
142
-
143
- private _readItemsAttribute(): void {
144
- const raw = this.getAttribute('items');
145
- if (!raw) {
146
- this._items = [];
147
- return;
148
- }
149
- try {
150
- const parsed = JSON.parse(raw);
151
- this._items = Array.isArray(parsed)
152
- ? parsed.map(item => ({
153
- ...(item.separator ? {} : {
154
- label: String(item.label ?? item.value ?? ''),
155
- value: String(item.value ?? item.label ?? ''),
156
- }),
157
- disabled: Boolean(item.disabled),
158
- separator: Boolean(item.separator),
159
- }))
160
- : [];
161
- } catch (error) {
162
- console.warn('Invalid ge-dropdown items attribute.', error);
163
- }
164
- }
165
-
166
- private _render(): void {
167
- this._menu.replaceChildren();
168
- for (const item of this._items) {
169
- if (item.separator) {
170
- this._menu.append(createMenuSeparator());
171
- continue;
172
- }
173
- const button = createMenuItemButton(item);
174
- button.addEventListener('click', (event) => {
175
- event.stopPropagation();
176
- if (!item.value || item.disabled) return;
177
- this.dispatchEvent(new CustomEvent<GEDropdownSelectDetail>('item-select', {
178
- detail: { value: item.value, item },
179
- bubbles: true,
180
- composed: true,
181
- }));
182
- this.close();
183
- });
184
- this._menu.append(button);
185
- }
186
- if (this.open) this._syncPosition();
187
- }
188
-
189
- private _syncPosition(): void {
190
- if (!this.open) return;
191
- const hostRect = this.getBoundingClientRect();
192
- this._menu.style.left = '0px';
193
- this._menu.style.top = '0px';
194
- this._menu.style.minWidth = `${Math.max(148, hostRect.width)}px`;
195
- const menuRect = this._menu.getBoundingClientRect();
196
- const placement = this.placement;
197
- const left = placement.endsWith('end') ? hostRect.right - menuRect.width : hostRect.left;
198
- const top = placement.startsWith('top') ? hostRect.top - menuRect.height - 4 : hostRect.bottom + 4;
199
- const viewport = getVisualViewportRect();
200
- this._menu.style.left = `${Math.max(viewport.left + 4, Math.min(left, viewport.left + viewport.width - menuRect.width - 4))}px`;
201
- this._menu.style.top = `${Math.max(viewport.top + 4, Math.min(top, viewport.top + viewport.height - menuRect.height - 4))}px`;
202
- }
203
-
204
- private _syncDocumentListeners(): void {
205
- if (this.open && this.isConnected) this._attachDocumentListeners();
206
- else this._detachDocumentListeners();
207
- }
208
-
209
- private _attachDocumentListeners(): void {
210
- if (this._documentListenerAbort) return;
211
- this._documentListenerAbort = new AbortController();
212
- const { signal } = this._documentListenerAbort;
213
- document.addEventListener('pointerdown', this._onDocumentPointerDown, { capture: true, signal });
214
- document.addEventListener('keydown', this._onDocumentKeyDown, { capture: true, signal });
215
- }
216
-
217
- private _detachDocumentListeners(): void {
218
- this._documentListenerAbort?.abort();
219
- this._documentListenerAbort = null;
220
- }
221
-
222
- private _onHostClick = (event: MouseEvent): void => {
223
- if (event.composedPath().includes(this._menu)) return;
224
- this.toggle();
225
- };
226
-
227
- private _onDocumentPointerDown = (event: PointerEvent): void => {
228
- if (!this.open) return;
229
- const path = event.composedPath();
230
- if (!path.includes(this)) this.close();
231
- };
232
-
233
- private _onDocumentKeyDown = (event: KeyboardEvent): void => {
234
- if (event.key === 'Escape') this.close();
235
- };
236
- }
237
-
238
- function getVisualViewportRect(): { left: number; top: number; width: number; height: number } {
239
- const viewport = window.visualViewport;
240
- return viewport
241
- ? { left: viewport.offsetLeft, top: viewport.offsetTop, width: viewport.width, height: viewport.height }
242
- : { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight };
243
- }
244
-
245
- export function defineDropdownComponents(): void {
246
- if (!customElements.get('ge-dropdown')) {
247
- customElements.define('ge-dropdown', GEDropdown);
248
- }
249
- }
package/src/index.ts DELETED
@@ -1,192 +0,0 @@
1
- import { defineTreeComponents } from './tree.js';
2
- import { defineSplitComponents } from './split.js';
3
- import { defineSelectComponents } from './select.js';
4
- import { defineTabsComponents } from './tabs.js';
5
- import { defineContextMenuComponents } from './context-menu.js';
6
- import { defineDropdownComponents } from './dropdown.js';
7
- import { defineTooltipComponents } from './tooltip.js';
8
- import { defineCheckboxComponents } from './checkbox.js';
9
- import { defineRadioComponents } from './radio.js';
10
- import { defineDialogComponents } from './dialog.js';
11
- import { defineInputComponents } from './input.js';
12
-
13
- export {
14
- defineTreeComponents,
15
- GETree,
16
- GETreeNode,
17
- } from './tree.js';
18
- export {
19
- defineSplitComponents,
20
- GESplit,
21
- } from './split.js';
22
- export {
23
- defineSelectComponents,
24
- GESelect,
25
- } from './select.js';
26
- export {
27
- defineTabsComponents,
28
- GETabs,
29
- } from './tabs.js';
30
- export {
31
- defineContextMenuComponents,
32
- GEContextMenu,
33
- } from './context-menu.js';
34
- export {
35
- defineDropdownComponents,
36
- GEDropdown,
37
- } from './dropdown.js';
38
- export {
39
- defineTooltipComponents,
40
- GETooltip,
41
- } from './tooltip.js';
42
- export {
43
- defineCheckboxComponents,
44
- GECheckbox,
45
- } from './checkbox.js';
46
- export {
47
- defineRadioComponents,
48
- GERadio,
49
- } from './radio.js';
50
- export {
51
- defineDialogComponents,
52
- GEDialog,
53
- } from './dialog.js';
54
- export {
55
- defineInputComponents,
56
- GEInput,
57
- } from './input.js';
58
- export type {
59
- GESplitDirection,
60
- GESplitRatioChangeDetail,
61
- } from './split.js';
62
- export type {
63
- GESelectChangeDetail,
64
- GESelectOption,
65
- } from './select.js';
66
- export type {
67
- GETabChangeDetail,
68
- GETabOption,
69
- } from './tabs.js';
70
- export type {
71
- GEContextMenuItem,
72
- GEContextMenuSelectDetail,
73
- } from './context-menu.js';
74
- export type {
75
- GEDropdownItem,
76
- GEDropdownPlacement,
77
- GEDropdownSelectDetail,
78
- } from './dropdown.js';
79
- export type {
80
- GETooltipPlacement,
81
- } from './tooltip.js';
82
- export type {
83
- GECheckboxChangeDetail,
84
- } from './checkbox.js';
85
- export type {
86
- GERadioChangeDetail,
87
- } from './radio.js';
88
- export type {
89
- GEDialogCloseDetail,
90
- GEDialogCloseReason,
91
- } from './dialog.js';
92
- export type {
93
- GEInputChangeDetail,
94
- GEInputType,
95
- } from './input.js';
96
- export type {
97
- GETreeDataChangeDetail,
98
- GETreeDropPosition,
99
- GETreeNodeContextMenuDetail,
100
- GETreeNodeData,
101
- GETreeSelectionChangeDetail,
102
- } from './tree.js';
103
-
104
- export class GEButton extends HTMLElement {
105
- private readonly _button = document.createElement('button');
106
- private readonly _slot = document.createElement('slot');
107
-
108
- static get observedAttributes(): string[] {
109
- return ['label', 'disabled', 'aria-label', 'title', 'icon-only'];
110
- }
111
-
112
- constructor() {
113
- super();
114
- const root = this.attachShadow({ mode: 'open' });
115
- const style = document.createElement('style');
116
- style.textContent = `
117
- :host { display: inline-block; }
118
- button {
119
- min-height: 28px;
120
- padding: 0 10px;
121
- border: 1px solid #3d4654;
122
- border-radius: 4px;
123
- background: #202733;
124
- color: #eef3ff;
125
- font: 12px system-ui, sans-serif;
126
- cursor: pointer;
127
- }
128
- button.icon-only {
129
- width: 28px;
130
- padding: 0;
131
- display: inline-grid;
132
- place-items: center;
133
- }
134
- ::slotted(svg) {
135
- width: 16px;
136
- height: 16px;
137
- display: block;
138
- fill: none;
139
- stroke: currentColor;
140
- stroke-width: 2;
141
- stroke-linecap: round;
142
- stroke-linejoin: round;
143
- }
144
- button:disabled {
145
- opacity: 0.5;
146
- cursor: default;
147
- }
148
- `;
149
- root.append(style, this._button);
150
- }
151
-
152
- connectedCallback(): void {
153
- this._sync();
154
- }
155
-
156
- attributeChangedCallback(): void {
157
- this._sync();
158
- }
159
-
160
- private _sync(): void {
161
- const label = this.getAttribute('label');
162
- if (label !== null) {
163
- this._button.textContent = label;
164
- this._button.classList.remove('icon-only');
165
- this._button.removeAttribute('aria-label');
166
- } else {
167
- if (this._button.firstChild !== this._slot) this._button.replaceChildren(this._slot);
168
- this._button.classList.toggle('icon-only', this.hasAttribute('icon-only'));
169
- const accessibleLabel = this.getAttribute('aria-label') ?? this.getAttribute('title') ?? '';
170
- if (accessibleLabel) this._button.setAttribute('aria-label', accessibleLabel);
171
- else this._button.removeAttribute('aria-label');
172
- }
173
- this._button.disabled = this.hasAttribute('disabled');
174
- }
175
- }
176
-
177
- export function defineHaiyueUI(): void {
178
- if (!customElements.get('ge-button')) {
179
- customElements.define('ge-button', GEButton);
180
- }
181
- defineTreeComponents();
182
- defineSplitComponents();
183
- defineSelectComponents();
184
- defineTabsComponents();
185
- defineContextMenuComponents();
186
- defineDropdownComponents();
187
- defineTooltipComponents();
188
- defineCheckboxComponents();
189
- defineRadioComponents();
190
- defineDialogComponents();
191
- defineInputComponents();
192
- }
package/src/input.ts DELETED
@@ -1,150 +0,0 @@
1
- export type GEInputType = 'text' | 'number' | 'color';
2
-
3
- export interface GEInputChangeDetail {
4
- value: string;
5
- valueAsNumber: number | null;
6
- valid: boolean;
7
- }
8
-
9
- export class GEInput extends HTMLElement {
10
- private readonly _input = document.createElement('input');
11
-
12
- static get observedAttributes(): string[] {
13
- return [
14
- 'type', 'value', 'min', 'max', 'step', 'placeholder', 'disabled', 'readonly',
15
- 'required', 'name', 'autocomplete', 'aria-label', 'title',
16
- ];
17
- }
18
-
19
- constructor() {
20
- super();
21
- const root = this.attachShadow({ mode: 'open' });
22
- const style = document.createElement('style');
23
- style.textContent = `
24
- :host {
25
- display: block;
26
- width: 100%;
27
- min-width: 0;
28
- }
29
- input {
30
- box-sizing: border-box;
31
- width: 100%;
32
- height: var(--ge-input-height, 28px);
33
- min-width: 0;
34
- padding: 0 8px;
35
- border: 1px solid var(--ge-border-color, #303746);
36
- border-radius: 4px;
37
- outline: none;
38
- color: var(--ge-text-color, #d8e2f2);
39
- background: var(--ge-input-bg-color, var(--ge-surface-color, #121822));
40
- font: var(--ge-input-font, 12px system-ui, sans-serif);
41
- color-scheme: dark;
42
- }
43
- input:hover:not(:disabled):not(:read-only) {
44
- border-color: var(--ge-hover-border-color, #435268);
45
- }
46
- input:focus {
47
- border-color: var(--ge-focus-border-color, #3d6fa8);
48
- box-shadow: 0 0 0 2px var(--ge-focus-ring-color, rgba(61, 111, 168, 0.24));
49
- }
50
- input:invalid {
51
- border-color: var(--ge-invalid-border-color, #b64f62);
52
- }
53
- input:disabled,
54
- input:read-only {
55
- opacity: 0.55;
56
- cursor: default;
57
- }
58
- input[type="color"] {
59
- padding: 3px;
60
- cursor: pointer;
61
- }
62
- input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
63
- input[type="color"]::-webkit-color-swatch {
64
- border: 0;
65
- border-radius: 2px;
66
- }
67
- `;
68
- root.append(style, this._input);
69
- }
70
-
71
- connectedCallback(): void {
72
- this._input.addEventListener('input', this._onInput);
73
- this._input.addEventListener('change', this._onChange);
74
- this._sync();
75
- }
76
-
77
- disconnectedCallback(): void {
78
- this._input.removeEventListener('input', this._onInput);
79
- this._input.removeEventListener('change', this._onChange);
80
- }
81
-
82
- attributeChangedCallback(): void {
83
- this._sync();
84
- }
85
-
86
- get type(): GEInputType { return normalizedType(this.getAttribute('type')); }
87
- set type(value: GEInputType) { this.setAttribute('type', normalizedType(value)); }
88
-
89
- get value(): string { return this.getAttribute('value') ?? ''; }
90
- set value(value: string) {
91
- this.setAttribute('value', String(value));
92
- this._input.value = String(value);
93
- }
94
-
95
- get valueAsNumber(): number {
96
- return this._input.valueAsNumber;
97
- }
98
-
99
- get disabled(): boolean { return this.hasAttribute('disabled'); }
100
- set disabled(value: boolean) { this.toggleAttribute('disabled', value); }
101
-
102
- get readOnly(): boolean { return this.hasAttribute('readonly'); }
103
- set readOnly(value: boolean) { this.toggleAttribute('readonly', value); }
104
-
105
- get valid(): boolean { return this._input.validity.valid; }
106
-
107
- override focus(options?: FocusOptions): void { this._input.focus(options); }
108
- select(): void { this._input.select(); }
109
-
110
- private _sync(): void {
111
- const type = this.type;
112
- if (this._input.type !== type) this._input.type = type;
113
- const value = this.value;
114
- if (this._input.value !== value) this._input.value = type === 'color' && !/^#[0-9a-f]{6}$/iu.test(value)
115
- ? '#000000'
116
- : value;
117
- for (const name of ['min', 'max', 'step', 'placeholder', 'name', 'autocomplete', 'aria-label', 'title']) {
118
- const attribute = this.getAttribute(name);
119
- if (attribute === null) this._input.removeAttribute(name);
120
- else this._input.setAttribute(name, attribute);
121
- }
122
- this._input.disabled = this.disabled;
123
- this._input.readOnly = this.readOnly;
124
- this._input.required = this.hasAttribute('required');
125
- }
126
-
127
- private _onInput = (): void => {
128
- this.setAttribute('value', this._input.value);
129
- };
130
-
131
- private _onChange = (): void => {
132
- this.setAttribute('value', this._input.value);
133
- const number = this.type === 'number' && Number.isFinite(this._input.valueAsNumber)
134
- ? this._input.valueAsNumber
135
- : null;
136
- this.dispatchEvent(new CustomEvent<GEInputChangeDetail>('value-change', {
137
- detail: { value: this._input.value, valueAsNumber: number, valid: this.valid },
138
- bubbles: true,
139
- composed: true,
140
- }));
141
- };
142
- }
143
-
144
- export function defineInputComponents(): void {
145
- if (!customElements.get('ge-input')) customElements.define('ge-input', GEInput);
146
- }
147
-
148
- function normalizedType(value: string | null): GEInputType {
149
- return value === 'number' || value === 'color' ? value : 'text';
150
- }
@@ -1,65 +0,0 @@
1
- export interface GEMenuItemLike {
2
- label?: string;
3
- value?: string;
4
- disabled?: boolean;
5
- separator?: boolean;
6
- }
7
-
8
- export interface GEMenuButtonOptions {
9
- index?: number;
10
- role?: string;
11
- tabIndex?: number;
12
- }
13
-
14
- export const GE_MENU_ITEM_STYLES = `
15
- button {
16
- box-sizing: border-box;
17
- width: 100%;
18
- height: 26px;
19
- display: flex;
20
- align-items: center;
21
- padding: 0 8px;
22
- border: 0;
23
- border-radius: 3px;
24
- color: var(--ge-text-color, #d8e2f2);
25
- background: transparent;
26
- font: inherit;
27
- text-align: left;
28
- cursor: pointer;
29
- }
30
- button:hover,
31
- button:focus,
32
- button.active {
33
- background: var(--ge-menu-item-hover-bg-color, var(--ge-accent-strong-color, #255a91));
34
- outline: none;
35
- }
36
- button:disabled {
37
- color: var(--ge-muted-color, #66758b);
38
- cursor: default;
39
- }
40
- button:disabled:hover {
41
- background: transparent;
42
- }
43
- .separator {
44
- height: 1px;
45
- margin: 4px 2px;
46
- background: var(--ge-border-color, #303746);
47
- }
48
- `;
49
-
50
- export function createMenuSeparator(): HTMLDivElement {
51
- const separator = document.createElement('div');
52
- separator.className = 'separator';
53
- return separator;
54
- }
55
-
56
- export function createMenuItemButton(item: GEMenuItemLike, options: GEMenuButtonOptions = {}): HTMLButtonElement {
57
- const button = document.createElement('button');
58
- button.type = 'button';
59
- button.textContent = item.label ?? item.value ?? '';
60
- button.disabled = Boolean(item.disabled);
61
- if (options.index !== undefined) button.dataset.index = String(options.index);
62
- if (options.role) button.setAttribute('role', options.role);
63
- if (options.tabIndex !== undefined) button.tabIndex = options.tabIndex;
64
- return button;
65
- }