@iyulab/components 0.1.10 → 0.1.11
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 +6 -0
- package/dist/components/FloatingElement.js +1 -1
- package/dist/components/FloatingElement.styles.js +0 -4
- package/dist/components/divider/UDivider.component.d.ts +3 -18
- package/dist/components/divider/UDivider.component.js +3 -62
- package/dist/components/divider/UDivider.styles.js +11 -43
- package/dist/components/icon/UIcon.component.d.ts +4 -1
- package/dist/components/menu/UMenu.component.d.ts +4 -7
- package/dist/components/menu/UMenu.component.js +62 -63
- package/dist/components/menu/UMenu.styles.js +1 -1
- package/dist/components/menu-item/UMenuItem.component.d.ts +8 -2
- package/dist/components/menu-item/UMenuItem.component.js +37 -25
- package/dist/components/menu-item/UMenuItem.styles.js +4 -4
- package/dist/components/split-panel/USplitPanel.component.d.ts +22 -8
- package/dist/components/split-panel/USplitPanel.component.js +117 -71
- package/dist/components/split-panel/USplitPanel.styles.js +30 -0
- package/dist/components/tree-item/UTreeItem.component.d.ts +16 -8
- package/dist/components/tree-item/UTreeItem.component.js +69 -31
- package/dist/components/tree-item/UTreeItem.styles.js +8 -3
- package/dist/index.js +1 -1
- package/dist/utilities/theme.d.ts +1 -2
- package/dist/utilities/theme.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.11 (2026-01-06)
|
|
4
|
+
- Fixed d.ts generation error for `Theme` class by exporting the class
|
|
5
|
+
- Fixed `UTreeItem.childrenItems` accessibility for `UTree` component
|
|
6
|
+
- Changed private field to public readonly getter for d.ts compatibility
|
|
7
|
+
- Fixed `UTree.getAllTreeItems` parameter type to accept readonly arrays
|
|
8
|
+
|
|
3
9
|
## 0.1.10 (2025-12-19)
|
|
4
10
|
- Fixed circular dependency issue between `UMenu` and `UMenuItem`
|
|
5
11
|
- Changed `UMenuItem` to use tagName check instead of `instanceof` for submenu detection
|
|
@@ -55,8 +55,8 @@ class FloatingElement extends BaseElement {
|
|
|
55
55
|
updated(changedProperties) {
|
|
56
56
|
super.updated(changedProperties);
|
|
57
57
|
if (changedProperties.has("visible")) {
|
|
58
|
-
this.toggleAttribute("inert", !this.visible);
|
|
59
58
|
this.toggleAttribute("aria-hidden", !this.visible);
|
|
59
|
+
this.toggleAttribute("inert", !this.visible);
|
|
60
60
|
this.updateVisibleState(this.visible);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -1,25 +1,10 @@
|
|
|
1
|
-
import { nothing } from 'lit';
|
|
2
1
|
import { BaseElement } from '../BaseElement.js';
|
|
3
2
|
/**
|
|
4
|
-
* Divider 컴포넌트는 엘리먼트 사이에
|
|
3
|
+
* Divider 컴포넌트는 엘리먼트 사이에 구분선을 제공합니다.
|
|
5
4
|
*/
|
|
6
5
|
export declare class UDivider extends BaseElement {
|
|
7
6
|
static styles: import('lit').CSSResultGroup[];
|
|
8
7
|
static dependencies: Record<string, typeof BaseElement>;
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
/** 분할선이 움직이는지 여부를 나타냅니다. */
|
|
12
|
-
moving: boolean;
|
|
13
|
-
/** 분할 방향을 설정합니다. */
|
|
14
|
-
orientation: 'horizontal' | 'vertical';
|
|
15
|
-
/** 분할선을 움직일 수 있는지 여부를 설정합니다. */
|
|
16
|
-
movable: boolean;
|
|
17
|
-
disconnectedCallback(): void;
|
|
18
|
-
render(): typeof nothing | import('lit-html').TemplateResult<1>;
|
|
19
|
-
/** 마우스 다운 이벤트 핸들러 */
|
|
20
|
-
private handleMousedown;
|
|
21
|
-
/** 마우스 무브 이벤트 핸들러 */
|
|
22
|
-
private handleMousemove;
|
|
23
|
-
/** 마우스 업 이벤트 핸들러 */
|
|
24
|
-
private handleMouseup;
|
|
8
|
+
/** 수직 방향 여부를 설정합니다. (기본값: 수평) */
|
|
9
|
+
vertical: boolean;
|
|
25
10
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { state, property } from 'lit/decorators.js';
|
|
1
|
+
import { property } from 'lit/decorators.js';
|
|
3
2
|
import { BaseElement } from '../BaseElement.js';
|
|
4
3
|
import { styles } from './UDivider.styles.js';
|
|
5
4
|
|
|
@@ -15,41 +14,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
15
14
|
class UDivider extends BaseElement {
|
|
16
15
|
constructor() {
|
|
17
16
|
super(...arguments);
|
|
18
|
-
|
|
19
|
-
this.prevPointerPosition = 0;
|
|
20
|
-
this.moving = false;
|
|
21
|
-
this.orientation = "horizontal";
|
|
22
|
-
this.movable = false;
|
|
23
|
-
/** 마우스 다운 이벤트 핸들러 */
|
|
24
|
-
this.handleMousedown = (e) => {
|
|
25
|
-
e.preventDefault();
|
|
26
|
-
this.moving = true;
|
|
27
|
-
this.prevPointerPosition = this.orientation === "horizontal" ? e.clientX : e.clientY;
|
|
28
|
-
this.emit("u-movestart");
|
|
29
|
-
document.addEventListener("mousemove", this.handleMousemove);
|
|
30
|
-
document.addEventListener("mouseup", this.handleMouseup);
|
|
31
|
-
document.body.style.userSelect = "none";
|
|
32
|
-
document.body.style.cursor = this.orientation === "horizontal" ? "col-resize" : "row-resize";
|
|
33
|
-
};
|
|
34
|
-
/** 마우스 무브 이벤트 핸들러 */
|
|
35
|
-
this.handleMousemove = (e) => {
|
|
36
|
-
if (!this.moving) return;
|
|
37
|
-
e.preventDefault();
|
|
38
|
-
const currentPointerPosition = this.orientation === "horizontal" ? e.clientX : e.clientY;
|
|
39
|
-
const delta = currentPointerPosition - this.prevPointerPosition;
|
|
40
|
-
this.emit("u-move", { delta });
|
|
41
|
-
this.prevPointerPosition = currentPointerPosition;
|
|
42
|
-
};
|
|
43
|
-
/** 마우스 업 이벤트 핸들러 */
|
|
44
|
-
this.handleMouseup = (_) => {
|
|
45
|
-
if (!this.moving) return;
|
|
46
|
-
this.moving = false;
|
|
47
|
-
this.emit("u-moveend");
|
|
48
|
-
document.removeEventListener("mousemove", this.handleMousemove);
|
|
49
|
-
document.removeEventListener("mouseup", this.handleMouseup);
|
|
50
|
-
document.body.style.userSelect = "";
|
|
51
|
-
document.body.style.cursor = "";
|
|
52
|
-
};
|
|
17
|
+
this.vertical = false;
|
|
53
18
|
}
|
|
54
19
|
static {
|
|
55
20
|
this.styles = [super.styles, styles];
|
|
@@ -57,33 +22,9 @@ class UDivider extends BaseElement {
|
|
|
57
22
|
static {
|
|
58
23
|
this.dependencies = {};
|
|
59
24
|
}
|
|
60
|
-
disconnectedCallback() {
|
|
61
|
-
super.disconnectedCallback();
|
|
62
|
-
document.removeEventListener("mousemove", this.handleMousemove);
|
|
63
|
-
document.removeEventListener("mouseup", this.handleMouseup);
|
|
64
|
-
}
|
|
65
|
-
render() {
|
|
66
|
-
if (this.movable) {
|
|
67
|
-
return html`
|
|
68
|
-
<div class="handler" part="handler"
|
|
69
|
-
?moving=${this.moving}
|
|
70
|
-
orientation=${this.orientation}
|
|
71
|
-
@mousedown=${this.handleMousedown}
|
|
72
|
-
></div>
|
|
73
|
-
`;
|
|
74
|
-
} else {
|
|
75
|
-
return nothing;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
25
|
}
|
|
79
|
-
__decorateClass([
|
|
80
|
-
state()
|
|
81
|
-
], UDivider.prototype, "moving");
|
|
82
|
-
__decorateClass([
|
|
83
|
-
property({ type: String, reflect: true })
|
|
84
|
-
], UDivider.prototype, "orientation");
|
|
85
26
|
__decorateClass([
|
|
86
27
|
property({ type: Boolean, reflect: true })
|
|
87
|
-
], UDivider.prototype, "
|
|
28
|
+
], UDivider.prototype, "vertical");
|
|
88
29
|
|
|
89
30
|
export { UDivider };
|
|
@@ -2,53 +2,21 @@ import { css } from 'lit';
|
|
|
2
2
|
|
|
3
3
|
const styles = css`
|
|
4
4
|
:host {
|
|
5
|
-
--divider-size:
|
|
6
|
-
--
|
|
7
|
-
--
|
|
5
|
+
--divider-size: 1px;
|
|
6
|
+
--divider-color: var(--u-neutral-200, #e5e7eb);
|
|
7
|
+
--divider-spacing: 8px;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
:host {
|
|
11
|
-
position: relative;
|
|
12
|
-
background-color: var(--u-neutral-200, #e5e7eb);
|
|
13
|
-
}
|
|
14
|
-
:host([orientation="horizontal"]) {
|
|
15
|
-
width: var(--divider-size, 2px);
|
|
16
|
-
min-height: 0px;
|
|
17
|
-
}
|
|
18
|
-
:host([orientation="vertical"]) {
|
|
19
|
-
min-width: 0px;
|
|
20
|
-
height: var(--divider-size, 2px);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.handler {
|
|
24
|
-
position: absolute;
|
|
25
|
-
z-index: 100;
|
|
26
11
|
display: block;
|
|
27
|
-
background-color: var(--
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
transform: translateX(-50%);
|
|
36
|
-
width: var(--handler-size, 4px);
|
|
37
|
-
height: 100%;
|
|
38
|
-
cursor: col-resize;
|
|
39
|
-
}
|
|
40
|
-
.handler[orientation="vertical"] {
|
|
41
|
-
left: 0;
|
|
42
|
-
right: 0;
|
|
43
|
-
top: 50%;
|
|
44
|
-
transform: translateY(-50%);
|
|
45
|
-
width: 100%;
|
|
46
|
-
height: var(--handler-size, 4px);
|
|
47
|
-
cursor: row-resize;
|
|
48
|
-
}
|
|
49
|
-
.handler[moving],
|
|
50
|
-
.handler:hover {
|
|
51
|
-
opacity: 1;
|
|
12
|
+
background-color: var(--divider-color);
|
|
13
|
+
height: var(--divider-size);
|
|
14
|
+
margin: var(--divider-spacing) 0;
|
|
15
|
+
}
|
|
16
|
+
:host([vertical]) {
|
|
17
|
+
width: var(--divider-size);
|
|
18
|
+
height: auto;
|
|
19
|
+
margin: 0 var(--divider-spacing);
|
|
52
20
|
}
|
|
53
21
|
`;
|
|
54
22
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nothing } from 'lit';
|
|
1
2
|
import { BaseElement } from '../BaseElement.js';
|
|
2
3
|
export type IconLibrary = "internal" | "default" | (string & {});
|
|
3
4
|
/**
|
|
@@ -18,7 +19,9 @@ export declare class UIcon extends BaseElement {
|
|
|
18
19
|
* 사용할 아이콘의 이름을 지정합니다.
|
|
19
20
|
*/
|
|
20
21
|
name?: string;
|
|
21
|
-
render(): import('lit-html/directive.js').DirectiveResult<
|
|
22
|
+
render(): import('lit-html/directive.js').DirectiveResult<{
|
|
23
|
+
new (_partInfo: import('lit-html/directive.js').PartInfo): import('lit-html/directives/until.js').UntilDirective<typeof nothing | Promise<import('lit-html/directive.js').DirectiveResult<typeof import('lit-html/directives/unsafe-html.js').UnsafeHTMLDirective>>>;
|
|
24
|
+
}>;
|
|
22
25
|
/**
|
|
23
26
|
* SVG 콘텐츠가 유효한 HTML인지 검사합니다.
|
|
24
27
|
*/
|
|
@@ -17,7 +17,6 @@ export declare class UMenu extends FloatingElement {
|
|
|
17
17
|
* - default: 일반 메뉴 (수동 제어)
|
|
18
18
|
* - dropdown: 드롭다운 메뉴 (앵커 클릭 시 토글)
|
|
19
19
|
* - contextmenu: 컨텍스트 메뉴 (앵커 우클릭 시 표시)
|
|
20
|
-
* - submenu: 서브메뉴 (앵커 호버/포커스 시 표시)
|
|
21
20
|
*/
|
|
22
21
|
type: MenuType;
|
|
23
22
|
/**
|
|
@@ -57,14 +56,12 @@ export declare class UMenu extends FloatingElement {
|
|
|
57
56
|
private handleWindowFocusOut;
|
|
58
57
|
private handleWindowKeydown;
|
|
59
58
|
private handleWindowPointerDown;
|
|
60
|
-
private handleAnchorPointerEnter;
|
|
61
|
-
private handleAnchorPointerLeave;
|
|
62
|
-
private handleAnchorFocusIn;
|
|
63
|
-
private handleAnchorFocusOut;
|
|
64
59
|
private handleAnchorPointerDown;
|
|
65
60
|
private handleAnchorKeydown;
|
|
66
61
|
private handleAnchorContextMenu;
|
|
67
|
-
|
|
68
|
-
private
|
|
62
|
+
private handleSubmenuAnchorPointerEnter;
|
|
63
|
+
private handleSubmenuAnchorPointerLeave;
|
|
64
|
+
private handleSubmenuAnchorFocusIn;
|
|
65
|
+
private handleSubmenuAnchorFocusOut;
|
|
69
66
|
}
|
|
70
67
|
export {};
|
|
@@ -24,7 +24,7 @@ class UMenu extends FloatingElement {
|
|
|
24
24
|
*/
|
|
25
25
|
this.focusAt = async (index = 0) => {
|
|
26
26
|
await this.updateComplete;
|
|
27
|
-
const items = this.
|
|
27
|
+
const items = this.getItems("enabled");
|
|
28
28
|
const length = items.length;
|
|
29
29
|
if (length === 0) return;
|
|
30
30
|
index = index < 0 ? Math.max(0, length + index) : Math.min(index, length - 1);
|
|
@@ -34,8 +34,10 @@ class UMenu extends FloatingElement {
|
|
|
34
34
|
};
|
|
35
35
|
//#region 기본 이벤트 핸들러
|
|
36
36
|
this.handleClick = async (e) => {
|
|
37
|
-
const item =
|
|
38
|
-
if (!item
|
|
37
|
+
const item = e.target;
|
|
38
|
+
if (!(item instanceof UMenuItem)) return;
|
|
39
|
+
if (item.disabled) return;
|
|
40
|
+
if (item.submenu) return;
|
|
39
41
|
const items = this.getItems("enabled");
|
|
40
42
|
if (!items.includes(item)) return;
|
|
41
43
|
const value = item.value || "";
|
|
@@ -56,8 +58,9 @@ class UMenu extends FloatingElement {
|
|
|
56
58
|
this.emit("u-select", { value });
|
|
57
59
|
};
|
|
58
60
|
this.handleKeydown = async (e) => {
|
|
59
|
-
const item =
|
|
60
|
-
if (!item) return;
|
|
61
|
+
const item = e.target;
|
|
62
|
+
if (!(item instanceof UMenuItem)) return;
|
|
63
|
+
if (item.disabled) return;
|
|
61
64
|
const items = this.getItems("enabled");
|
|
62
65
|
if (!items.includes(item)) return;
|
|
63
66
|
const currentIndex = items.findIndex((i) => i === item);
|
|
@@ -87,18 +90,19 @@ class UMenu extends FloatingElement {
|
|
|
87
90
|
await this.focusAt(nextIndex);
|
|
88
91
|
break;
|
|
89
92
|
case "ArrowLeft":
|
|
90
|
-
e.preventDefault();
|
|
91
|
-
e.stopPropagation();
|
|
92
93
|
if (this.type === "submenu") {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
e.stopPropagation();
|
|
96
|
+
await this.hide();
|
|
93
97
|
this.anchor?.focus();
|
|
94
98
|
}
|
|
95
99
|
break;
|
|
96
100
|
case "ArrowRight":
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
await
|
|
101
|
+
if (item.submenu) {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
e.stopPropagation();
|
|
104
|
+
await item.submenu.show();
|
|
105
|
+
await item.submenu.focusAt(0);
|
|
102
106
|
}
|
|
103
107
|
break;
|
|
104
108
|
}
|
|
@@ -131,28 +135,6 @@ class UMenu extends FloatingElement {
|
|
|
131
135
|
}
|
|
132
136
|
};
|
|
133
137
|
//#endregion
|
|
134
|
-
//#region 'submenu' 이벤트 핸들러
|
|
135
|
-
this.handleAnchorPointerEnter = (e) => {
|
|
136
|
-
const item = this.getEnabledItemFrom(e);
|
|
137
|
-
if (!item) return;
|
|
138
|
-
this.show();
|
|
139
|
-
};
|
|
140
|
-
this.handleAnchorPointerLeave = async (e) => {
|
|
141
|
-
const item = this.getEnabledItemFrom(e);
|
|
142
|
-
if (!item) return;
|
|
143
|
-
this.hide();
|
|
144
|
-
};
|
|
145
|
-
this.handleAnchorFocusIn = (e) => {
|
|
146
|
-
const item = this.getEnabledItemFrom(e);
|
|
147
|
-
if (!item) return;
|
|
148
|
-
this.show();
|
|
149
|
-
};
|
|
150
|
-
this.handleAnchorFocusOut = async (e) => {
|
|
151
|
-
const item = this.getEnabledItemFrom(e);
|
|
152
|
-
if (!item) return;
|
|
153
|
-
this.hide();
|
|
154
|
-
};
|
|
155
|
-
//#endregion
|
|
156
138
|
//#region 'dropdown' 이벤트 핸들러
|
|
157
139
|
this.handleAnchorPointerDown = async (e) => {
|
|
158
140
|
if (e.button !== 0) return;
|
|
@@ -187,14 +169,40 @@ class UMenu extends FloatingElement {
|
|
|
187
169
|
};
|
|
188
170
|
await this.show(virtual, false);
|
|
189
171
|
};
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region 'submenu' 이벤트 핸들러
|
|
174
|
+
this.handleSubmenuAnchorPointerEnter = () => {
|
|
175
|
+
const anchor = this.anchor;
|
|
176
|
+
if (anchor?.disabled) return;
|
|
177
|
+
this.show();
|
|
178
|
+
};
|
|
179
|
+
this.handleSubmenuAnchorPointerLeave = (e) => {
|
|
180
|
+
const related = e.relatedTarget;
|
|
181
|
+
if (related) {
|
|
182
|
+
const anchor = this.anchor;
|
|
183
|
+
if (anchor?.contains(related) || this.contains(related)) return;
|
|
184
|
+
}
|
|
185
|
+
this.hide();
|
|
186
|
+
};
|
|
187
|
+
this.handleSubmenuAnchorFocusIn = () => {
|
|
188
|
+
const anchor = this.anchor;
|
|
189
|
+
if (anchor?.disabled) return;
|
|
190
|
+
this.show();
|
|
191
|
+
};
|
|
192
|
+
this.handleSubmenuAnchorFocusOut = (e) => {
|
|
193
|
+
const related = e.relatedTarget;
|
|
194
|
+
if (related) {
|
|
195
|
+
const anchor = this.anchor;
|
|
196
|
+
if (anchor?.contains(related) || this.contains(related)) return;
|
|
197
|
+
}
|
|
198
|
+
this.hide();
|
|
199
|
+
};
|
|
190
200
|
}
|
|
191
201
|
static {
|
|
192
202
|
this.styles = [super.styles, styles];
|
|
193
203
|
}
|
|
194
204
|
static {
|
|
195
|
-
this.dependencies = {
|
|
196
|
-
"u-menu-item": UMenuItem
|
|
197
|
-
};
|
|
205
|
+
this.dependencies = {};
|
|
198
206
|
}
|
|
199
207
|
connectedCallback() {
|
|
200
208
|
super.connectedCallback();
|
|
@@ -228,13 +236,12 @@ class UMenu extends FloatingElement {
|
|
|
228
236
|
* 재귀적으로 서브메뉴의 아이템들도 해제됩니다.
|
|
229
237
|
*/
|
|
230
238
|
clearAll() {
|
|
231
|
-
const items = this.
|
|
239
|
+
const items = this.getItems("all");
|
|
232
240
|
for (const item of items) {
|
|
233
241
|
item.selected = false;
|
|
234
242
|
item.checked = false;
|
|
235
|
-
if (item.
|
|
236
|
-
|
|
237
|
-
submenu?.clearAll();
|
|
243
|
+
if (item.submenu) {
|
|
244
|
+
item.submenu.clearAll();
|
|
238
245
|
}
|
|
239
246
|
}
|
|
240
247
|
}
|
|
@@ -245,7 +252,7 @@ class UMenu extends FloatingElement {
|
|
|
245
252
|
* @return 조건에 맞는 아이템들의 배열입니다
|
|
246
253
|
*/
|
|
247
254
|
getItems(filter = "all") {
|
|
248
|
-
const items = this.items || [];
|
|
255
|
+
const items = this.items?.filter((item) => item instanceof UMenuItem) || [];
|
|
249
256
|
switch (filter) {
|
|
250
257
|
case "all":
|
|
251
258
|
return items;
|
|
@@ -267,15 +274,7 @@ class UMenu extends FloatingElement {
|
|
|
267
274
|
}
|
|
268
275
|
/** 앵커에 이벤트 바인딩 */
|
|
269
276
|
bind(type, target) {
|
|
270
|
-
if (type === "
|
|
271
|
-
this.visible = false;
|
|
272
|
-
this.placement ||= "right-start";
|
|
273
|
-
this.offset = -8;
|
|
274
|
-
target.addEventListener("pointerenter", this.handleAnchorPointerEnter);
|
|
275
|
-
target.addEventListener("pointerleave", this.handleAnchorPointerLeave);
|
|
276
|
-
target.addEventListener("focusin", this.handleAnchorFocusIn);
|
|
277
|
-
target.addEventListener("focusout", this.handleAnchorFocusOut);
|
|
278
|
-
} else if (type === "dropdown") {
|
|
277
|
+
if (type === "dropdown") {
|
|
279
278
|
this.visible = false;
|
|
280
279
|
this.placement ||= "bottom-start";
|
|
281
280
|
target.addEventListener("pointerdown", this.handleAnchorPointerDown);
|
|
@@ -284,6 +283,13 @@ class UMenu extends FloatingElement {
|
|
|
284
283
|
this.visible = false;
|
|
285
284
|
this.placement ||= "bottom-start";
|
|
286
285
|
target.addEventListener("contextmenu", this.handleAnchorContextMenu);
|
|
286
|
+
} else if (type === "submenu") {
|
|
287
|
+
this.visible = false;
|
|
288
|
+
this.placement ||= "right-start";
|
|
289
|
+
target.addEventListener("pointerenter", this.handleSubmenuAnchorPointerEnter);
|
|
290
|
+
target.addEventListener("pointerleave", this.handleSubmenuAnchorPointerLeave);
|
|
291
|
+
target.addEventListener("focusin", this.handleSubmenuAnchorFocusIn);
|
|
292
|
+
target.addEventListener("focusout", this.handleSubmenuAnchorFocusOut);
|
|
287
293
|
} else {
|
|
288
294
|
this.visible = true;
|
|
289
295
|
return;
|
|
@@ -297,25 +303,18 @@ class UMenu extends FloatingElement {
|
|
|
297
303
|
window.removeEventListener("focusout", this.handleWindowFocusOut);
|
|
298
304
|
window.removeEventListener("keydown", this.handleWindowKeydown);
|
|
299
305
|
window.removeEventListener("pointerdown", this.handleWindowPointerDown);
|
|
300
|
-
target.removeEventListener("pointerenter", this.handleAnchorPointerEnter);
|
|
301
|
-
target.removeEventListener("pointerleave", this.handleAnchorPointerLeave);
|
|
302
|
-
target.removeEventListener("focusin", this.handleAnchorFocusIn);
|
|
303
|
-
target.removeEventListener("focusout", this.handleAnchorFocusOut);
|
|
304
306
|
target.removeEventListener("pointerdown", this.handleAnchorPointerDown);
|
|
305
307
|
target.removeEventListener("keydown", this.handleAnchorKeydown);
|
|
306
308
|
target.removeEventListener("contextmenu", this.handleAnchorContextMenu);
|
|
309
|
+
target.removeEventListener("pointerenter", this.handleSubmenuAnchorPointerEnter);
|
|
310
|
+
target.removeEventListener("pointerleave", this.handleSubmenuAnchorPointerLeave);
|
|
311
|
+
target.removeEventListener("focusin", this.handleSubmenuAnchorFocusIn);
|
|
312
|
+
target.removeEventListener("focusout", this.handleSubmenuAnchorFocusOut);
|
|
307
313
|
}
|
|
308
314
|
//#endregion
|
|
309
|
-
/** 이벤트로부터 활성화된 (비활성화되지 않은) 메뉴 아이템을 반환 */
|
|
310
|
-
getEnabledItemFrom(event) {
|
|
311
|
-
const target = event.target;
|
|
312
|
-
const item = target.closest("u-menu-item");
|
|
313
|
-
if (!item || item.disabled) return null;
|
|
314
|
-
return item;
|
|
315
|
-
}
|
|
316
315
|
}
|
|
317
316
|
__decorateClass([
|
|
318
|
-
queryAssignedElements({ flatten: false
|
|
317
|
+
queryAssignedElements({ flatten: false })
|
|
319
318
|
], UMenu.prototype, "items");
|
|
320
319
|
__decorateClass([
|
|
321
320
|
property({ type: String, reflect: true })
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { PropertyValues } from 'lit';
|
|
2
2
|
import { BaseElement } from '../BaseElement.js';
|
|
3
|
+
import { UMenu } from '../menu/UMenu.component.js';
|
|
3
4
|
export declare class UMenuItem extends BaseElement {
|
|
4
5
|
static styles: import('lit').CSSResultGroup[];
|
|
5
6
|
static dependencies: Record<string, typeof BaseElement>;
|
|
6
|
-
/**
|
|
7
|
-
|
|
7
|
+
/** 서브메뉴 요소 */
|
|
8
|
+
submenu: UMenu | null;
|
|
8
9
|
/** 비활성화 여부 @default false */
|
|
9
10
|
disabled: boolean;
|
|
11
|
+
/** 로딩 상태 @default false */
|
|
12
|
+
loading: boolean;
|
|
10
13
|
/** 선택 여부(multiple mode) @default false */
|
|
11
14
|
checked: boolean;
|
|
12
15
|
/** 선택 여부(single mode) @default false */
|
|
@@ -15,5 +18,8 @@ export declare class UMenuItem extends BaseElement {
|
|
|
15
18
|
value: string;
|
|
16
19
|
protected updated(changedProperties: PropertyValues): void;
|
|
17
20
|
render(): import('lit-html').TemplateResult<1>;
|
|
21
|
+
/** 기본 슬롯 변경 감지 */
|
|
22
|
+
private handleSlotChange;
|
|
23
|
+
/** submenu 슬롯 변경 감지 */
|
|
18
24
|
private handleSubmenuSlotChange;
|
|
19
25
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { html } from 'lit';
|
|
2
2
|
import { state, property } from 'lit/decorators.js';
|
|
3
3
|
import { BaseElement } from '../BaseElement.js';
|
|
4
|
+
import { UIcon } from '../icon/UIcon.component.js';
|
|
5
|
+
import { UMenu } from '../menu/UMenu.component.js';
|
|
6
|
+
import { USpinner } from '../spinner/USpinner.component.js';
|
|
4
7
|
import { styles } from './UMenuItem.styles.js';
|
|
5
8
|
|
|
6
9
|
var __defProp = Object.defineProperty;
|
|
@@ -15,27 +18,30 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
15
18
|
class UMenuItem extends BaseElement {
|
|
16
19
|
constructor() {
|
|
17
20
|
super(...arguments);
|
|
18
|
-
this.
|
|
21
|
+
this.submenu = null;
|
|
19
22
|
this.disabled = false;
|
|
23
|
+
this.loading = false;
|
|
20
24
|
this.checked = false;
|
|
21
25
|
this.selected = false;
|
|
22
26
|
this.value = "";
|
|
27
|
+
/** 기본 슬롯 변경 감지 */
|
|
28
|
+
this.handleSlotChange = (e) => {
|
|
29
|
+
const slot = e.target;
|
|
30
|
+
const elements = slot.assignedElements({ flatten: true });
|
|
31
|
+
const menu = elements.find((el) => el instanceof UMenu);
|
|
32
|
+
menu?.setAttribute("slot", "submenu");
|
|
33
|
+
};
|
|
34
|
+
/** submenu 슬롯 변경 감지 */
|
|
23
35
|
this.handleSubmenuSlotChange = (e) => {
|
|
24
36
|
const slot = e.target;
|
|
25
37
|
const elements = slot.assignedElements({ flatten: true });
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
const menu = elements.find((el) => el instanceof UMenu);
|
|
39
|
+
if (menu) {
|
|
40
|
+
menu.type = "submenu";
|
|
41
|
+
menu.anchor = this;
|
|
42
|
+
this.submenu = menu;
|
|
29
43
|
} else {
|
|
30
|
-
|
|
31
|
-
if ("anchor" in submenu && "type" in submenu) {
|
|
32
|
-
submenu.anchor = this;
|
|
33
|
-
submenu.type = "submenu";
|
|
34
|
-
this.isNested = true;
|
|
35
|
-
} else {
|
|
36
|
-
console.warn(`the element assigned to the 'submenu' slot is not a valid UMenu component.`);
|
|
37
|
-
this.isNested = false;
|
|
38
|
-
}
|
|
44
|
+
this.submenu = null;
|
|
39
45
|
}
|
|
40
46
|
};
|
|
41
47
|
}
|
|
@@ -43,13 +49,15 @@ class UMenuItem extends BaseElement {
|
|
|
43
49
|
this.styles = [super.styles, styles];
|
|
44
50
|
}
|
|
45
51
|
static {
|
|
46
|
-
this.dependencies = {
|
|
52
|
+
this.dependencies = {
|
|
53
|
+
"u-icon": UIcon,
|
|
54
|
+
"u-spinner": USpinner
|
|
55
|
+
};
|
|
47
56
|
}
|
|
48
57
|
updated(changedProperties) {
|
|
49
58
|
super.updated(changedProperties);
|
|
50
59
|
if (changedProperties.has("disabled")) {
|
|
51
|
-
|
|
52
|
-
this.setAttribute("tabindex", tabIndex);
|
|
60
|
+
this.setAttribute("tabindex", this.disabled ? "-1" : "0");
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
63
|
render() {
|
|
@@ -57,36 +65,40 @@ class UMenuItem extends BaseElement {
|
|
|
57
65
|
<u-icon class="prefix icon"
|
|
58
66
|
?hidden=${!this.checked}
|
|
59
67
|
lib="internal"
|
|
60
|
-
name="check
|
|
68
|
+
name="check"
|
|
61
69
|
></u-icon>
|
|
62
70
|
|
|
71
|
+
<u-spinner class="prefix icon"
|
|
72
|
+
?hidden=${!this.loading}
|
|
73
|
+
></u-spinner>
|
|
74
|
+
|
|
63
75
|
<slot name="prefix"></slot>
|
|
64
76
|
|
|
65
|
-
<span class="
|
|
66
|
-
<slot></slot>
|
|
77
|
+
<span class="label">
|
|
78
|
+
<slot @slotchange=${this.handleSlotChange}></slot>
|
|
67
79
|
</span>
|
|
68
80
|
|
|
69
81
|
<slot name="suffix"></slot>
|
|
70
82
|
|
|
71
83
|
<u-icon class="suffix icon"
|
|
72
|
-
?hidden=${
|
|
84
|
+
?hidden=${this.submenu === null}
|
|
73
85
|
lib="internal"
|
|
74
86
|
name="chevron-right"
|
|
75
87
|
></u-icon>
|
|
76
88
|
|
|
77
|
-
<slot name="submenu"
|
|
78
|
-
?hidden=${!this.isNested}
|
|
79
|
-
@slotchange=${this.handleSubmenuSlotChange}
|
|
80
|
-
></slot>
|
|
89
|
+
<slot name="submenu" @slotchange=${this.handleSubmenuSlotChange}></slot>
|
|
81
90
|
`;
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
__decorateClass([
|
|
85
94
|
state()
|
|
86
|
-
], UMenuItem.prototype, "
|
|
95
|
+
], UMenuItem.prototype, "submenu");
|
|
87
96
|
__decorateClass([
|
|
88
97
|
property({ type: Boolean, reflect: true })
|
|
89
98
|
], UMenuItem.prototype, "disabled");
|
|
99
|
+
__decorateClass([
|
|
100
|
+
property({ type: Boolean, reflect: true })
|
|
101
|
+
], UMenuItem.prototype, "loading");
|
|
90
102
|
__decorateClass([
|
|
91
103
|
property({ type: Boolean, reflect: true })
|
|
92
104
|
], UMenuItem.prototype, "checked");
|
|
@@ -22,7 +22,7 @@ const styles = css`
|
|
|
22
22
|
pointer-events: none;
|
|
23
23
|
cursor: not-allowed;
|
|
24
24
|
}
|
|
25
|
-
:host(
|
|
25
|
+
:host([selected]) {
|
|
26
26
|
color: var(--selected-color);
|
|
27
27
|
background-color: var(--selected-bg-color);
|
|
28
28
|
}
|
|
@@ -31,9 +31,9 @@ const styles = css`
|
|
|
31
31
|
background-color: var(--u-bg-color-hover);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
/*
|
|
35
|
-
.
|
|
36
|
-
flex: 1
|
|
34
|
+
/* 라벨 영역 */
|
|
35
|
+
.label {
|
|
36
|
+
flex: 1 1 auto;
|
|
37
37
|
font-size: 1em;
|
|
38
38
|
line-height: 1.5;
|
|
39
39
|
overflow: hidden;
|
|
@@ -6,11 +6,17 @@ import { BaseElement } from '../BaseElement.js';
|
|
|
6
6
|
export declare class USplitPanel extends BaseElement {
|
|
7
7
|
static styles: import('lit').CSSResultGroup[];
|
|
8
8
|
static dependencies: Record<string, typeof BaseElement>;
|
|
9
|
+
/** 현재 상태를 저장하는 패널 요소들 */
|
|
9
10
|
private panels;
|
|
10
|
-
private
|
|
11
|
-
|
|
12
|
-
private
|
|
13
|
-
|
|
11
|
+
private sizes;
|
|
12
|
+
/** 드래그 시작 시 저장되는 정보 */
|
|
13
|
+
private draggingIndex;
|
|
14
|
+
private dragStartSizes;
|
|
15
|
+
private dragStartPosition;
|
|
16
|
+
private containerSize;
|
|
17
|
+
slotEls?: HTMLElement[];
|
|
18
|
+
/** 스플리터 개수 (렌더링용) */
|
|
19
|
+
splitterCount: number;
|
|
14
20
|
/** 분할 방향을 설정합니다. 'horizontal'은 좌우 분할, 'vertical'은 상하 분할입니다. */
|
|
15
21
|
orientation: 'horizontal' | 'vertical';
|
|
16
22
|
/** 초기 패널 크기 비율을 설정합니다. (예: [30, 70]은 첫 번째 패널이 30%, 두 번째 패널이 70%를 차지) */
|
|
@@ -22,9 +28,17 @@ export declare class USplitPanel extends BaseElement {
|
|
|
22
28
|
disconnectedCallback(): void;
|
|
23
29
|
protected willUpdate(changedProperties: PropertyValues): void;
|
|
24
30
|
render(): import('lit-html').TemplateResult<1>;
|
|
25
|
-
|
|
31
|
+
/** 슬롯 변경시 패널 초기화 */
|
|
26
32
|
private handleSlotChange;
|
|
27
|
-
/**
|
|
28
|
-
private
|
|
29
|
-
|
|
33
|
+
/** 패널 및 크기 초기화 */
|
|
34
|
+
private initialize;
|
|
35
|
+
/** 패널에 새로운 스타일 적용 */
|
|
36
|
+
private updatePanelStyles;
|
|
37
|
+
private handleSplitterMouseDown;
|
|
38
|
+
private handleDocumentMouseMove;
|
|
39
|
+
private handleDocumentMouseUp;
|
|
40
|
+
/** 전역 이벤트 리스너 제거 */
|
|
41
|
+
private removeEventListeners;
|
|
42
|
+
/** 거터 크기 가져오기 */
|
|
43
|
+
private getGutterSize;
|
|
30
44
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { html } from 'lit';
|
|
2
|
-
import { queryAssignedElements, property } from 'lit/decorators.js';
|
|
2
|
+
import { queryAssignedElements, state, property } from 'lit/decorators.js';
|
|
3
3
|
import { arrayAttributeConverter } from '../../internals/attribute-converters.js';
|
|
4
4
|
import { BaseElement } from '../BaseElement.js';
|
|
5
|
-
import { UDivider } from '../divider/UDivider.component.js';
|
|
6
5
|
import { styles } from './USplitPanel.styles.js';
|
|
7
6
|
|
|
8
7
|
var __defProp = Object.defineProperty;
|
|
@@ -17,90 +16,113 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
17
16
|
class USplitPanel extends BaseElement {
|
|
18
17
|
constructor() {
|
|
19
18
|
super(...arguments);
|
|
19
|
+
/** 현재 상태를 저장하는 패널 요소들 */
|
|
20
20
|
this.panels = [];
|
|
21
|
-
this.
|
|
22
|
-
|
|
23
|
-
this.
|
|
21
|
+
this.sizes = [];
|
|
22
|
+
/** 드래그 시작 시 저장되는 정보 */
|
|
23
|
+
this.draggingIndex = -1;
|
|
24
|
+
this.dragStartSizes = [];
|
|
25
|
+
this.dragStartPosition = 0;
|
|
26
|
+
this.containerSize = 0;
|
|
27
|
+
this.splitterCount = 0;
|
|
24
28
|
this.orientation = "horizontal";
|
|
25
29
|
this.initRatio = [];
|
|
26
30
|
this.minSizes = [];
|
|
27
31
|
this.maxSizes = [];
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
/** 슬롯 변경시 패널 초기화 */
|
|
33
|
+
this.handleSlotChange = () => {
|
|
34
|
+
const panels = this.slotEls || [];
|
|
35
|
+
if (panels.every((p, i) => p === this.panels[i])) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.initialize();
|
|
39
|
+
};
|
|
40
|
+
/** 패널 및 크기 초기화 */
|
|
41
|
+
this.initialize = () => {
|
|
42
|
+
this.panels = this.slotEls || [];
|
|
43
|
+
if (this.panels.length === 0) {
|
|
44
|
+
this.splitterCount = 0;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.splitterCount = Math.max(0, this.panels.length - 1);
|
|
33
48
|
if (this.initRatio.length === this.panels.length) {
|
|
34
|
-
const
|
|
35
|
-
this.
|
|
49
|
+
const total = this.initRatio.reduce((sum, r) => sum + r, 0);
|
|
50
|
+
this.sizes = this.initRatio.map((r) => r / total * 100);
|
|
36
51
|
} else {
|
|
37
|
-
const
|
|
38
|
-
this.
|
|
52
|
+
const equalSize = 100 / this.panels.length;
|
|
53
|
+
this.sizes = this.panels.map(() => equalSize);
|
|
39
54
|
}
|
|
40
|
-
this.
|
|
41
|
-
const property2 = this.orientation === "horizontal" ? "width" : "height";
|
|
42
|
-
const dividerSize = this.getDividerSize();
|
|
43
|
-
this.panels.forEach((panel, index) => {
|
|
44
|
-
const ratio = this.panelSizes[index];
|
|
45
|
-
panel.style.flex = "none";
|
|
46
|
-
panel.style[property2] = index === 0 || index === this.panels.length - 1 ? `calc(${ratio}% - ${dividerSize / 2}px)` : `calc(${ratio}% - ${dividerSize}px)`;
|
|
47
|
-
if (index === this.panels.length - 1) return;
|
|
48
|
-
const divider = new UDivider();
|
|
49
|
-
divider.orientation = this.orientation;
|
|
50
|
-
divider.movable = true;
|
|
51
|
-
divider.addEventListener("u-move", this.handleDividerMove);
|
|
52
|
-
this.dividers.push(divider);
|
|
53
|
-
panel.after(divider);
|
|
54
|
-
});
|
|
55
|
+
this.updatePanelStyles();
|
|
55
56
|
this.requestUpdate();
|
|
56
|
-
await this.updateComplete;
|
|
57
57
|
};
|
|
58
|
-
this.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
this.handleSplitterMouseDown = (e, index) => {
|
|
59
|
+
if (e.button !== 0) return;
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
this.draggingIndex = index;
|
|
62
|
+
this.dragStartPosition = this.orientation === "horizontal" ? e.clientX : e.clientY;
|
|
63
|
+
this.dragStartSizes = [...this.sizes];
|
|
64
|
+
const rect = this.getBoundingClientRect();
|
|
65
|
+
this.containerSize = this.orientation === "horizontal" ? rect.width : rect.height;
|
|
66
|
+
this.containerSize -= this.getGutterSize() * this.splitterCount;
|
|
67
|
+
const splitters = this.shadowRoot?.querySelectorAll(".splitter");
|
|
68
|
+
splitters?.[index]?.classList.add("active");
|
|
69
|
+
document.addEventListener("mousemove", this.handleDocumentMouseMove);
|
|
70
|
+
document.addEventListener("mouseup", this.handleDocumentMouseUp);
|
|
71
|
+
document.body.style.userSelect = "none";
|
|
72
|
+
document.body.style.cursor = this.orientation === "horizontal" ? "col-resize" : "row-resize";
|
|
64
73
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
74
|
+
this.handleDocumentMouseMove = (e) => {
|
|
75
|
+
if (this.draggingIndex === -1) return;
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
const currentPosition = this.orientation === "horizontal" ? e.clientX : e.clientY;
|
|
78
|
+
const delta = currentPosition - this.dragStartPosition;
|
|
79
|
+
const deltaPercent = delta / this.containerSize * 100;
|
|
80
|
+
const aIndex = this.draggingIndex;
|
|
81
|
+
const bIndex = this.draggingIndex + 1;
|
|
82
|
+
let newASize = this.dragStartSizes[aIndex] + deltaPercent;
|
|
83
|
+
let newBSize = this.dragStartSizes[bIndex] - deltaPercent;
|
|
84
|
+
const minAPercent = (this.minSizes[aIndex] ?? 0) / this.containerSize * 100;
|
|
85
|
+
const minBPercent = (this.minSizes[bIndex] ?? 0) / this.containerSize * 100;
|
|
86
|
+
const maxAPercent = this.maxSizes[aIndex] ? this.maxSizes[aIndex] / this.containerSize * 100 : 100;
|
|
87
|
+
const maxBPercent = this.maxSizes[bIndex] ? this.maxSizes[bIndex] / this.containerSize * 100 : 100;
|
|
88
|
+
if (newASize < minAPercent) {
|
|
89
|
+
newASize = minAPercent;
|
|
90
|
+
newBSize = this.dragStartSizes[aIndex] + this.dragStartSizes[bIndex] - newASize;
|
|
91
|
+
}
|
|
92
|
+
if (newBSize < minBPercent) {
|
|
93
|
+
newBSize = minBPercent;
|
|
94
|
+
newASize = this.dragStartSizes[aIndex] + this.dragStartSizes[bIndex] - newBSize;
|
|
95
|
+
}
|
|
96
|
+
if (newASize > maxAPercent) {
|
|
97
|
+
newASize = maxAPercent;
|
|
98
|
+
newBSize = this.dragStartSizes[aIndex] + this.dragStartSizes[bIndex] - newASize;
|
|
89
99
|
}
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
if (newBSize > maxBPercent) {
|
|
101
|
+
newBSize = maxBPercent;
|
|
102
|
+
newASize = this.dragStartSizes[aIndex] + this.dragStartSizes[bIndex] - newBSize;
|
|
103
|
+
}
|
|
104
|
+
this.sizes[aIndex] = newASize;
|
|
105
|
+
this.sizes[bIndex] = newBSize;
|
|
106
|
+
this.updatePanelStyles();
|
|
107
|
+
};
|
|
108
|
+
this.handleDocumentMouseUp = () => {
|
|
109
|
+
if (this.draggingIndex === -1) return;
|
|
110
|
+
const splitters = this.shadowRoot?.querySelectorAll(".splitter");
|
|
111
|
+
splitters?.[this.draggingIndex]?.classList.remove("active");
|
|
112
|
+
this.draggingIndex = -1;
|
|
113
|
+
this.removeEventListeners();
|
|
114
|
+
document.body.style.userSelect = "";
|
|
115
|
+
document.body.style.cursor = "";
|
|
92
116
|
};
|
|
93
117
|
}
|
|
94
118
|
static {
|
|
95
119
|
this.styles = [super.styles, styles];
|
|
96
120
|
}
|
|
97
121
|
static {
|
|
98
|
-
this.dependencies = {
|
|
99
|
-
"u-divider": UDivider
|
|
100
|
-
};
|
|
122
|
+
this.dependencies = {};
|
|
101
123
|
}
|
|
102
124
|
disconnectedCallback() {
|
|
103
|
-
this.
|
|
125
|
+
this.removeEventListeners();
|
|
104
126
|
super.disconnectedCallback();
|
|
105
127
|
}
|
|
106
128
|
willUpdate(changedProperties) {
|
|
@@ -111,18 +133,42 @@ class USplitPanel extends BaseElement {
|
|
|
111
133
|
}
|
|
112
134
|
render() {
|
|
113
135
|
return html`
|
|
136
|
+
${Array.from({ length: this.splitterCount }, (_, i) => html`
|
|
137
|
+
<div class="splitter" style="order: ${i * 2 + 1}"
|
|
138
|
+
orientation=${this.orientation}
|
|
139
|
+
@mousedown=${(e) => this.handleSplitterMouseDown(e, i)}>
|
|
140
|
+
</div>
|
|
141
|
+
`)}
|
|
114
142
|
<slot @slotchange=${this.handleSlotChange}></slot>
|
|
115
143
|
`;
|
|
116
144
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
145
|
+
/** 패널에 새로운 스타일 적용 */
|
|
146
|
+
updatePanelStyles() {
|
|
147
|
+
const dimension = this.orientation === "horizontal" ? "width" : "height";
|
|
148
|
+
const gutterSize = this.getGutterSize();
|
|
149
|
+
this.panels.forEach((panel, index) => {
|
|
150
|
+
const size = this.sizes[index];
|
|
151
|
+
panel.style[dimension] = `calc(${size}% - ${gutterSize * (this.splitterCount / this.panels.length)}px)`;
|
|
152
|
+
panel.style.order = String(index * 2);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** 전역 이벤트 리스너 제거 */
|
|
156
|
+
removeEventListeners() {
|
|
157
|
+
document.removeEventListener("mousemove", this.handleDocumentMouseMove);
|
|
158
|
+
document.removeEventListener("mouseup", this.handleDocumentMouseUp);
|
|
159
|
+
}
|
|
160
|
+
/** 거터 크기 가져오기 */
|
|
161
|
+
getGutterSize() {
|
|
162
|
+
const sizeStr = getComputedStyle(this).getPropertyValue("--splitter-size").trim();
|
|
163
|
+
return parseFloat(sizeStr) || 4;
|
|
121
164
|
}
|
|
122
165
|
}
|
|
123
166
|
__decorateClass([
|
|
124
167
|
queryAssignedElements({ flatten: true })
|
|
125
|
-
], USplitPanel.prototype, "
|
|
168
|
+
], USplitPanel.prototype, "slotEls");
|
|
169
|
+
__decorateClass([
|
|
170
|
+
state()
|
|
171
|
+
], USplitPanel.prototype, "splitterCount");
|
|
126
172
|
__decorateClass([
|
|
127
173
|
property({ type: String, reflect: true })
|
|
128
174
|
], USplitPanel.prototype, "orientation");
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
2
|
|
|
3
3
|
const styles = css`
|
|
4
|
+
:host {
|
|
5
|
+
--splitter-size: 4px;
|
|
6
|
+
--splitter-color: var(--u-neutral-200, #e5e7eb);
|
|
7
|
+
--splitter-active-color: var(--u-blue-500, #3b82f6);
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
:host {
|
|
5
11
|
position: relative;
|
|
6
12
|
display: flex;
|
|
@@ -14,6 +20,30 @@ const styles = css`
|
|
|
14
20
|
:host([orientation="vertical"]) {
|
|
15
21
|
flex-direction: column;
|
|
16
22
|
}
|
|
23
|
+
|
|
24
|
+
/* Splitter */
|
|
25
|
+
.splitter {
|
|
26
|
+
flex-shrink: 0;
|
|
27
|
+
background-color: var(--splitter-color);
|
|
28
|
+
transition: background-color 0.15s ease;
|
|
29
|
+
}
|
|
30
|
+
.splitter[orientation="horizontal"] {
|
|
31
|
+
width: var(--splitter-size);
|
|
32
|
+
cursor: col-resize;
|
|
33
|
+
}
|
|
34
|
+
.splitter[orientation="vertical"] {
|
|
35
|
+
height: var(--splitter-size);
|
|
36
|
+
cursor: row-resize;
|
|
37
|
+
}
|
|
38
|
+
.splitter:hover,
|
|
39
|
+
.splitter.active {
|
|
40
|
+
background-color: var(--splitter-active-color);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Slotted panels */
|
|
44
|
+
::slotted(*) {
|
|
45
|
+
overflow: auto;
|
|
46
|
+
}
|
|
17
47
|
`;
|
|
18
48
|
|
|
19
49
|
export { styles };
|
|
@@ -1,32 +1,40 @@
|
|
|
1
1
|
import { PropertyValues } from 'lit';
|
|
2
2
|
import { BaseElement } from '../BaseElement.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* UTreeItem 컴포넌트는 트리의 개별 노드를 나타냅니다.
|
|
5
|
+
* UTree 컴포넌트와 함께 사용되며, 중첩된 구조를 지원합니다.
|
|
6
6
|
*/
|
|
7
7
|
export declare class UTreeItem extends BaseElement {
|
|
8
8
|
static styles: import('lit').CSSResultGroup[];
|
|
9
9
|
static dependencies: Record<string, typeof BaseElement>;
|
|
10
|
+
/** DOM 변경 감지를 위한 MutationObserver */
|
|
11
|
+
private mutationObserver?;
|
|
12
|
+
/** 자식 트리 아이템 배열 */
|
|
13
|
+
private _childrenItems;
|
|
14
|
+
/** 자식 트리 아이템 배열 (읽기 전용) */
|
|
15
|
+
get childrenItems(): readonly UTreeItem[];
|
|
10
16
|
headerEl: HTMLElement;
|
|
11
|
-
childrenItems: UTreeItem[];
|
|
12
17
|
/** 트리 항목이 리프 노드인지 여부입니다. */
|
|
13
18
|
leaf: boolean;
|
|
14
19
|
/** 트리 항목의 들여쓰기 레벨입니다. */
|
|
15
20
|
level: number;
|
|
21
|
+
/** 트리 항목이 확장된 상태인지 여부입니다. */
|
|
22
|
+
expanded: boolean;
|
|
16
23
|
/** 트리 항목이 비활성화 상태인지 여부입니다. */
|
|
17
24
|
disabled: boolean;
|
|
25
|
+
/** 로딩 상태 @default false */
|
|
26
|
+
loading: boolean;
|
|
18
27
|
/** 트리 항목이 선택된 상태인지 여부입니다. */
|
|
19
28
|
selected: boolean;
|
|
20
|
-
/** 트리 항목이 확장된 상태인지 여부입니다. */
|
|
21
|
-
expanded: boolean;
|
|
22
29
|
/** 트리 항목의 값입니다. */
|
|
23
30
|
value: string;
|
|
24
31
|
connectedCallback(): void;
|
|
32
|
+
disconnectedCallback(): void;
|
|
25
33
|
protected updated(changedProperties: PropertyValues): void;
|
|
26
34
|
render(): import('lit-html').TemplateResult<1>;
|
|
27
|
-
/**
|
|
28
|
-
private
|
|
29
|
-
/** 자식 슬롯 변경 시
|
|
35
|
+
/** 자식 u-tree-item 요소들에 slot 속성 부여 */
|
|
36
|
+
private processChildren;
|
|
37
|
+
/** 자식 슬롯 변경 시 처리 */
|
|
30
38
|
private handleChildrenSlotChange;
|
|
31
39
|
/** 헤더 클릭 이벤트를 처리합니다. */
|
|
32
40
|
private handleHeaderClick;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { html } from 'lit';
|
|
2
|
-
import { query,
|
|
2
|
+
import { query, state, property } from 'lit/decorators.js';
|
|
3
3
|
import { BaseElement } from '../BaseElement.js';
|
|
4
4
|
import { UIcon } from '../icon/UIcon.component.js';
|
|
5
5
|
import { styles } from './UTreeItem.styles.js';
|
|
6
|
+
import { USpinner } from '../spinner/USpinner.component.js';
|
|
6
7
|
|
|
7
8
|
var __defProp = Object.defineProperty;
|
|
8
9
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
@@ -13,29 +14,27 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
13
14
|
if (result) __defProp(target, key, result);
|
|
14
15
|
return result;
|
|
15
16
|
};
|
|
16
|
-
|
|
17
|
+
class UTreeItem extends BaseElement {
|
|
17
18
|
constructor() {
|
|
18
19
|
super(...arguments);
|
|
20
|
+
/** 자식 트리 아이템 배열 */
|
|
21
|
+
this._childrenItems = [];
|
|
19
22
|
this.leaf = true;
|
|
20
23
|
this.level = 0;
|
|
24
|
+
this.expanded = false;
|
|
21
25
|
this.disabled = false;
|
|
26
|
+
this.loading = false;
|
|
22
27
|
this.selected = false;
|
|
23
|
-
this.expanded = false;
|
|
24
28
|
this.value = "";
|
|
25
|
-
/** 슬롯 변경 시
|
|
26
|
-
this.
|
|
29
|
+
/** 자식 슬롯 변경 시 처리 */
|
|
30
|
+
this.handleChildrenSlotChange = (e) => {
|
|
27
31
|
const slot = e.target;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
35
|
-
/** 자식 슬롯 변경 시 자식 노드 재설정 */
|
|
36
|
-
this.handleChildrenSlotChange = () => {
|
|
37
|
-
this.leaf = this.childrenItems.length === 0;
|
|
38
|
-
this.childrenItems.forEach((child) => {
|
|
32
|
+
const assignedElements = slot.assignedElements({ flatten: true });
|
|
33
|
+
this._childrenItems = assignedElements.filter(
|
|
34
|
+
(el) => el.tagName.toLowerCase() === "u-tree-item"
|
|
35
|
+
);
|
|
36
|
+
this.leaf = this._childrenItems.length === 0;
|
|
37
|
+
this._childrenItems.forEach((child) => {
|
|
39
38
|
child.level = this.level + 1;
|
|
40
39
|
});
|
|
41
40
|
};
|
|
@@ -60,12 +59,34 @@ const _UTreeItem = class _UTreeItem extends BaseElement {
|
|
|
60
59
|
}
|
|
61
60
|
static {
|
|
62
61
|
this.dependencies = {
|
|
63
|
-
"u-icon": UIcon
|
|
62
|
+
"u-icon": UIcon,
|
|
63
|
+
"u-spinner": USpinner
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
+
/** 자식 트리 아이템 배열 (읽기 전용) */
|
|
67
|
+
get childrenItems() {
|
|
68
|
+
return this._childrenItems;
|
|
69
|
+
}
|
|
66
70
|
connectedCallback() {
|
|
67
71
|
super.connectedCallback();
|
|
68
72
|
this.setAttribute("tabindex", this.disabled ? "-1" : "0");
|
|
73
|
+
this.mutationObserver = new MutationObserver((mutations) => {
|
|
74
|
+
for (const mutation of mutations) {
|
|
75
|
+
if (mutation.type === "childList") {
|
|
76
|
+
mutation.addedNodes.forEach((node) => {
|
|
77
|
+
if (node instanceof Element && node.tagName.toLowerCase() === "u-tree-item" && !node.hasAttribute("slot")) {
|
|
78
|
+
node.setAttribute("slot", "children");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
this.mutationObserver.observe(this, { childList: true });
|
|
85
|
+
this.processChildren();
|
|
86
|
+
}
|
|
87
|
+
disconnectedCallback() {
|
|
88
|
+
super.disconnectedCallback();
|
|
89
|
+
this.mutationObserver?.disconnect();
|
|
69
90
|
}
|
|
70
91
|
updated(changedProperties) {
|
|
71
92
|
super.updated(changedProperties);
|
|
@@ -73,7 +94,7 @@ const _UTreeItem = class _UTreeItem extends BaseElement {
|
|
|
73
94
|
this.setAttribute("tabindex", this.disabled ? "-1" : "0");
|
|
74
95
|
}
|
|
75
96
|
if (changedProperties.has("level")) {
|
|
76
|
-
this.
|
|
97
|
+
this.style.setProperty("--indent-level", this.level.toString());
|
|
77
98
|
}
|
|
78
99
|
}
|
|
79
100
|
render() {
|
|
@@ -84,40 +105,57 @@ const _UTreeItem = class _UTreeItem extends BaseElement {
|
|
|
84
105
|
lib="internal"
|
|
85
106
|
name=${this.expanded ? "chevron-down" : "chevron-right"}
|
|
86
107
|
></u-icon>
|
|
108
|
+
|
|
109
|
+
<u-spinner class="prefix icon"
|
|
110
|
+
?hidden=${!this.loading}
|
|
111
|
+
></u-spinner>
|
|
112
|
+
|
|
87
113
|
<slot name="prefix"></slot>
|
|
88
|
-
|
|
114
|
+
|
|
115
|
+
<span class="label">
|
|
116
|
+
<slot></slot>
|
|
117
|
+
</span>
|
|
118
|
+
|
|
89
119
|
<slot name="suffix"></slot>
|
|
90
120
|
</div>
|
|
121
|
+
|
|
91
122
|
<div class="children" ?hidden=${!this.expanded}>
|
|
92
123
|
<slot name="children" @slotchange=${this.handleChildrenSlotChange}></slot>
|
|
93
124
|
</div>
|
|
94
125
|
`;
|
|
95
126
|
}
|
|
96
|
-
|
|
127
|
+
/** 자식 u-tree-item 요소들에 slot 속성 부여 */
|
|
128
|
+
processChildren() {
|
|
129
|
+
Array.from(this.children).forEach((child) => {
|
|
130
|
+
if (child.tagName.toLowerCase() === "u-tree-item" && !child.hasAttribute("slot")) {
|
|
131
|
+
child.setAttribute("slot", "children");
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
97
136
|
__decorateClass([
|
|
98
137
|
query(".header")
|
|
99
|
-
],
|
|
100
|
-
__decorateClass([
|
|
101
|
-
queryAssignedElements({ slot: "children", selector: "u-tree-item", flatten: true })
|
|
102
|
-
], _UTreeItem.prototype, "childrenItems");
|
|
138
|
+
], UTreeItem.prototype, "headerEl");
|
|
103
139
|
__decorateClass([
|
|
104
140
|
state()
|
|
105
|
-
],
|
|
141
|
+
], UTreeItem.prototype, "leaf");
|
|
106
142
|
__decorateClass([
|
|
107
143
|
state()
|
|
108
|
-
],
|
|
144
|
+
], UTreeItem.prototype, "level");
|
|
145
|
+
__decorateClass([
|
|
146
|
+
property({ type: Boolean, reflect: true })
|
|
147
|
+
], UTreeItem.prototype, "expanded");
|
|
109
148
|
__decorateClass([
|
|
110
149
|
property({ type: Boolean, reflect: true })
|
|
111
|
-
],
|
|
150
|
+
], UTreeItem.prototype, "disabled");
|
|
112
151
|
__decorateClass([
|
|
113
152
|
property({ type: Boolean, reflect: true })
|
|
114
|
-
],
|
|
153
|
+
], UTreeItem.prototype, "loading");
|
|
115
154
|
__decorateClass([
|
|
116
155
|
property({ type: Boolean, reflect: true })
|
|
117
|
-
],
|
|
156
|
+
], UTreeItem.prototype, "selected");
|
|
118
157
|
__decorateClass([
|
|
119
158
|
property({ type: String })
|
|
120
|
-
],
|
|
121
|
-
let UTreeItem = _UTreeItem;
|
|
159
|
+
], UTreeItem.prototype, "value");
|
|
122
160
|
|
|
123
161
|
export { UTreeItem };
|
|
@@ -2,10 +2,13 @@ import { css } from 'lit';
|
|
|
2
2
|
|
|
3
3
|
const styles = css`
|
|
4
4
|
:host {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
--indent-level: 0;
|
|
7
6
|
--indent-size: 20px;
|
|
8
7
|
}
|
|
8
|
+
|
|
9
|
+
:host {
|
|
10
|
+
display: block;
|
|
11
|
+
}
|
|
9
12
|
:host([disabled]) {
|
|
10
13
|
opacity: 0.5;
|
|
11
14
|
pointer-events: none;
|
|
@@ -22,6 +25,7 @@ const styles = css`
|
|
|
22
25
|
align-items: center;
|
|
23
26
|
gap: 6px;
|
|
24
27
|
padding: 6px 8px;
|
|
28
|
+
padding-left: calc(var(--indent-level, 0) * var(--indent-size));
|
|
25
29
|
line-height: 1.5;
|
|
26
30
|
border-radius: 4px;
|
|
27
31
|
background-color: transparent;
|
|
@@ -48,7 +52,8 @@ const styles = css`
|
|
|
48
52
|
align-items: center;
|
|
49
53
|
justify-content: center;
|
|
50
54
|
}
|
|
51
|
-
|
|
55
|
+
|
|
56
|
+
.label {
|
|
52
57
|
flex: 1;
|
|
53
58
|
display: inline-flex;
|
|
54
59
|
align-items: center;
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import './components/tree/UTree.js';
|
|
|
19
19
|
import './components/tree-item/UTreeItem.js';
|
|
20
20
|
export { BrowserStorage } from './utilities/BrowserStorage.js';
|
|
21
21
|
export { notifier } from './utilities/notifier.js';
|
|
22
|
-
export { theme } from './utilities/theme.js';
|
|
22
|
+
export { Theme, theme } from './utilities/theme.js';
|
|
23
23
|
export { UAlert } from './components/alert/UAlert.component.js';
|
|
24
24
|
export { UButton } from './components/button/UButton.component.js';
|
|
25
25
|
export { UDialog } from './components/dialog/UDialog.component.js';
|
|
@@ -27,7 +27,7 @@ export interface ThemeInitOptions {
|
|
|
27
27
|
/**
|
|
28
28
|
* 현재 문서에 테마를 적용하고 관리하는 유틸리티 클래스입니다.
|
|
29
29
|
*/
|
|
30
|
-
declare class Theme {
|
|
30
|
+
export declare class Theme {
|
|
31
31
|
private static _instance;
|
|
32
32
|
private readonly STORAGE_THEME_KEY;
|
|
33
33
|
private storage;
|
|
@@ -58,4 +58,3 @@ declare class Theme {
|
|
|
58
58
|
* 테마 유틸리티의 싱글톤 인스턴스입니다.
|
|
59
59
|
*/
|
|
60
60
|
export declare const theme: Theme;
|
|
61
|
-
export {};
|
package/dist/utilities/theme.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/components",
|
|
3
3
|
"description": "web-components library based on lit-element made by iyulab",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.11",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iyulab",
|
|
7
7
|
"components",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@floating-ui/dom": "^1.7.4",
|
|
43
|
-
"lit": "^3.3.
|
|
43
|
+
"lit": "^3.3.2",
|
|
44
44
|
"react": "^19.2.3",
|
|
45
45
|
"reflect-metadata": "^0.2.2"
|
|
46
46
|
},
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"eslint-plugin-lit": "^2.1.1",
|
|
53
53
|
"globals": "^16.5.0",
|
|
54
54
|
"typescript": "^5.9.3",
|
|
55
|
-
"typescript-eslint": "^8.50.
|
|
55
|
+
"typescript-eslint": "^8.50.1",
|
|
56
56
|
"vite": "^7.3.0",
|
|
57
57
|
"vite-plugin-dts": "^4.5.4",
|
|
58
58
|
"vite-plugin-static-copy": "^3.1.4"
|