elementdrawing 1.0.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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/dist/elementdrawing.min.js +3 -0
  3. package/dist/elementdrawing.min.js.LICENSE.txt +8 -0
  4. package/dist/elementdrawing.min.js.map +1 -0
  5. package/dist/index.html +1 -0
  6. package/package.json +127 -0
  7. package/src/core/bridge.h +855 -0
  8. package/src/core/diff.c +900 -0
  9. package/src/core/element.c +1078 -0
  10. package/src/core/event.c +813 -0
  11. package/src/core/fiber.c +1027 -0
  12. package/src/core/hooks.c +919 -0
  13. package/src/core/renderer.c +963 -0
  14. package/src/core/scheduler.c +702 -0
  15. package/src/core/state.c +803 -0
  16. package/src/css/animations.css +779 -0
  17. package/src/css/base.css +615 -0
  18. package/src/css/components.css +1311 -0
  19. package/src/css/tailwind.css +370 -0
  20. package/src/css/themes.css +517 -0
  21. package/src/css/utilities.css +475 -0
  22. package/src/index.js +746 -0
  23. package/src/js/animation.js +655 -0
  24. package/src/js/dom.js +665 -0
  25. package/src/js/events.js +585 -0
  26. package/src/js/http.js +446 -0
  27. package/src/js/index.js +26 -0
  28. package/src/js/router.js +483 -0
  29. package/src/js/store.js +539 -0
  30. package/src/js/utils.js +593 -0
  31. package/src/js/validator.js +529 -0
  32. package/src/jsx/components/Accordion.jsx +210 -0
  33. package/src/jsx/components/Alert.jsx +169 -0
  34. package/src/jsx/components/Avatar.jsx +214 -0
  35. package/src/jsx/components/Badge.jsx +136 -0
  36. package/src/jsx/components/Breadcrumb.jsx +200 -0
  37. package/src/jsx/components/Button.jsx +188 -0
  38. package/src/jsx/components/Card.jsx +192 -0
  39. package/src/jsx/components/Carousel.jsx +278 -0
  40. package/src/jsx/components/Checkbox.jsx +215 -0
  41. package/src/jsx/components/Dialog.jsx +242 -0
  42. package/src/jsx/components/Drawer.jsx +190 -0
  43. package/src/jsx/components/Dropdown.jsx +268 -0
  44. package/src/jsx/components/Form.jsx +274 -0
  45. package/src/jsx/components/Input.jsx +285 -0
  46. package/src/jsx/components/Menu.jsx +276 -0
  47. package/src/jsx/components/Modal.jsx +274 -0
  48. package/src/jsx/components/Navbar.jsx +292 -0
  49. package/src/jsx/components/Pagination.jsx +268 -0
  50. package/src/jsx/components/Progress.jsx +252 -0
  51. package/src/jsx/components/Radio.jsx +208 -0
  52. package/src/jsx/components/Select.jsx +397 -0
  53. package/src/jsx/components/Sidebar.jsx +250 -0
  54. package/src/jsx/components/Slider.jsx +310 -0
  55. package/src/jsx/components/Spinner.jsx +198 -0
  56. package/src/jsx/components/Switch.jsx +201 -0
  57. package/src/jsx/components/Table.jsx +332 -0
  58. package/src/jsx/components/Tabs.jsx +227 -0
  59. package/src/jsx/components/Textarea.jsx +212 -0
  60. package/src/jsx/components/Toast.jsx +270 -0
  61. package/src/jsx/components/Tooltip.jsx +178 -0
  62. package/src/jsx/components/Typography.jsx +299 -0
  63. package/src/jsx/components/index.jsx +70 -0
  64. package/src/jsx/core/element.js +3 -0
  65. package/src/jsx/hooks/index.js +356 -0
  66. package/src/jsx/hooks/useCallback.js +472 -0
  67. package/src/jsx/hooks/useContext.js +586 -0
  68. package/src/jsx/hooks/useEffect.js +704 -0
  69. package/src/jsx/hooks/useLayoutEffect.js +508 -0
  70. package/src/jsx/hooks/useMemo.js +689 -0
  71. package/src/jsx/hooks/useReducer.js +729 -0
  72. package/src/jsx/hooks/useRef.js +542 -0
  73. package/src/jsx/hooks/useState.js +854 -0
  74. package/src/jsx/runtime/commit.js +903 -0
  75. package/src/jsx/runtime/createElement.js +860 -0
  76. package/src/jsx/runtime/index.js +356 -0
  77. package/src/jsx/runtime/reconcile.js +687 -0
  78. package/src/jsx/runtime/render.js +914 -0
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Dialog Component for ElementDrawing Framework
3
+ * Supports confirmation, alert, prompt dialogs, custom content, keyboard navigation, focus trap
4
+ */
5
+ const ED = require('../core/element');
6
+
7
+ const DIALOG_TYPES = {
8
+ confirm: {
9
+ icon: ED.createElement('svg', {
10
+ className: 'ed-w-6 ed-h-6 ed-text-blue-600',
11
+ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
12
+ children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z' }),
13
+ }),
14
+ iconBg: 'ed-bg-blue-100',
15
+ },
16
+ alert: {
17
+ icon: ED.createElement('svg', {
18
+ className: 'ed-w-6 ed-h-6 ed-text-red-600',
19
+ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
20
+ children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z' }),
21
+ }),
22
+ iconBg: 'ed-bg-red-100',
23
+ },
24
+ prompt: {
25
+ icon: ED.createElement('svg', {
26
+ className: 'ed-w-6 ed-h-6 ed-text-green-600',
27
+ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
28
+ children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z' }),
29
+ }),
30
+ iconBg: 'ed-bg-green-100',
31
+ },
32
+ info: {
33
+ icon: ED.createElement('svg', {
34
+ className: 'ed-w-6 ed-h-6 ed-text-blue-600',
35
+ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
36
+ children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z' }),
37
+ }),
38
+ iconBg: 'ed-bg-blue-100',
39
+ },
40
+ warning: {
41
+ icon: ED.createElement('svg', {
42
+ className: 'ed-w-6 ed-h-6 ed-text-yellow-600',
43
+ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
44
+ children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z' }),
45
+ }),
46
+ iconBg: 'ed-bg-yellow-100',
47
+ },
48
+ };
49
+
50
+ function Dialog(props) {
51
+ const {
52
+ visible = false,
53
+ type = 'confirm',
54
+ title,
55
+ content,
56
+ children,
57
+ icon,
58
+ okText = 'OK',
59
+ cancelText = 'Cancel',
60
+ onOk,
61
+ onCancel,
62
+ onClose,
63
+ confirmLoading = false,
64
+ showCancel = true,
65
+ closable = true,
66
+ closeOnBackdrop = true,
67
+ closeOnEscape = true,
68
+ backdrop = true,
69
+ className = '',
70
+ style = {},
71
+ width = 420,
72
+ footer,
73
+ header,
74
+ inputProps,
75
+ inputValue,
76
+ onInputChange,
77
+ okButtonProps,
78
+ cancelButtonProps,
79
+ danger = false,
80
+ autoFocus = true,
81
+ focusTrap = true,
82
+ zIndex = 1000,
83
+ afterClose,
84
+ keyboard = true,
85
+ maskClosable = true,
86
+ centered = true,
87
+ } = props;
88
+
89
+ if (!visible) return null;
90
+
91
+ const dialogType = DIALOG_TYPES[type] || DIALOG_TYPES.confirm;
92
+
93
+ const handleBackdropClick = (e) => {
94
+ if ((closeOnBackdrop || maskClosable) && e.target === e.currentTarget) {
95
+ onClose?.();
96
+ onCancel?.();
97
+ }
98
+ };
99
+
100
+ const handleKeyDown = (e) => {
101
+ if (!keyboard) return;
102
+ if (closeOnEscape && e.key === 'Escape') {
103
+ onClose?.();
104
+ onCancel?.();
105
+ }
106
+ if (focusTrap) {
107
+ if (e.key === 'Tab') {
108
+ const dialogEl = e.currentTarget;
109
+ const focusable = dialogEl.querySelectorAll(
110
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
111
+ );
112
+ if (focusable.length > 0) {
113
+ const first = focusable[0];
114
+ const last = focusable[focusable.length - 1];
115
+ if (e.shiftKey && document.activeElement === first) {
116
+ e.preventDefault();
117
+ last.focus();
118
+ } else if (!e.shiftKey && document.activeElement === last) {
119
+ e.preventDefault();
120
+ first.focus();
121
+ }
122
+ }
123
+ }
124
+ }
125
+ };
126
+
127
+ const iconElement = icon !== undefined ? icon
128
+ : ED.createElement('div', {
129
+ className: `ed-w-12 ed-h-12 ed-rounded-full ed-flex ed-items-center ed-justify-center ed-mx-auto ed-mb-4 ${dialogType.iconBg}`,
130
+ children: dialogType.icon,
131
+ });
132
+
133
+ const titleElement = title
134
+ ? ED.createElement('h3', {
135
+ className: 'ed-text-lg ed-font-semibold ed-text-gray-900 ed-text-center ed-mb-2',
136
+ }, title)
137
+ : null;
138
+
139
+ const contentElement = content || children
140
+ ? ED.createElement('div', {
141
+ className: 'ed-text-sm ed-text-gray-500 ed-text-center ed-mb-6',
142
+ }, content || children)
143
+ : null;
144
+
145
+ const promptInput = type === 'prompt'
146
+ ? ED.createElement('div', { className: 'ed-mb-6' },
147
+ ED.createElement('input', {
148
+ className: 'ed-w-full ed-px-3 ed-py-2 ed-border ed-border-gray-300 ed-rounded-md ed-text-sm focus:ed-outline-none focus:ed-ring-2 focus:ed-ring-blue-500 focus:ed-border-transparent',
149
+ value: inputValue,
150
+ onChange: onInputChange,
151
+ autoFocus,
152
+ ...inputProps,
153
+ })
154
+ )
155
+ : null;
156
+
157
+ const cancelButton = showCancel
158
+ ? ED.createElement('button', {
159
+ key: 'cancel',
160
+ className: 'ed-px-4 ed-py-2 ed-text-sm ed-font-medium ed-text-gray-700 ed-bg-white ed-border ed-border-gray-300 ed-rounded-md hover:ed-bg-gray-50 focus:ed-outline-none focus:ed-ring-2 focus:ed-ring-offset-2 focus:ed-ring-gray-300 ed-transition-colors',
161
+ onClick: onCancel || onClose,
162
+ ...cancelButtonProps,
163
+ }, cancelText)
164
+ : null;
165
+
166
+ const okButtonClass = danger
167
+ ? 'ed-bg-red-600 hover:ed-bg-red-700 focus:ed-ring-red-500'
168
+ : 'ed-bg-blue-600 hover:ed-bg-blue-700 focus:ed-ring-blue-500';
169
+
170
+ const okButton = ED.createElement('button', {
171
+ key: 'ok',
172
+ className: [
173
+ 'ed-px-4 ed-py-2 ed-text-sm ed-font-medium ed-text-white ed-rounded-md focus:ed-outline-none focus:ed-ring-2 focus:ed-ring-offset-2 ed-transition-colors',
174
+ okButtonClass,
175
+ confirmLoading ? 'ed-opacity-70 ed-cursor-not-allowed' : '',
176
+ ].filter(Boolean).join(' '),
177
+ onClick: onOk,
178
+ disabled: confirmLoading,
179
+ autoFocus: type !== 'prompt' && autoFocus,
180
+ ...okButtonProps,
181
+ children: confirmLoading
182
+ ? ED.createElement('span', { className: 'ed-flex ed-items-center ed-gap-2' },
183
+ ED.createElement('span', { className: 'ed-w-4 ed-h-4 ed-border-2 ed-border-white ed-border-t-transparent ed-rounded-full ed-animate-spin' }),
184
+ okText
185
+ )
186
+ : okText,
187
+ });
188
+
189
+ const footerElement = footer !== undefined ? footer
190
+ : ED.createElement('div', {
191
+ className: 'ed-flex ed-items-center ed-justify-center ed-gap-3',
192
+ }, [cancelButton, okButton].filter(Boolean));
193
+
194
+ return ED.createElement('div', {
195
+ className: [
196
+ 'ed-fixed ed-inset-0 ed-flex ed-z-50',
197
+ centered ? 'ed-items-center ed-justify-center' : 'ed-items-start ed-justify-center ed-pt-24',
198
+ backdrop ? '' : '',
199
+ 'ed-dialog-backdrop',
200
+ ].filter(Boolean).join(' '),
201
+ style: { zIndex },
202
+ onClick: handleBackdropClick,
203
+ onKeyDown: handleKeyDown,
204
+ children: ED.createElement('div', {
205
+ className: [
206
+ 'ed-bg-white ed-rounded-xl ed-shadow-2xl ed-p-6 ed-w-full',
207
+ 'ed-transform ed-transition-all ed-duration-200',
208
+ 'ed-animate-zoom-in',
209
+ className,
210
+ ].filter(Boolean).join(' '),
211
+ style: { maxWidth: width, ...style },
212
+ role: 'dialog',
213
+ 'aria-modal': 'true',
214
+ 'aria-labelledby': title ? 'dialog-title' : undefined,
215
+ children: [
216
+ closable
217
+ ? ED.createElement('button', {
218
+ key: 'close',
219
+ className: 'ed-absolute ed-top-3 ed-right-3 ed-p-1 ed-rounded-md ed-text-gray-400 hover:ed-text-gray-600 hover:ed-bg-gray-100 ed-transition-colors',
220
+ onClick: onClose,
221
+ 'aria-label': 'Close dialog',
222
+ children: ED.createElement('svg', {
223
+ className: 'ed-w-5 ed-h-5',
224
+ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
225
+ children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M6 18L18 6M6 6l12 12' }),
226
+ }),
227
+ })
228
+ : null,
229
+ iconElement,
230
+ titleElement,
231
+ contentElement,
232
+ promptInput,
233
+ footerElement,
234
+ ].filter(Boolean),
235
+ }),
236
+ });
237
+ }
238
+
239
+ Dialog.displayName = 'Dialog';
240
+ Dialog.TYPES = DIALOG_TYPES;
241
+
242
+ module.exports = Dialog;
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Drawer Component for ElementDrawing Framework
3
+ * Supports positions, sizes, overlay, close on escape, nested drawers, resizable
4
+ */
5
+ const ED = require('../core/element');
6
+
7
+ const DRAWER_POSITIONS = {
8
+ left: {
9
+ container: 'ed-left-0 ed-top-0 ed-bottom-0 ed-h-full',
10
+ animation: 'ed-animate-slide-in-left',
11
+ defaultSize: { width: 400 },
12
+ resizeClass: 'ed-right-0 ed-top-0 ed-bottom-0 ed-w-1 ed-cursor-ew-resize',
13
+ },
14
+ right: {
15
+ container: 'ed-right-0 ed-top-0 ed-bottom-0 ed-h-full',
16
+ animation: 'ed-animate-slide-in-right',
17
+ defaultSize: { width: 400 },
18
+ resizeClass: 'ed-left-0 ed-top-0 ed-bottom-0 ed-w-1 ed-cursor-ew-resize',
19
+ },
20
+ top: {
21
+ container: 'ed-top-0 ed-left-0 ed-right-0 ed-w-full',
22
+ animation: 'ed-animate-slide-in-top',
23
+ defaultSize: { height: 300 },
24
+ resizeClass: 'ed-bottom-0 ed-left-0 ed-right-0 ed-h-1 ed-cursor-ns-resize',
25
+ },
26
+ bottom: {
27
+ container: 'ed-bottom-0 ed-left-0 ed-right-0 ed-w-full',
28
+ animation: 'ed-animate-slide-in-bottom',
29
+ defaultSize: { height: 300 },
30
+ resizeClass: 'ed-top-0 ed-left-0 ed-right-0 ed-h-1 ed-cursor-ns-resize',
31
+ },
32
+ };
33
+
34
+ const DRAWER_SIZES = {
35
+ sm: { width: 300, height: 200 },
36
+ md: { width: 400, height: 300 },
37
+ lg: { width: 600, height: 450 },
38
+ xl: { width: 800, height: 600 },
39
+ full: { width: '100%', height: '100%' },
40
+ };
41
+
42
+ function Drawer(props) {
43
+ const {
44
+ visible = false,
45
+ position = 'right',
46
+ size = 'md',
47
+ width,
48
+ height,
49
+ overlay = true,
50
+ overlayOpacity = 0.5,
51
+ closeOnEscape = true,
52
+ closeOnOverlay = true,
53
+ closable = true,
54
+ onClose,
55
+ title,
56
+ children,
57
+ footer,
58
+ className = '',
59
+ style = {},
60
+ zIndex = 1000,
61
+ headerStyle = {},
62
+ bodyStyle = {},
63
+ footerStyle = {},
64
+ resizable = false,
65
+ nested = false,
66
+ destroyOnClose = true,
67
+ placement = position,
68
+ mask = true,
69
+ maskClosable = true,
70
+ afterOpenChange,
71
+ extra,
72
+ width: widthProp,
73
+ } = props;
74
+
75
+ if (!visible && destroyOnClose) return null;
76
+ if (!visible) return null;
77
+
78
+ const pos = DRAWER_POSITIONS[placement || position] || DRAWER_POSITIONS.right;
79
+ const sizeConfig = DRAWER_SIZES[size] || DRAWER_SIZES.md;
80
+ const drawerWidth = widthProp || width || sizeConfig.width;
81
+ const drawerHeight = height || sizeConfig.height;
82
+
83
+ const isHorizontal = placement === 'left' || placement === 'right';
84
+
85
+ const drawerClasses = [
86
+ 'ed-fixed ed-bg-white ed-shadow-xl ed-flex ed-flex-col ed-z-50',
87
+ pos.container,
88
+ 'ed-transition-transform ed-duration-300',
89
+ className,
90
+ ].filter(Boolean).join(' ');
91
+
92
+ const drawerStyle = {
93
+ [isHorizontal ? 'width' : 'height']: drawerWidth || drawerHeight,
94
+ ...style,
95
+ };
96
+
97
+ const handleOverlayClick = () => {
98
+ if (closeOnOverlay || maskClosable) onClose?.();
99
+ };
100
+
101
+ const handleKeyDown = (e) => {
102
+ if (closeOnEscape && e.key === 'Escape') onClose?.();
103
+ };
104
+
105
+ const headerElement = title || closable || extra
106
+ ? ED.createElement('div', {
107
+ className: 'ed-flex ed-items-center ed-justify-between ed-px-6 ed-py-4 ed-border-b ed-border-gray-200 ed-flex-shrink-0',
108
+ style: headerStyle,
109
+ children: [
110
+ ED.createElement('div', { key: 'title-area', className: 'ed-flex ed-items-center ed-gap-3' },
111
+ title
112
+ ? ED.createElement('h3', { className: 'ed-text-lg ed-font-semibold ed-text-gray-900' }, title)
113
+ : null,
114
+ extra
115
+ ? ED.createElement('div', { className: 'ed-text-sm ed-text-gray-500' }, extra)
116
+ : null
117
+ ),
118
+ closable
119
+ ? ED.createElement('button', {
120
+ key: 'close',
121
+ className: 'ed-p-1.5 ed-rounded-md ed-text-gray-400 hover:ed-text-gray-600 hover:ed-bg-gray-100 ed-transition-colors',
122
+ onClick: onClose,
123
+ 'aria-label': 'Close drawer',
124
+ children: ED.createElement('svg', {
125
+ className: 'ed-w-5 ed-h-5',
126
+ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor',
127
+ children: ED.createElement('path', { strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: 2, d: 'M6 18L18 6M6 6l12 12' }),
128
+ }),
129
+ })
130
+ : null,
131
+ ].filter(Boolean),
132
+ })
133
+ : null;
134
+
135
+ const bodyElement = ED.createElement('div', {
136
+ className: 'ed-flex-1 ed-overflow-y-auto ed-px-6 ed-py-4',
137
+ style: bodyStyle,
138
+ children,
139
+ });
140
+
141
+ const footerElement = footer
142
+ ? ED.createElement('div', {
143
+ className: 'ed-px-6 ed-py-3 ed-border-t ed-border-gray-200 ed-flex ed-items-center ed-justify-end ed-gap-3 ed-flex-shrink-0',
144
+ style: footerStyle,
145
+ children: footer,
146
+ })
147
+ : null;
148
+
149
+ const resizeHandle = resizable
150
+ ? ED.createElement('div', {
151
+ className: [
152
+ 'ed-absolute ed-bg-transparent ed-hover:ed-bg-blue-200 ed-transition-colors ed-z-10',
153
+ pos.resizeClass,
154
+ ].join(' '),
155
+ })
156
+ : null;
157
+
158
+ const overlayElement = (overlay || mask)
159
+ ? ED.createElement('div', {
160
+ className: [
161
+ 'ed-fixed ed-inset-0 ed-bg-black ed-transition-opacity ed-duration-300',
162
+ nested ? '' : '',
163
+ ].join(' '),
164
+ style: {
165
+ zIndex: zIndex - 1,
166
+ opacity: overlayOpacity,
167
+ },
168
+ onClick: handleOverlayClick,
169
+ })
170
+ : null;
171
+
172
+ return ED.createElement(ED.Fragment, null, [
173
+ overlayElement,
174
+ ED.createElement('div', {
175
+ key: 'drawer',
176
+ className: drawerClasses,
177
+ style: { ...drawerStyle, zIndex },
178
+ onKeyDown: handleKeyDown,
179
+ role: 'dialog',
180
+ 'aria-modal': 'true',
181
+ children: [headerElement, bodyElement, footerElement, resizeHandle].filter(Boolean),
182
+ }),
183
+ ]);
184
+ }
185
+
186
+ Drawer.displayName = 'Drawer';
187
+ Drawer.POSITIONS = DRAWER_POSITIONS;
188
+ Drawer.SIZES = DRAWER_SIZES;
189
+
190
+ module.exports = Drawer;
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Dropdown Component for ElementDrawing Framework
3
+ * Supports triggers, items, dividers, icons, submenus, keyboard navigation, search
4
+ */
5
+ const ED = require('../core/element');
6
+
7
+ function DropdownItem(props) {
8
+ const {
9
+ children,
10
+ label,
11
+ icon,
12
+ disabled = false,
13
+ active = false,
14
+ danger = false,
15
+ shortcut,
16
+ href,
17
+ onClick,
18
+ className = '',
19
+ style = {},
20
+ divided = false,
21
+ selected = false,
22
+ iconRight,
23
+ description,
24
+ } = props;
25
+
26
+ if (divided) {
27
+ return ED.createElement('div', {
28
+ className: 'ed-my-1 ed-border-t ed-border-gray-100',
29
+ role: 'separator',
30
+ });
31
+ }
32
+
33
+ const itemClasses = [
34
+ 'ed-flex ed-items-center ed-px-3 ed-py-2 ed-text-sm ed-transition-colors ed-duration-150 ed-gap-2',
35
+ disabled
36
+ ? 'ed-text-gray-400 ed-cursor-not-allowed'
37
+ : danger
38
+ ? 'ed-text-red-600 hover:ed-bg-red-50 ed-cursor-pointer'
39
+ : active || selected
40
+ ? 'ed-bg-blue-50 ed-text-blue-700 ed-cursor-pointer'
41
+ : 'ed-text-gray-700 hover:ed-bg-gray-50 ed-cursor-pointer',
42
+ className,
43
+ ].filter(Boolean).join(' ');
44
+
45
+ const iconElement = icon
46
+ ? ED.createElement('span', {
47
+ className: `ed-w-4 ed-h-4 ed-flex ed-items-center ed-justify-center ed-flex-shrink-0 ${disabled ? 'ed-text-gray-300' : ''}`,
48
+ children: typeof icon === 'string' ? ED.createElement('i', { className: icon }) : icon,
49
+ })
50
+ : null;
51
+
52
+ const shortcutElement = shortcut
53
+ ? ED.createElement('span', {
54
+ className: 'ed-ml-auto ed-text-xs ed-text-gray-400 ed-font-mono',
55
+ }, shortcut)
56
+ : null;
57
+
58
+ const iconRightElement = iconRight
59
+ ? ED.createElement('span', {
60
+ className: 'ed-ml-auto ed-w-4 ed-h-4 ed-flex ed-items-center ed-justify-center ed-text-gray-400',
61
+ children: typeof iconRight === 'string' ? ED.createElement('i', { className: iconRight }) : iconRight,
62
+ })
63
+ : null;
64
+
65
+ const labelElement = label || children;
66
+ const descriptionElement = description
67
+ ? ED.createElement('span', { className: 'ed-text-xs ed-text-gray-400 ed-block' }, description)
68
+ : null;
69
+
70
+ const contentArea = ED.createElement('div', { className: 'ed-flex-1 ed-min-w-0' }, [
71
+ ED.createElement('span', { key: 'label', className: 'ed-truncate ed-block' }, labelElement),
72
+ descriptionElement,
73
+ ].filter(Boolean));
74
+
75
+ if (href && !disabled) {
76
+ return ED.createElement('a', {
77
+ href,
78
+ className: itemClasses,
79
+ style,
80
+ onClick,
81
+ role: 'menuitem',
82
+ tabIndex: disabled ? -1 : 0,
83
+ children: [iconElement, contentArea, shortcutElement, iconRightElement].filter(Boolean),
84
+ });
85
+ }
86
+
87
+ return ED.createElement('div', {
88
+ className: itemClasses,
89
+ style,
90
+ onClick: disabled ? undefined : onClick,
91
+ role: 'menuitem',
92
+ tabIndex: disabled ? -1 : 0,
93
+ 'aria-disabled': disabled,
94
+ onKeyDown: (e) => {
95
+ if (e.key === 'Enter' && !disabled) onClick?.(e);
96
+ },
97
+ children: [iconElement, contentArea, shortcutElement, iconRightElement].filter(Boolean),
98
+ });
99
+ }
100
+
101
+ DropdownItem.displayName = 'DropdownItem';
102
+
103
+ function Dropdown(props) {
104
+ const {
105
+ children,
106
+ items = [],
107
+ trigger = 'click',
108
+ placement = 'bottom-start',
109
+ disabled = false,
110
+ className = '',
111
+ style = {},
112
+ menuClassName = '',
113
+ menuStyle = {},
114
+ open,
115
+ defaultOpen = false,
116
+ onOpenChange,
117
+ onSelect,
118
+ searchable = false,
119
+ searchPlaceholder = 'Search...',
120
+ onSearch,
121
+ searchValue,
122
+ emptyText = 'No items',
123
+ maxHeight = 300,
124
+ width,
125
+ arrow = false,
126
+ offset = 4,
127
+ closeOnSelect = true,
128
+ closeOnOutsideClick = true,
129
+ overlay,
130
+ renderMenu,
131
+ } = props;
132
+
133
+ const isOpen = open !== undefined ? open : defaultOpen;
134
+
135
+ const placementClasses = {
136
+ 'bottom-start': 'ed-top-full ed-left-0',
137
+ 'bottom': 'ed-top-full ed-left-1/2 ed--translate-x-1/2',
138
+ 'bottom-end': 'ed-top-full ed-right-0',
139
+ 'top-start': 'ed-bottom-full ed-left-0',
140
+ 'top': 'ed-bottom-full ed-left-1/2 ed--translate-x-1/2',
141
+ 'top-end': 'ed-bottom-full ed-right-0',
142
+ 'left-start': 'ed-right-full ed-top-0',
143
+ 'left': 'ed-right-full ed-top-1/2 ed--translate-y-1/2',
144
+ 'left-end': 'ed-right-full ed-bottom-0',
145
+ 'right-start': 'ed-left-full ed-top-0',
146
+ 'right': 'ed-left-full ed-top-1/2 ed--translate-y-1/2',
147
+ 'right-end': 'ed-left-full ed-bottom-0',
148
+ };
149
+
150
+ const handleToggle = () => {
151
+ if (disabled) return;
152
+ onOpenChange?.(!isOpen);
153
+ };
154
+
155
+ const handleItemClick = (item) => {
156
+ if (item.disabled) return;
157
+ onSelect?.(item);
158
+ if (closeOnSelect) onOpenChange?.(false);
159
+ };
160
+
161
+ const handleKeyDown = (e) => {
162
+ if (e.key === 'Escape') onOpenChange?.(false);
163
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
164
+ e.preventDefault();
165
+ const menuEl = e.currentTarget.querySelector('[role="menu"]');
166
+ if (menuEl) {
167
+ const focusable = menuEl.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])');
168
+ const currentIdx = Array.from(focusable).indexOf(document.activeElement);
169
+ const nextIdx = e.key === 'ArrowDown'
170
+ ? Math.min(currentIdx + 1, focusable.length - 1)
171
+ : Math.max(currentIdx - 1, 0);
172
+ focusable[nextIdx]?.focus();
173
+ }
174
+ }
175
+ };
176
+
177
+ const searchElement = searchable
178
+ ? ED.createElement('div', { className: 'ed-px-2 ed-py-2 ed-border-b ed-border-gray-100' },
179
+ ED.createElement('input', {
180
+ className: 'ed-w-full ed-px-2 ed-py-1.5 ed-text-sm ed-border ed-border-gray-200 ed-rounded ed-outline-none focus:ed-ring-1 focus:ed-ring-blue-500',
181
+ placeholder: searchPlaceholder,
182
+ value: searchValue,
183
+ onChange: (e) => onSearch?.(e.target.value),
184
+ autoFocus: true,
185
+ })
186
+ )
187
+ : null;
188
+
189
+ const menuItems = items.length > 0
190
+ ? items.map((item, idx) =>
191
+ ED.createElement(DropdownItem, {
192
+ key: item.key || idx,
193
+ label: item.label,
194
+ icon: item.icon,
195
+ disabled: item.disabled,
196
+ active: item.active,
197
+ danger: item.danger,
198
+ shortcut: item.shortcut,
199
+ href: item.href,
200
+ divided: item.divided,
201
+ selected: item.selected,
202
+ iconRight: item.iconRight,
203
+ description: item.description,
204
+ onClick: () => handleItemClick(item),
205
+ })
206
+ )
207
+ : children;
208
+
209
+ const menuContent = renderMenu
210
+ ? renderMenu(items)
211
+ : ED.createElement('div', {
212
+ className: [
213
+ 'ed-absolute ed-bg-white ed-rounded-lg ed-shadow-lg ed-border ed-border-gray-200 ed-z-50',
214
+ 'ed-min-w-[180px] ed-py-1',
215
+ placementClasses[placement] || placementClasses['bottom-start'],
216
+ arrow ? 'ed-mt-2' : '',
217
+ menuClassName,
218
+ ].filter(Boolean).join(' '),
219
+ style: {
220
+ maxHeight,
221
+ overflowY: 'auto',
222
+ width,
223
+ ...menuStyle,
224
+ },
225
+ role: 'menu',
226
+ children: [
227
+ searchElement,
228
+ menuItems?.length > 0 || children
229
+ ? menuItems
230
+ : ED.createElement('div', { className: 'ed-px-3 ed-py-4 ed-text-center ed-text-sm ed-text-gray-400' }, emptyText),
231
+ ].filter(Boolean),
232
+ });
233
+
234
+ const triggerEvents = {};
235
+ if (trigger === 'click') {
236
+ triggerEvents.onClick = handleToggle;
237
+ } else if (trigger === 'hover') {
238
+ triggerEvents.onMouseEnter = () => onOpenChange?.(true);
239
+ triggerEvents.onMouseLeave = () => onOpenChange?.(false);
240
+ } else if (trigger === 'context') {
241
+ triggerEvents.onContextMenu = (e) => { e.preventDefault(); handleToggle(); };
242
+ } else if (trigger === 'focus') {
243
+ triggerEvents.onFocus = () => onOpenChange?.(true);
244
+ triggerEvents.onBlur = () => onOpenChange?.(false);
245
+ }
246
+
247
+ return ED.createElement('div', {
248
+ className: `ed-relative ed-inline-flex ${className}`,
249
+ style,
250
+ onKeyDown: handleKeyDown,
251
+ ...triggerEvents,
252
+ children: [
253
+ ED.createElement('div', {
254
+ key: 'trigger',
255
+ className: 'ed-dropdown-trigger ed-inline-flex',
256
+ 'aria-haspopup': 'true',
257
+ 'aria-expanded': isOpen,
258
+ children,
259
+ }),
260
+ isOpen ? menuContent : null,
261
+ ],
262
+ });
263
+ }
264
+
265
+ Dropdown.displayName = 'Dropdown';
266
+ Dropdown.Item = DropdownItem;
267
+
268
+ module.exports = Dropdown;