@iyulab/components 0.1.10 → 0.2.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 (77) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/assets/icons/arrow-repeat.svg.js +3 -0
  3. package/dist/assets/icons/box-arrow-up-right.svg.js +3 -0
  4. package/dist/assets/icons/file-earmark.svg.js +3 -0
  5. package/dist/assets/icons/flag.svg.js +3 -0
  6. package/dist/assets/icons/globe.svg.js +3 -0
  7. package/dist/assets/icons/hand-thumbs-down-fill.svg.js +3 -0
  8. package/dist/assets/icons/hand-thumbs-down.svg.js +3 -0
  9. package/dist/assets/icons/hand-thumbs-up-fill.svg.js +3 -0
  10. package/dist/assets/icons/hand-thumbs-up.svg.js +3 -0
  11. package/dist/assets/icons/share.svg.js +3 -0
  12. package/dist/assets/icons/stop-circle.svg.js +3 -0
  13. package/dist/assets/icons/tools.svg.js +3 -0
  14. package/dist/components/FloatingElement.d.ts +13 -5
  15. package/dist/components/FloatingElement.js +32 -15
  16. package/dist/components/FloatingElement.styles.js +0 -4
  17. package/dist/components/button/UButton.component.d.ts +7 -0
  18. package/dist/components/button/UButton.component.js +20 -1
  19. package/dist/components/button/UButton.styles.js +51 -8
  20. package/dist/components/dialog/UDialog.component.d.ts +2 -0
  21. package/dist/components/dialog/UDialog.component.js +14 -9
  22. package/dist/components/divider/UDivider.component.d.ts +3 -18
  23. package/dist/components/divider/UDivider.component.js +3 -62
  24. package/dist/components/divider/UDivider.styles.js +11 -43
  25. package/dist/components/drawer/UDrawer.component.d.ts +2 -0
  26. package/dist/components/drawer/UDrawer.component.js +14 -9
  27. package/dist/components/form/UForm.component.js +3 -3
  28. package/dist/components/icon/UIcon.component.d.ts +4 -1
  29. package/dist/components/icon/UIcon.component.js +2 -2
  30. package/dist/components/menu/UMenu.component.d.ts +4 -7
  31. package/dist/components/menu/UMenu.component.js +94 -83
  32. package/dist/components/menu/UMenu.styles.js +1 -1
  33. package/dist/components/menu-item/UMenuItem.component.d.ts +10 -4
  34. package/dist/components/menu-item/UMenuItem.component.js +37 -26
  35. package/dist/components/menu-item/UMenuItem.styles.js +4 -4
  36. package/dist/components/split-panel/USplitPanel.component.d.ts +22 -8
  37. package/dist/components/split-panel/USplitPanel.component.js +121 -75
  38. package/dist/components/split-panel/USplitPanel.styles.js +30 -0
  39. package/dist/components/tag/UTag.component.d.ts +23 -0
  40. package/dist/components/tag/UTag.component.js +72 -0
  41. package/dist/components/tag/UTag.d.ts +8 -0
  42. package/dist/components/tag/UTag.js +5 -0
  43. package/dist/components/tag/UTag.styles.js +83 -0
  44. package/dist/components/tooltip/UTooltip.component.d.ts +8 -0
  45. package/dist/components/tooltip/UTooltip.component.js +59 -17
  46. package/dist/components/tooltip/UTooltip.styles.js +15 -0
  47. package/dist/components/tree-item/UTreeItem.component.d.ts +16 -8
  48. package/dist/components/tree-item/UTreeItem.component.js +69 -31
  49. package/dist/components/tree-item/UTreeItem.styles.js +8 -3
  50. package/dist/index.d.ts +4 -3
  51. package/dist/index.js +5 -4
  52. package/dist/react-components/UTag.d.ts +8 -0
  53. package/dist/react-components/UTag.js +10 -0
  54. package/dist/react-components/index.d.ts +1 -1
  55. package/dist/react-components/index.js +1 -1
  56. package/dist/utilities/{icons.d.ts → IconRegistry.d.ts} +20 -26
  57. package/dist/utilities/{icons.js → IconRegistry.js} +29 -20
  58. package/dist/utilities/{notifier.d.ts → Notifier.d.ts} +8 -13
  59. package/dist/utilities/{notifier.js → Notifier.js} +8 -10
  60. package/dist/utilities/{theme.d.ts → Theme.d.ts} +14 -20
  61. package/dist/utilities/{theme.js → Theme.js} +39 -35
  62. package/dist/{internals/attribute-converters.d.ts → utilities/converters.d.ts} +1 -1
  63. package/dist/{internals/attribute-converters.js → utilities/converters.js} +2 -2
  64. package/dist/{internals/node-helpers.js → utilities/elements.js} +6 -5
  65. package/package.json +7 -7
  66. package/dist/assets/icons/arrow-clockwise.svg.js +0 -3
  67. package/dist/assets/icons/speedometer.svg.js +0 -3
  68. package/dist/components/icon-button/UIconButton.component.d.ts +0 -20
  69. package/dist/components/icon-button/UIconButton.component.js +0 -59
  70. package/dist/components/icon-button/UIconButton.d.ts +0 -7
  71. package/dist/components/icon-button/UIconButton.js +0 -5
  72. package/dist/components/icon-button/UIconButton.styles.js +0 -54
  73. package/dist/internals/react-wrapper.d.ts +0 -21
  74. package/dist/react-components/UIconButton.d.ts +0 -8
  75. package/dist/react-components/UIconButton.js +0 -10
  76. /package/dist/components/{icon-button/UIconButton.styles.d.ts → tag/UTag.styles.d.ts} +0 -0
  77. /package/dist/{internals/node-helpers.d.ts → utilities/elements.d.ts} +0 -0
@@ -2,53 +2,21 @@ import { css } from 'lit';
2
2
 
3
3
  const styles = css`
4
4
  :host {
5
- --divider-size: 2px;
6
- --handler-size: 4px;
7
- --handler-color: var(--u-blue-500, #3b82f6);
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(--handler-color, #3b82f6);
28
- opacity: 0;
29
- transition: opacity 0.2s ease;
30
- }
31
- .handler[orientation="horizontal"] {
32
- top: 0;
33
- bottom: 0;
34
- left: 50%;
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
 
@@ -10,6 +10,8 @@ export declare class UDrawer extends ModalElement {
10
10
  static dependencies: Record<string, typeof BaseElement>;
11
11
  /** 드로어가 나타나는 위치 */
12
12
  placement: DrawerPlacement;
13
+ /** 헤드리스 모드 여부 */
14
+ headless: boolean;
13
15
  /** 타이틀 텍스트 */
14
16
  heading: string;
15
17
  render(): import('lit-html').TemplateResult<1>;
@@ -1,7 +1,8 @@
1
1
  import { html } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
3
  import { ModalElement } from '../ModalElement.js';
4
- import { UIconButton } from '../icon-button/UIconButton.component.js';
4
+ import { UIcon } from '../icon/UIcon.component.js';
5
+ import { UButton } from '../button/UButton.component.js';
5
6
  import { styles } from './UDrawer.styles.js';
6
7
 
7
8
  var __defProp = Object.defineProperty;
@@ -17,6 +18,7 @@ class UDrawer extends ModalElement {
17
18
  constructor() {
18
19
  super(...arguments);
19
20
  this.placement = "left";
21
+ this.headless = false;
20
22
  this.heading = "";
21
23
  }
22
24
  static {
@@ -24,22 +26,22 @@ class UDrawer extends ModalElement {
24
26
  }
25
27
  static {
26
28
  this.dependencies = {
27
- "u-icon-button": UIconButton
29
+ "u-icon": UIcon,
30
+ "u-button": UButton
28
31
  };
29
32
  }
30
33
  render() {
31
34
  return html`
32
35
  <div class="drawer" part="base" ?open=${this.open} placement=${this.placement}>
33
- <div class="header" part="header" ?hidden=${!this.heading}>
36
+ <div class="header" part="header" ?hidden=${this.headless}>
34
37
  <span class="title" part="title">
35
38
  ${this.heading}
36
39
  </span>
37
- <u-icon-button class="close-btn" part="close-btn"
38
- borderless
39
- lib="internal"
40
- name="x-lg"
41
- @click=${() => this.hide()}
42
- ></u-icon-button>
40
+ <u-button class="close-btn" part="close-btn"
41
+ variant="link"
42
+ @click=${() => this.hide()}>
43
+ <u-icon lib="internal" name="x-lg"></u-icon>
44
+ </u-button>
43
45
  </div>
44
46
 
45
47
  <div class="content" part="content" scrollable>
@@ -52,6 +54,9 @@ class UDrawer extends ModalElement {
52
54
  __decorateClass([
53
55
  property({ type: String, reflect: true })
54
56
  ], UDrawer.prototype, "placement");
57
+ __decorateClass([
58
+ property({ type: Boolean, reflect: true })
59
+ ], UDrawer.prototype, "headless");
55
60
  __decorateClass([
56
61
  property({ type: String })
57
62
  ], UDrawer.prototype, "heading");
@@ -1,6 +1,6 @@
1
1
  import { html } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
- import { arrayAttributeConverter } from '../../internals/attribute-converters.js';
3
+ import { arrayAttrConverter } from '../../utilities/converters.js';
4
4
  import { BaseElement } from '../BaseElement.js';
5
5
  import { UInput } from '../input/UInput.component.js';
6
6
  import { styles } from './UForm.styles.js';
@@ -64,10 +64,10 @@ class UForm extends BaseElement {
64
64
  }
65
65
  }
66
66
  __decorateClass([
67
- property({ type: Array, converter: arrayAttributeConverter((v) => v) })
67
+ property({ type: Array, converter: arrayAttrConverter((v) => v) })
68
68
  ], UForm.prototype, "includes");
69
69
  __decorateClass([
70
- property({ type: Array, converter: arrayAttributeConverter((v) => v) })
70
+ property({ type: Array, converter: arrayAttrConverter((v) => v) })
71
71
  ], UForm.prototype, "excludes");
72
72
  __decorateClass([
73
73
  property({ type: Object, attribute: false })
@@ -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<typeof import('lit-html/directives/until.js').UntilDirective>;
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
  */
@@ -3,7 +3,7 @@ import { property } from 'lit/decorators.js';
3
3
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
4
4
  import { until } from 'lit/directives/until.js';
5
5
  import { BaseElement } from '../BaseElement.js';
6
- import { icons } from '../../utilities/icons.js';
6
+ import { IconRegistry } from '../../utilities/IconRegistry.js';
7
7
  import { styles } from './UIcon.styles.js';
8
8
 
9
9
  var __defProp = Object.defineProperty;
@@ -28,7 +28,7 @@ class UIcon extends BaseElement {
28
28
  }
29
29
  render() {
30
30
  if (!this.lib || !this.name) return nothing;
31
- return until(icons.resolve(this.lib, this.name).then((html) => {
31
+ return until(IconRegistry.resolve(this.lib, this.name).then((html) => {
32
32
  return this.validate(html) ? unsafeHTML(html) : nothing;
33
33
  }), nothing);
34
34
  }
@@ -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 getEnabledItemFrom;
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.items || [];
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 = this.getEnabledItemFrom(e);
38
- if (!item || item.isNested) return;
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 = this.getEnabledItemFrom(e);
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,21 @@ 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") {
93
- this.anchor?.focus();
94
+ e.preventDefault();
95
+ e.stopPropagation();
96
+ await this.hide();
97
+ if (this.target instanceof HTMLElement) {
98
+ this.target.focus();
99
+ }
94
100
  }
95
101
  break;
96
102
  case "ArrowRight":
97
- e.preventDefault();
98
- e.stopPropagation();
99
- const menu = item.querySelector("u-menu");
100
- if (menu?.type === "submenu") {
101
- await menu?.focusAt(0);
103
+ if (item.submenu) {
104
+ e.preventDefault();
105
+ e.stopPropagation();
106
+ await item.submenu.show(item);
107
+ await item.submenu.focusAt(0);
102
108
  }
103
109
  break;
104
110
  }
@@ -121,51 +127,31 @@ class UMenu extends FloatingElement {
121
127
  }
122
128
  await this.hide();
123
129
  } else if (this.type === "dropdown") {
124
- const anchor = this.anchor;
125
- if (!anchor) return;
130
+ const currentTarget = this.target;
131
+ if (!currentTarget || !(currentTarget instanceof HTMLElement)) return;
126
132
  const targets = e.composedPath().filter((v) => v instanceof HTMLElement);
127
133
  for (const target of targets) {
128
- if (this.contains(target) || anchor.contains(target)) return;
134
+ if (this.contains(target) || currentTarget.contains(target)) return;
129
135
  }
130
136
  await this.hide();
131
137
  }
132
138
  };
133
139
  //#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
140
  //#region 'dropdown' 이벤트 핸들러
157
141
  this.handleAnchorPointerDown = async (e) => {
158
142
  if (e.button !== 0) return;
159
- if (this.visible)
143
+ const target = e.currentTarget;
144
+ if (this.visible && this.target === target)
160
145
  await this.hide();
161
146
  else
162
- await this.show();
147
+ await this.show(target);
163
148
  };
164
149
  this.handleAnchorKeydown = async (e) => {
165
150
  if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
166
151
  e.preventDefault();
152
+ const target = e.currentTarget;
167
153
  if (this.visible) return;
168
- await this.show();
154
+ await this.show(target);
169
155
  }
170
156
  };
171
157
  //#endregion
@@ -187,14 +173,40 @@ class UMenu extends FloatingElement {
187
173
  };
188
174
  await this.show(virtual, false);
189
175
  };
176
+ //#endregion
177
+ //#region 'submenu' 이벤트 핸들러
178
+ this.handleSubmenuAnchorPointerEnter = (e) => {
179
+ const target = e.currentTarget;
180
+ if (target?.disabled) return;
181
+ this.show(target);
182
+ };
183
+ this.handleSubmenuAnchorPointerLeave = (e) => {
184
+ const related = e.relatedTarget;
185
+ if (related) {
186
+ const target = this.target;
187
+ if (target?.contains(related) || this.contains(related)) return;
188
+ }
189
+ this.hide();
190
+ };
191
+ this.handleSubmenuAnchorFocusIn = (e) => {
192
+ const target = e.currentTarget;
193
+ if (target?.disabled) return;
194
+ this.show(target);
195
+ };
196
+ this.handleSubmenuAnchorFocusOut = (e) => {
197
+ const related = e.relatedTarget;
198
+ if (related) {
199
+ const target = this.target;
200
+ if (target?.contains(related) || this.contains(related)) return;
201
+ }
202
+ this.hide();
203
+ };
190
204
  }
191
205
  static {
192
206
  this.styles = [super.styles, styles];
193
207
  }
194
208
  static {
195
- this.dependencies = {
196
- "u-menu-item": UMenuItem
197
- };
209
+ this.dependencies = {};
198
210
  }
199
211
  connectedCallback() {
200
212
  super.connectedCallback();
@@ -208,13 +220,13 @@ class UMenu extends FloatingElement {
208
220
  }
209
221
  willUpdate(changedProperties) {
210
222
  super.willUpdate(changedProperties);
211
- if (changedProperties.has("anchor") || changedProperties.has("type")) {
223
+ if (changedProperties.has("anchors") || changedProperties.has("type")) {
212
224
  const oldType = changedProperties.get("type") || "default";
213
225
  const newType = this.type || "default";
214
- const oldAnchor = changedProperties.get("anchor");
215
- const newAnchor = this.anchor;
216
- if (oldAnchor) this.unbind(oldType, oldAnchor);
217
- if (newAnchor) this.bind(newType, newAnchor);
226
+ const oldAnchors = changedProperties.get("anchors");
227
+ const newAnchors = this.anchors;
228
+ if (oldAnchors) this.unbind(oldType, oldAnchors);
229
+ if (newAnchors) this.bind(newType, newAnchors);
218
230
  }
219
231
  if (changedProperties.has("mode")) {
220
232
  this.clearAll();
@@ -228,13 +240,12 @@ class UMenu extends FloatingElement {
228
240
  * 재귀적으로 서브메뉴의 아이템들도 해제됩니다.
229
241
  */
230
242
  clearAll() {
231
- const items = this.items || [];
243
+ const items = this.getItems("all");
232
244
  for (const item of items) {
233
245
  item.selected = false;
234
246
  item.checked = false;
235
- if (item.isNested) {
236
- const submenu = item.querySelector("u-menu");
237
- submenu?.clearAll();
247
+ if (item.submenu) {
248
+ item.submenu.clearAll();
238
249
  }
239
250
  }
240
251
  }
@@ -245,7 +256,7 @@ class UMenu extends FloatingElement {
245
256
  * @return 조건에 맞는 아이템들의 배열입니다
246
257
  */
247
258
  getItems(filter = "all") {
248
- const items = this.items || [];
259
+ const items = this.items?.filter((item) => item instanceof UMenuItem) || [];
249
260
  switch (filter) {
250
261
  case "all":
251
262
  return items;
@@ -266,24 +277,29 @@ class UMenu extends FloatingElement {
266
277
  }
267
278
  }
268
279
  /** 앵커에 이벤트 바인딩 */
269
- bind(type, target) {
270
- if (type === "submenu") {
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") {
280
+ bind(type, anchors) {
281
+ if (type === "dropdown") {
279
282
  this.visible = false;
280
283
  this.placement ||= "bottom-start";
281
- target.addEventListener("pointerdown", this.handleAnchorPointerDown);
282
- target.addEventListener("keydown", this.handleAnchorKeydown);
284
+ for (const anchor of anchors) {
285
+ anchor.addEventListener("pointerdown", this.handleAnchorPointerDown);
286
+ anchor.addEventListener("keydown", this.handleAnchorKeydown);
287
+ }
283
288
  } else if (type === "contextmenu") {
284
289
  this.visible = false;
285
290
  this.placement ||= "bottom-start";
286
- target.addEventListener("contextmenu", this.handleAnchorContextMenu);
291
+ for (const anchor of anchors) {
292
+ anchor.addEventListener("contextmenu", this.handleAnchorContextMenu);
293
+ }
294
+ } else if (type === "submenu") {
295
+ this.visible = false;
296
+ this.placement ||= "right-start";
297
+ for (const anchor of anchors) {
298
+ anchor.addEventListener("pointerenter", this.handleSubmenuAnchorPointerEnter);
299
+ anchor.addEventListener("pointerleave", this.handleSubmenuAnchorPointerLeave);
300
+ anchor.addEventListener("focusin", this.handleSubmenuAnchorFocusIn);
301
+ anchor.addEventListener("focusout", this.handleSubmenuAnchorFocusOut);
302
+ }
287
303
  } else {
288
304
  this.visible = true;
289
305
  return;
@@ -293,29 +309,24 @@ class UMenu extends FloatingElement {
293
309
  window.addEventListener("pointerdown", this.handleWindowPointerDown);
294
310
  }
295
311
  /** 앵커에서 이벤트 바인딩 해제 */
296
- unbind(_, target) {
312
+ unbind(_, anchors) {
297
313
  window.removeEventListener("focusout", this.handleWindowFocusOut);
298
314
  window.removeEventListener("keydown", this.handleWindowKeydown);
299
315
  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
- target.removeEventListener("pointerdown", this.handleAnchorPointerDown);
305
- target.removeEventListener("keydown", this.handleAnchorKeydown);
306
- target.removeEventListener("contextmenu", this.handleAnchorContextMenu);
316
+ for (const anchor of anchors) {
317
+ anchor.removeEventListener("pointerdown", this.handleAnchorPointerDown);
318
+ anchor.removeEventListener("keydown", this.handleAnchorKeydown);
319
+ anchor.removeEventListener("contextmenu", this.handleAnchorContextMenu);
320
+ anchor.removeEventListener("pointerenter", this.handleSubmenuAnchorPointerEnter);
321
+ anchor.removeEventListener("pointerleave", this.handleSubmenuAnchorPointerLeave);
322
+ anchor.removeEventListener("focusin", this.handleSubmenuAnchorFocusIn);
323
+ anchor.removeEventListener("focusout", this.handleSubmenuAnchorFocusOut);
324
+ }
307
325
  }
308
326
  //#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
327
  }
317
328
  __decorateClass([
318
- queryAssignedElements({ flatten: false, selector: "u-menu-item" })
329
+ queryAssignedElements({ flatten: false })
319
330
  ], UMenu.prototype, "items");
320
331
  __decorateClass([
321
332
  property({ type: String, reflect: true })
@@ -22,7 +22,7 @@ const styles = css`
22
22
  opacity: 1;
23
23
  }
24
24
 
25
- /* dropdown/contextmenu/submenu 타입: 플로팅 메뉴 */
25
+ /* dropdown/contextmenu 타입: 플로팅 메뉴 */
26
26
  :host([type="dropdown"]),
27
27
  :host([type="contextmenu"]),
28
28
  :host([type="submenu"]) {
@@ -1,19 +1,25 @@
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
- isNested: boolean;
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 */
13
16
  selected: boolean;
14
- /** 값 */
15
- value: string;
17
+ /** 메뉴 아이템 값 */
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,29 @@ var __decorateClass = (decorators, target, key, kind) => {
15
18
  class UMenuItem extends BaseElement {
16
19
  constructor() {
17
20
  super(...arguments);
18
- this.isNested = false;
21
+ this.submenu = null;
19
22
  this.disabled = false;
23
+ this.loading = false;
20
24
  this.checked = false;
21
25
  this.selected = false;
22
- this.value = "";
26
+ /** 기본 슬롯 변경 감지 */
27
+ this.handleSlotChange = (e) => {
28
+ const slot = e.target;
29
+ const elements = slot.assignedElements({ flatten: true });
30
+ const menu = elements.find((el) => el instanceof UMenu);
31
+ menu?.setAttribute("slot", "submenu");
32
+ };
33
+ /** submenu 슬롯 변경 감지 */
23
34
  this.handleSubmenuSlotChange = (e) => {
24
35
  const slot = e.target;
25
36
  const elements = slot.assignedElements({ flatten: true });
26
- if (elements.length !== 1) {
27
- console.warn(`when using submenu, there must be exactly one UMenu component in the 'submenu' slot.`);
28
- this.isNested = false;
37
+ const menu = elements.find((el) => el instanceof UMenu);
38
+ if (menu) {
39
+ menu.type = "submenu";
40
+ menu.anchors = [this];
41
+ this.submenu = menu;
29
42
  } else {
30
- const submenu = elements[0];
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
- }
43
+ this.submenu = null;
39
44
  }
40
45
  };
41
46
  }
@@ -43,13 +48,15 @@ class UMenuItem extends BaseElement {
43
48
  this.styles = [super.styles, styles];
44
49
  }
45
50
  static {
46
- this.dependencies = {};
51
+ this.dependencies = {
52
+ "u-icon": UIcon,
53
+ "u-spinner": USpinner
54
+ };
47
55
  }
48
56
  updated(changedProperties) {
49
57
  super.updated(changedProperties);
50
58
  if (changedProperties.has("disabled")) {
51
- const tabIndex = this.disabled ? "-1" : "0";
52
- this.setAttribute("tabindex", tabIndex);
59
+ this.setAttribute("tabindex", this.disabled ? "-1" : "0");
53
60
  }
54
61
  }
55
62
  render() {
@@ -57,36 +64,40 @@ class UMenuItem extends BaseElement {
57
64
  <u-icon class="prefix icon"
58
65
  ?hidden=${!this.checked}
59
66
  lib="internal"
60
- name="check-lg"
67
+ name="check"
61
68
  ></u-icon>
62
69
 
70
+ <u-spinner class="prefix icon"
71
+ ?hidden=${!this.loading}
72
+ ></u-spinner>
73
+
63
74
  <slot name="prefix"></slot>
64
75
 
65
- <span class="content">
66
- <slot></slot>
76
+ <span class="label">
77
+ <slot @slotchange=${this.handleSlotChange}></slot>
67
78
  </span>
68
79
 
69
80
  <slot name="suffix"></slot>
70
81
 
71
82
  <u-icon class="suffix icon"
72
- ?hidden=${!this.isNested}
83
+ ?hidden=${this.submenu === null}
73
84
  lib="internal"
74
85
  name="chevron-right"
75
86
  ></u-icon>
76
87
 
77
- <slot name="submenu"
78
- ?hidden=${!this.isNested}
79
- @slotchange=${this.handleSubmenuSlotChange}
80
- ></slot>
88
+ <slot name="submenu" @slotchange=${this.handleSubmenuSlotChange}></slot>
81
89
  `;
82
90
  }
83
91
  }
84
92
  __decorateClass([
85
93
  state()
86
- ], UMenuItem.prototype, "isNested");
94
+ ], UMenuItem.prototype, "submenu");
87
95
  __decorateClass([
88
96
  property({ type: Boolean, reflect: true })
89
97
  ], UMenuItem.prototype, "disabled");
98
+ __decorateClass([
99
+ property({ type: Boolean, reflect: true })
100
+ ], UMenuItem.prototype, "loading");
90
101
  __decorateClass([
91
102
  property({ type: Boolean, reflect: true })
92
103
  ], UMenuItem.prototype, "checked");