@iyulab/components 0.1.5 → 0.1.7

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 (82) hide show
  1. package/CHANGELOG.md +18 -18
  2. package/LICENSE +20 -20
  3. package/README.md +33 -33
  4. package/dist/assets/icons/arrow-clockwise.svg.js +1 -1
  5. package/dist/assets/icons/arrow-down.svg.js +1 -1
  6. package/dist/assets/icons/arrow-up.svg.js +1 -1
  7. package/dist/assets/icons/ban.svg.js +1 -1
  8. package/dist/assets/icons/bell-fill.svg.js +1 -1
  9. package/dist/assets/icons/check-circle-fill.svg.js +1 -1
  10. package/dist/assets/icons/check-lg.svg.js +1 -1
  11. package/dist/assets/icons/chevron-down.svg.js +1 -1
  12. package/dist/assets/icons/chevron-left.svg.js +1 -1
  13. package/dist/assets/icons/chevron-right.svg.js +1 -1
  14. package/dist/assets/icons/chevron-up.svg.js +1 -1
  15. package/dist/assets/icons/copy.svg.js +1 -1
  16. package/dist/assets/icons/dash-lg.svg.js +1 -1
  17. package/dist/assets/icons/exclamation-circle-fill.svg.js +1 -1
  18. package/dist/assets/icons/exclamation-triangle-fill.svg.js +1 -1
  19. package/dist/assets/icons/eye-slash.svg.js +1 -1
  20. package/dist/assets/icons/eye.svg.js +1 -1
  21. package/dist/assets/icons/info-circle-fill.svg.js +1 -1
  22. package/dist/assets/icons/layout-sidebar.svg.js +1 -1
  23. package/dist/assets/icons/lightbulb-fill.svg.js +1 -1
  24. package/dist/assets/icons/lightbulb-off.svg.js +1 -1
  25. package/dist/assets/icons/lightbulb.svg.js +1 -1
  26. package/dist/assets/icons/mic-fill.svg.js +1 -1
  27. package/dist/assets/icons/mic-mute-fill.svg.js +1 -1
  28. package/dist/assets/icons/paperclip.svg.js +1 -1
  29. package/dist/assets/icons/pencil-square.svg.js +1 -1
  30. package/dist/assets/icons/plus-lg.svg.js +1 -1
  31. package/dist/assets/icons/search.svg.js +1 -1
  32. package/dist/assets/icons/speedometer.svg.js +1 -1
  33. package/dist/assets/icons/x-lg.svg.js +1 -1
  34. package/dist/assets/styles/dark.css +129 -129
  35. package/dist/assets/styles/dark.css.js +1 -1
  36. package/dist/assets/styles/light.css +129 -129
  37. package/dist/assets/styles/light.css.js +1 -1
  38. package/dist/components/FloatingElement.d.ts +84 -0
  39. package/dist/components/FloatingElement.js +166 -0
  40. package/dist/components/FloatingElement.styles.d.ts +1 -0
  41. package/dist/components/FloatingElement.styles.js +26 -0
  42. package/dist/components/context-menu/ContextMenu.d.ts +20 -39
  43. package/dist/components/context-menu/ContextMenu.js +92 -137
  44. package/dist/components/context-menu/ContextMenu.styles.js +3 -28
  45. package/dist/components/context-menu/index.d.ts +1 -1
  46. package/dist/components/dialog/Dialog.d.ts +14 -7
  47. package/dist/components/dialog/Dialog.js +88 -27
  48. package/dist/components/dialog/Dialog.styles.js +60 -11
  49. package/dist/components/divider/Divider.d.ts +5 -5
  50. package/dist/components/divider/Divider.js +17 -17
  51. package/dist/components/divider/Divider.styles.js +4 -6
  52. package/dist/components/dropdown-menu/DropdownMenu.d.ts +27 -0
  53. package/dist/components/dropdown-menu/DropdownMenu.js +135 -0
  54. package/dist/components/dropdown-menu/DropdownMenu.styles.d.ts +1 -0
  55. package/dist/components/dropdown-menu/DropdownMenu.styles.js +15 -0
  56. package/dist/components/dropdown-menu/index.d.ts +7 -0
  57. package/dist/components/dropdown-menu/index.js +5 -0
  58. package/dist/components/icon/Icon.js +1 -0
  59. package/dist/components/index.d.ts +2 -0
  60. package/dist/components/input/Input.js +1 -1
  61. package/dist/components/input/Input.styles.js +5 -5
  62. package/dist/components/menu/Menu.d.ts +4 -15
  63. package/dist/components/menu/Menu.js +43 -28
  64. package/dist/components/menu/Menu.styles.js +7 -19
  65. package/dist/components/menu-item/MenuItem.d.ts +21 -12
  66. package/dist/components/menu-item/MenuItem.js +184 -15
  67. package/dist/components/menu-item/MenuItem.styles.js +6 -0
  68. package/dist/components/menu-item/index.js +5 -0
  69. package/dist/components/split-panel/SplitPanel.d.ts +10 -2
  70. package/dist/components/split-panel/SplitPanel.js +41 -22
  71. package/dist/components/split-panel/SplitPanel.styles.js +1 -16
  72. package/dist/components/tooltip/Tooltip.d.ts +9 -29
  73. package/dist/components/tooltip/Tooltip.js +27 -149
  74. package/dist/components/tooltip/Tooltip.styles.js +8 -22
  75. package/dist/index.js +4 -0
  76. package/dist/integrations/react/UDropdownMenu.d.ts +8 -0
  77. package/dist/integrations/react/UDropdownMenu.js +10 -0
  78. package/dist/integrations/react/index.d.ts +1 -0
  79. package/dist/integrations/react/index.js +1 -0
  80. package/dist/internals/node-helpers.d.ts +6 -1
  81. package/dist/internals/node-helpers.js +5 -6
  82. package/package.json +59 -59
@@ -1,7 +1,7 @@
1
1
  import { ContextMenu } from './ContextMenu.js';
2
2
  declare global {
3
3
  interface HTMLElementTagNameMap {
4
- 'u-context-menu': ContextMenu;
4
+ "u-context-menu": ContextMenu;
5
5
  }
6
6
  }
7
7
  export { ContextMenu };
@@ -7,13 +7,15 @@ import { BaseElement } from '../BaseElement.js';
7
7
  export declare class Dialog extends BaseElement {
8
8
  static styles: import('lit').CSSResultGroup[];
9
9
  static dependencies: Record<string, typeof BaseElement>;
10
- overlayEl: HTMLDivElement;
10
+ dialogEl: HTMLDivElement;
11
+ /** 모달 모드 활성화 (배경 차단 및 포커스 트랩) */
12
+ modal: boolean;
11
13
  /** 다이얼로그가 열려있는지 여부 */
12
14
  open: boolean;
13
- /** 오버레이 클릭 닫기 활성화 여부 */
14
- closeOnOverlayClick: boolean;
15
- /** ESC 키로 닫기 활성화 여부 */
16
- closeOnEscape: boolean;
15
+ /** 이벤트에 의한 다이얼로그 닫기 허용 여부 */
16
+ closable: boolean;
17
+ /** 타이틀 텍스트 */
18
+ heading: string;
17
19
  connectedCallback(): void;
18
20
  disconnectedCallback(): void;
19
21
  protected updated(changedProperties: PropertyValues): void;
@@ -22,9 +24,14 @@ export declare class Dialog extends BaseElement {
22
24
  show: () => Promise<void>;
23
25
  /** 다이얼로그를 숨깁니다 */
24
26
  hide: () => Promise<void>;
27
+ /** 패널을 흔드는 애니메이션을 재생합니다 */
28
+ private shake;
25
29
  /** 오버레이 클릭 핸들러 */
26
- private handleOverlayClick;
30
+ private handleBackdropClick;
27
31
  /** 키보드 이벤트 핸들러 */
28
- private handleKeyDown;
32
+ private handleKeydown;
33
+ /** 포커스 트랩 핸들러 */
34
+ private handleFocusin;
29
35
  private updateOpenState;
36
+ private updateModalState;
30
37
  }
@@ -1,6 +1,7 @@
1
- import { html } from 'lit';
1
+ import { nothing, html } from 'lit';
2
2
  import { query, property } from 'lit/decorators.js';
3
3
  import { BaseElement } from '../BaseElement.js';
4
+ import { IconButton } from '../icon-button/IconButton.js';
4
5
  import { styles } from './Dialog.styles.js';
5
6
 
6
7
  var __defProp = Object.defineProperty;
@@ -15,30 +16,52 @@ var __decorateClass = (decorators, target, key, kind) => {
15
16
  class Dialog extends BaseElement {
16
17
  constructor() {
17
18
  super(...arguments);
19
+ this.modal = true;
18
20
  this.open = false;
19
- this.closeOnOverlayClick = true;
20
- this.closeOnEscape = true;
21
+ this.closable = true;
22
+ this.heading = "";
21
23
  /** 다이얼로그를 표시합니다 */
22
24
  this.show = async () => {
23
- this.open = true;
24
25
  await this.updateComplete;
26
+ requestAnimationFrame(() => {
27
+ this.open = true;
28
+ });
25
29
  };
26
30
  /** 다이얼로그를 숨깁니다 */
27
31
  this.hide = async () => {
28
32
  await this.updateComplete;
29
- this.open = false;
33
+ requestAnimationFrame(() => {
34
+ this.open = false;
35
+ });
30
36
  };
31
37
  /** 오버레이 클릭 핸들러 */
32
- this.handleOverlayClick = (e) => {
33
- if (this.closeOnOverlayClick && e.target === this.overlayEl) {
34
- this.hide();
38
+ this.handleBackdropClick = (e) => {
39
+ if (e.target === this) {
40
+ if (this.closable) {
41
+ this.hide();
42
+ } else {
43
+ this.shake();
44
+ }
35
45
  }
36
46
  };
37
47
  /** 키보드 이벤트 핸들러 */
38
- this.handleKeyDown = (e) => {
39
- if (this.closeOnEscape && e.key === "Escape" && this.open) {
48
+ this.handleKeydown = (e) => {
49
+ if (e.key === "Escape" && this.open) {
40
50
  e.preventDefault();
41
- this.hide();
51
+ if (this.closable) {
52
+ this.hide();
53
+ } else {
54
+ this.shake();
55
+ }
56
+ }
57
+ };
58
+ /** 포커스 트랩 핸들러 */
59
+ this.handleFocusin = (e) => {
60
+ if (!this.modal || !this.open) return;
61
+ const target = e.target;
62
+ if (!this.contains(target)) {
63
+ e.preventDefault();
64
+ this.dialogEl.focus();
42
65
  }
43
66
  };
44
67
  }
@@ -46,14 +69,17 @@ class Dialog extends BaseElement {
46
69
  this.styles = [super.styles, styles];
47
70
  }
48
71
  static {
49
- this.dependencies = {};
72
+ this.dependencies = {
73
+ "u-icon-button": IconButton
74
+ };
50
75
  }
51
76
  connectedCallback() {
52
77
  super.connectedCallback();
53
- window.addEventListener("keydown", this.handleKeyDown);
78
+ this.addEventListener("click", this.handleBackdropClick);
54
79
  }
55
80
  disconnectedCallback() {
56
- window.removeEventListener("keydown", this.handleKeyDown);
81
+ window.removeEventListener("keydown", this.handleKeydown);
82
+ window.removeEventListener("focusin", this.handleFocusin);
57
83
  super.disconnectedCallback();
58
84
  }
59
85
  updated(changedProperties) {
@@ -61,39 +87,74 @@ class Dialog extends BaseElement {
61
87
  if (changedProperties.has("open")) {
62
88
  this.updateOpenState(this.open);
63
89
  }
90
+ if (changedProperties.has("modal")) {
91
+ this.updateModalState(this.modal);
92
+ }
64
93
  }
65
94
  render() {
66
95
  return html`
67
- <div class="overlay" part="overlay"
68
- ?hidden=${!this.open}
69
- @click=${this.handleOverlayClick}>
70
- <div class="panel" part="panel">
71
- <slot></slot>
72
- </div>
96
+ <div class="dialog scrollable" part="base" tabindex="-1">
97
+
98
+ ${this.heading ? html`
99
+ <div class="header" part="header">
100
+ <span class="title" part="title">
101
+ ${this.heading}
102
+ </span>
103
+ <u-icon-button class="close-btn" part="close-btn"
104
+ lib="internal"
105
+ name="x-lg"
106
+ borderless
107
+ @click=${() => this.hide()}
108
+ ></u-icon-button>
109
+ </div>` : nothing}
110
+
111
+ <slot></slot>
112
+
73
113
  </div>
74
114
  `;
75
115
  }
116
+ /** 패널을 흔드는 애니메이션을 재생합니다 */
117
+ shake() {
118
+ this.dialogEl.classList.add("shake");
119
+ setTimeout(() => {
120
+ this.dialogEl.classList.remove("shake");
121
+ }, 500);
122
+ }
76
123
  updateOpenState(open) {
77
124
  if (open) {
78
- document.body.style.overflow = "hidden";
79
125
  this.emit("u-show");
126
+ document.body.style.overflow = this.modal ? "hidden" : "";
80
127
  } else {
81
- document.body.style.overflow = "";
82
128
  this.emit("u-hide");
129
+ document.body.style.overflow = "";
130
+ }
131
+ }
132
+ updateModalState(modal) {
133
+ if (modal) {
134
+ window.addEventListener("keydown", this.handleKeydown);
135
+ window.addEventListener("focusin", this.handleFocusin);
136
+ document.body.style.overflow = this.open ? "hidden" : "";
137
+ } else {
138
+ window.removeEventListener("keydown", this.handleKeydown);
139
+ window.removeEventListener("focusin", this.handleFocusin);
140
+ document.body.style.overflow = "";
83
141
  }
84
142
  }
85
143
  }
86
144
  __decorateClass([
87
- query(".overlay")
88
- ], Dialog.prototype, "overlayEl");
145
+ query(".dialog")
146
+ ], Dialog.prototype, "dialogEl");
147
+ __decorateClass([
148
+ property({ type: Boolean, reflect: true })
149
+ ], Dialog.prototype, "modal");
89
150
  __decorateClass([
90
151
  property({ type: Boolean, reflect: true })
91
152
  ], Dialog.prototype, "open");
92
153
  __decorateClass([
93
154
  property({ type: Boolean })
94
- ], Dialog.prototype, "closeOnOverlayClick");
155
+ ], Dialog.prototype, "closable");
95
156
  __decorateClass([
96
- property({ type: Boolean })
97
- ], Dialog.prototype, "closeOnEscape");
157
+ property({ type: String })
158
+ ], Dialog.prototype, "heading");
98
159
 
99
160
  export { Dialog };
@@ -2,30 +2,79 @@ import { css } from 'lit';
2
2
 
3
3
  const styles = css`
4
4
  :host {
5
- display: inline-block;
6
- }
7
-
8
- .overlay {
9
5
  position: fixed;
10
6
  z-index: 9999;
11
7
  top: 0;
8
+ bottom: 0;
12
9
  left: 0;
13
- width: 100vw;
14
- height: 100vh;
10
+ right: 0;
15
11
  display: flex;
16
12
  align-items: center;
17
13
  justify-content: center;
14
+ background: transparent;
15
+ visibility: hidden;
16
+ pointer-events: none;
17
+ }
18
+ :host([open]) {
19
+ visibility: visible;
20
+ pointer-events: auto;
21
+ }
22
+ :host([modal]) {
18
23
  background: var(--u-overlay-bg-color);
19
24
  }
20
25
 
21
- .panel {
26
+ .dialog {
27
+ position: relative;
22
28
  display: block;
23
- background: var(--u-panel-bg-color);
24
- border: 1px solid var(--u-border-color);
25
- border-radius: 6px;
26
29
  max-width: 90vw;
27
30
  max-height: 90vh;
28
- overflow: hidden;
31
+ padding: 20px;
32
+ border: 1px solid var(--u-border-color);
33
+ border-radius: 6px;
34
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
35
+ background: var(--u-panel-bg-color);
36
+ overflow: auto;
37
+ }
38
+ .dialog.shake {
39
+ animation: shake 0.5s cubic-bezier(0.36, 0.07, 0.19, 0.97);
40
+ }
41
+
42
+ .header {
43
+ display: flex;
44
+ flex-direction: row;
45
+ align-items: center;
46
+ justify-content: space-between;
47
+ font-size: 20px;
48
+ margin-bottom: 20px;
49
+ }
50
+ .header .title {
51
+ line-height: 1.2;
52
+ font-weight: 600;
53
+ }
54
+
55
+ /* 페이드 인 및 스케일 업 애니메이션 */
56
+ @keyframes fadeout {
57
+ 0% {
58
+ opacity: 0;
59
+ transform: scale(0.9);
60
+ }
61
+ 100% {
62
+ opacity: 1;
63
+ transform: scale(1);
64
+ }
65
+ }
66
+
67
+ /* 진동 애니메이션 */
68
+ @keyframes shake {
69
+ 0%, 100% {
70
+ transform: translateX(0);
71
+ }
72
+ 10%, 30%, 50%, 70%, 90% {
73
+ transform: translateX(-8px);
74
+ }
75
+ 20%, 40%, 60%, 80% {
76
+ transform: translateX(8px);
77
+ }
29
78
  }
30
79
  `;
31
80
 
@@ -6,8 +6,8 @@ import { BaseElement } from '../BaseElement.js';
6
6
  export declare class Divider extends BaseElement {
7
7
  static styles: import('lit').CSSResultGroup[];
8
8
  static dependencies: Record<string, typeof BaseElement>;
9
- /** 마지막 마우스 위치를 저장합니다. */
10
- private position;
9
+ /** 마지막 마우스 포인터 위치를 저장합니다. */
10
+ private prevPointerPosition;
11
11
  /** 분할선이 움직이는지 여부를 나타냅니다. */
12
12
  moving: boolean;
13
13
  /** 분할 방향을 설정합니다. */
@@ -17,9 +17,9 @@ export declare class Divider extends BaseElement {
17
17
  disconnectedCallback(): void;
18
18
  render(): typeof nothing | import('lit-html').TemplateResult<1>;
19
19
  /** 마우스 다운 이벤트 핸들러 */
20
- private handleMouseDown;
20
+ private handleMousedown;
21
21
  /** 마우스 무브 이벤트 핸들러 */
22
- private handleMouseMove;
22
+ private handleMousemove;
23
23
  /** 마우스 업 이벤트 핸들러 */
24
- private handleMouseUp;
24
+ private handleMouseup;
25
25
  }
@@ -15,38 +15,38 @@ var __decorateClass = (decorators, target, key, kind) => {
15
15
  class Divider extends BaseElement {
16
16
  constructor() {
17
17
  super(...arguments);
18
- /** 마지막 마우스 위치를 저장합니다. */
19
- this.position = 0;
18
+ /** 마지막 마우스 포인터 위치를 저장합니다. */
19
+ this.prevPointerPosition = 0;
20
20
  this.moving = false;
21
21
  this.orientation = "horizontal";
22
22
  this.movable = false;
23
23
  /** 마우스 다운 이벤트 핸들러 */
24
- this.handleMouseDown = (e) => {
24
+ this.handleMousedown = (e) => {
25
25
  e.preventDefault();
26
26
  this.moving = true;
27
- this.position = this.orientation === "horizontal" ? e.clientX : e.clientY;
27
+ this.prevPointerPosition = this.orientation === "horizontal" ? e.clientX : e.clientY;
28
28
  this.emit("u-movestart");
29
- document.addEventListener("mousemove", this.handleMouseMove);
30
- document.addEventListener("mouseup", this.handleMouseUp);
29
+ document.addEventListener("mousemove", this.handleMousemove);
30
+ document.addEventListener("mouseup", this.handleMouseup);
31
31
  document.body.style.userSelect = "none";
32
32
  document.body.style.cursor = this.orientation === "horizontal" ? "col-resize" : "row-resize";
33
33
  };
34
34
  /** 마우스 무브 이벤트 핸들러 */
35
- this.handleMouseMove = (e) => {
35
+ this.handleMousemove = (e) => {
36
36
  if (!this.moving) return;
37
37
  e.preventDefault();
38
- const position = this.orientation === "horizontal" ? e.clientX : e.clientY;
39
- const delta = position - this.position;
38
+ const currentPointerPosition = this.orientation === "horizontal" ? e.clientX : e.clientY;
39
+ const delta = currentPointerPosition - this.prevPointerPosition;
40
40
  this.emit("u-move", { delta });
41
- this.position = position;
41
+ this.prevPointerPosition = currentPointerPosition;
42
42
  };
43
43
  /** 마우스 업 이벤트 핸들러 */
44
- this.handleMouseUp = (_) => {
44
+ this.handleMouseup = (_) => {
45
45
  if (!this.moving) return;
46
46
  this.moving = false;
47
47
  this.emit("u-moveend");
48
- document.removeEventListener("mousemove", this.handleMouseMove);
49
- document.removeEventListener("mouseup", this.handleMouseUp);
48
+ document.removeEventListener("mousemove", this.handleMousemove);
49
+ document.removeEventListener("mouseup", this.handleMouseup);
50
50
  document.body.style.userSelect = "";
51
51
  document.body.style.cursor = "";
52
52
  };
@@ -59,16 +59,16 @@ class Divider extends BaseElement {
59
59
  }
60
60
  disconnectedCallback() {
61
61
  super.disconnectedCallback();
62
- document.removeEventListener("mousemove", this.handleMouseMove);
63
- document.removeEventListener("mouseup", this.handleMouseUp);
62
+ document.removeEventListener("mousemove", this.handleMousemove);
63
+ document.removeEventListener("mouseup", this.handleMouseup);
64
64
  }
65
65
  render() {
66
66
  if (this.movable) {
67
67
  return html`
68
- <div class="handler"
68
+ <div class="handler" part="handler"
69
69
  ?moving=${this.moving}
70
70
  orientation=${this.orientation}
71
- @mousedown=${this.handleMouseDown}
71
+ @mousedown=${this.handleMousedown}
72
72
  ></div>
73
73
  `;
74
74
  } else {
@@ -9,19 +9,15 @@ const styles = css`
9
9
 
10
10
  :host {
11
11
  position: relative;
12
- display: block;
13
- flex-shrink: 0;
14
12
  background-color: var(--u-neutral-200, #e5e7eb);
15
13
  }
16
14
  :host([orientation="horizontal"]) {
17
15
  width: var(--divider-size, 2px);
18
- height: 100%;
19
- cursor: col-resize;
16
+ min-height: 0px;
20
17
  }
21
18
  :host([orientation="vertical"]) {
22
- width: 100%;
19
+ min-width: 0px;
23
20
  height: var(--divider-size, 2px);
24
- cursor: row-resize;
25
21
  }
26
22
 
27
23
  .handler {
@@ -39,6 +35,7 @@ const styles = css`
39
35
  transform: translateX(-50%);
40
36
  width: var(--handler-size, 4px);
41
37
  height: 100%;
38
+ cursor: col-resize;
42
39
  }
43
40
  .handler[orientation="vertical"] {
44
41
  left: 0;
@@ -47,6 +44,7 @@ const styles = css`
47
44
  transform: translateY(-50%);
48
45
  width: 100%;
49
46
  height: var(--handler-size, 4px);
47
+ cursor: row-resize;
50
48
  }
51
49
  .handler[moving],
52
50
  .handler:hover {
@@ -0,0 +1,27 @@
1
+ import { PropertyValues } from 'lit';
2
+ import { BaseElement } from '../BaseElement.js';
3
+ import { FloatingElement } from '../FloatingElement.js';
4
+ export declare class DropdownMenu extends FloatingElement {
5
+ static styles: import('lit').CSSResultGroup[];
6
+ static dependencies: Record<string, typeof BaseElement>;
7
+ closeOnSelect: boolean;
8
+ closeOnCheck: boolean;
9
+ private onDocPointerDown;
10
+ private onWindowBlur;
11
+ private get menu();
12
+ connectedCallback(): void;
13
+ disconnectedCallback(): void;
14
+ protected updated(changed: PropertyValues): void;
15
+ render(): import('lit-html').TemplateResult<1>;
16
+ show: () => Promise<void>;
17
+ hide: () => Promise<void>;
18
+ private attachAnchor;
19
+ private detachAnchor;
20
+ private onTriggerPointerDown;
21
+ private onTriggerKeydown;
22
+ private onSelect;
23
+ private onCheck;
24
+ private onRequestClose;
25
+ private attachDismiss;
26
+ private detachDismiss;
27
+ }
@@ -0,0 +1,135 @@
1
+ import { html } from 'lit';
2
+ import { property } from 'lit/decorators.js';
3
+ import { FloatingElement } from '../FloatingElement.js';
4
+ import { Menu } from '../menu/Menu.js';
5
+ import { MenuItem } from '../menu-item/MenuItem.js';
6
+ import { styles } from './DropdownMenu.styles.js';
7
+
8
+ var __defProp = Object.defineProperty;
9
+ var __decorateClass = (decorators, target, key, kind) => {
10
+ var result = void 0 ;
11
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
12
+ if (decorator = decorators[i])
13
+ result = (decorator(target, key, result) ) || result;
14
+ if (result) __defProp(target, key, result);
15
+ return result;
16
+ };
17
+ class DropdownMenu extends FloatingElement {
18
+ constructor() {
19
+ super(...arguments);
20
+ this.closeOnSelect = true;
21
+ this.closeOnCheck = false;
22
+ this.onDocPointerDown = (e) => {
23
+ if (!this.visible) return;
24
+ const path = e.composedPath();
25
+ if (path.includes(this)) return;
26
+ if (this.anchor && path.includes(this.anchor)) return;
27
+ this.hide();
28
+ };
29
+ this.onWindowBlur = () => {
30
+ if (this.visible) this.hide();
31
+ };
32
+ this.show = async () => {
33
+ this.menu && (this.menu.open = true);
34
+ await super.show();
35
+ await this.menu?.focusFirstItem?.();
36
+ };
37
+ this.hide = async () => {
38
+ await super.hide();
39
+ this.menu && (this.menu.open = false);
40
+ this.querySelectorAll("u-menu-item").forEach((i) => i.closeSubmenu?.());
41
+ };
42
+ this.onTriggerPointerDown = async (e) => {
43
+ if (e.button !== 0) return;
44
+ if (this.visible) await this.hide();
45
+ else await this.show();
46
+ };
47
+ this.onTriggerKeydown = async (e) => {
48
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
49
+ e.preventDefault();
50
+ if (!this.visible) await this.show();
51
+ }
52
+ };
53
+ this.onSelect = async () => {
54
+ if (this.closeOnSelect) await this.hide();
55
+ };
56
+ this.onCheck = async () => {
57
+ if (this.closeOnCheck) await this.hide();
58
+ };
59
+ this.onRequestClose = async () => {
60
+ await this.hide();
61
+ };
62
+ }
63
+ static {
64
+ this.styles = [super.styles, styles];
65
+ }
66
+ static {
67
+ this.dependencies = {
68
+ "u-menu": Menu,
69
+ "u-menu-item": MenuItem
70
+ };
71
+ }
72
+ get menu() {
73
+ return this.querySelector("u-menu");
74
+ }
75
+ connectedCallback() {
76
+ super.connectedCallback();
77
+ this.placement ||= "bottom-start";
78
+ this.distance ||= 6;
79
+ this.addEventListener("u-select", this.onSelect);
80
+ this.addEventListener("u-check", this.onCheck);
81
+ this.addEventListener("u-request-close", this.onRequestClose);
82
+ }
83
+ disconnectedCallback() {
84
+ this.detachAnchor(this.anchor);
85
+ this.detachDismiss();
86
+ this.removeEventListener("u-select", this.onSelect);
87
+ this.removeEventListener("u-check", this.onCheck);
88
+ this.removeEventListener("u-request-close", this.onRequestClose);
89
+ super.disconnectedCallback();
90
+ }
91
+ updated(changed) {
92
+ super.updated(changed);
93
+ if (changed.has("anchor")) {
94
+ this.detachAnchor(changed.get("anchor"));
95
+ this.attachAnchor(this.anchor);
96
+ }
97
+ if (changed.has("visible")) {
98
+ if (this.visible) this.attachDismiss();
99
+ else this.detachDismiss();
100
+ if (this.anchor) {
101
+ this.anchor.setAttribute("aria-expanded", this.visible ? "true" : "false");
102
+ }
103
+ }
104
+ }
105
+ render() {
106
+ return html`<slot></slot>`;
107
+ }
108
+ attachAnchor(target) {
109
+ if (!target) return;
110
+ target.setAttribute("aria-haspopup", "menu");
111
+ target.addEventListener("pointerdown", this.onTriggerPointerDown);
112
+ target.addEventListener("keydown", this.onTriggerKeydown);
113
+ }
114
+ detachAnchor(target) {
115
+ if (!target) return;
116
+ target.removeEventListener("pointerdown", this.onTriggerPointerDown);
117
+ target.removeEventListener("keydown", this.onTriggerKeydown);
118
+ }
119
+ attachDismiss() {
120
+ document.addEventListener("pointerdown", this.onDocPointerDown, true);
121
+ window.addEventListener("blur", this.onWindowBlur);
122
+ }
123
+ detachDismiss() {
124
+ document.removeEventListener("pointerdown", this.onDocPointerDown, true);
125
+ window.removeEventListener("blur", this.onWindowBlur);
126
+ }
127
+ }
128
+ __decorateClass([
129
+ property({ type: Boolean })
130
+ ], DropdownMenu.prototype, "closeOnSelect");
131
+ __decorateClass([
132
+ property({ type: Boolean })
133
+ ], DropdownMenu.prototype, "closeOnCheck");
134
+
135
+ export { DropdownMenu };
@@ -0,0 +1 @@
1
+ export declare const styles: import('lit').CSSResult;
@@ -0,0 +1,15 @@
1
+ import { css } from 'lit';
2
+
3
+ const styles = css`
4
+ :host {
5
+ opacity: 0;
6
+ transform: scale(0.98);
7
+ transition: opacity 0.12s ease, transform 0.12s ease;
8
+ }
9
+ :host([visible]) {
10
+ opacity: 1; /* FloatingElement의 0.8을 덮어씀 */
11
+ transform: scale(1);
12
+ }
13
+ `;
14
+
15
+ export { styles };
@@ -0,0 +1,7 @@
1
+ import { DropdownMenu } from './DropdownMenu.js';
2
+ declare global {
3
+ interface HTMLElementTagNameMap {
4
+ "u-dropdown-menu": DropdownMenu;
5
+ }
6
+ }
7
+ export { DropdownMenu };
@@ -0,0 +1,5 @@
1
+ import { DropdownMenu } from './DropdownMenu.js';
2
+
3
+ DropdownMenu.define("u-dropdown-menu");
4
+
5
+ export { DropdownMenu };
@@ -28,6 +28,7 @@ class Icon extends BaseElement {
28
28
  }
29
29
  render() {
30
30
  if (!this.name) return nothing;
31
+ console.log(this.library, this.name, "icon render");
31
32
  return until(icons.resolve(this.library, this.name).then((html) => {
32
33
  return this.validate(html) ? unsafeHTML(html) : nothing;
33
34
  }), nothing);
@@ -4,11 +4,13 @@ export * from './context-menu';
4
4
  export * from './copy-button';
5
5
  export * from './dialog';
6
6
  export * from './divider';
7
+ export * from './dropdown-menu';
7
8
  export * from './form';
8
9
  export * from './icon';
9
10
  export * from './icon-button';
10
11
  export * from './input';
11
12
  export * from './menu';
13
+ export * from './menu-item';
12
14
  export * from './progress-bar';
13
15
  export * from './progress-ring';
14
16
  export * from './spinner';