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,310 @@
1
+ /**
2
+ * Slider Component for ElementDrawing Framework
3
+ * Supports single/double handle, marks, tooltip, range, vertical, disabled, custom step, log scale
4
+ */
5
+ const ED = require('../core/element');
6
+
7
+ const SLIDER_SIZES = {
8
+ sm: { trackHeight: 4, handleSize: 14, markFont: 'ed-text-xs' },
9
+ md: { trackHeight: 6, handleSize: 18, markFont: 'ed-text-xs' },
10
+ lg: { trackHeight: 8, handleSize: 22, markFont: 'ed-text-sm' },
11
+ };
12
+
13
+ function Slider(props) {
14
+ const {
15
+ value,
16
+ defaultValue = 0,
17
+ onChange,
18
+ min = 0,
19
+ max = 100,
20
+ step = 1,
21
+ disabled = false,
22
+ range = false,
23
+ vertical = false,
24
+ marks = {},
25
+ tooltip = true,
26
+ tooltipVisible,
27
+ tooltipPlacement = 'top',
28
+ className = '',
29
+ style = {},
30
+ size = 'md',
31
+ included = true,
32
+ dots = false,
33
+ reverse = false,
34
+ logScale = false,
35
+ onAfterChange,
36
+ onFocus,
37
+ onBlur,
38
+ startPoint,
39
+ trackStyle = {},
40
+ handleStyle = {},
41
+ railStyle = {},
42
+ dotStyle = {},
43
+ activeDotStyle = {},
44
+ id,
45
+ name,
46
+ tabIndex,
47
+ ariaLabel,
48
+ ariaLabelForHandle,
49
+ } = props;
50
+
51
+ const sizeConfig = SLIDER_SIZES[size] || SLIDER_SIZES.md;
52
+ const currentValue = value !== undefined ? value : defaultValue;
53
+
54
+ const normalizeValue = (val) => {
55
+ if (logScale && min > 0) {
56
+ return Math.log(val / min) / Math.log(max / min);
57
+ }
58
+ return (val - min) / (max - min);
59
+ };
60
+
61
+ const denormalizeValue = (ratio) => {
62
+ if (logScale && min > 0) {
63
+ return min * Math.pow(max / min, ratio);
64
+ }
65
+ return min + ratio * (max - min);
66
+ };
67
+
68
+ const getStepValue = (val) => {
69
+ const rounded = Math.round((val - min) / step) * step + min;
70
+ return Math.min(max, Math.max(min, rounded));
71
+ };
72
+
73
+ const percent = (val) => {
74
+ const ratio = normalizeValue(val);
75
+ return reverse ? (1 - ratio) * 100 : ratio * 100;
76
+ };
77
+
78
+ const isRange = range && Array.isArray(currentValue);
79
+ const leftPercent = isRange ? percent(Math.min(currentValue[0], currentValue[1])) : 0;
80
+ const rightPercent = isRange ? percent(Math.max(currentValue[0], currentValue[1])) : percent(currentValue);
81
+
82
+ const handleMouseDown = (handleIndex, e) => {
83
+ if (disabled) return;
84
+ e.preventDefault();
85
+
86
+ const sliderEl = e.currentTarget.parentElement;
87
+ const rect = sliderEl.getBoundingClientRect();
88
+
89
+ const getPosition = (event) => {
90
+ if (vertical) {
91
+ const y = event.clientY - rect.top;
92
+ return reverse ? y / rect.height : (1 - y / rect.height);
93
+ }
94
+ const x = event.clientX - rect.left;
95
+ return reverse ? (1 - x / rect.width) : x / rect.width;
96
+ };
97
+
98
+ const handleMove = (event) => {
99
+ const ratio = Math.min(1, Math.max(0, getPosition(event)));
100
+ const rawVal = denormalizeValue(ratio);
101
+ const steppedVal = getStepValue(rawVal);
102
+
103
+ if (isRange) {
104
+ const newValues = [...currentValue];
105
+ newValues[handleIndex] = steppedVal;
106
+ onChange?.(newValues);
107
+ } else {
108
+ onChange?.(steppedVal);
109
+ }
110
+ };
111
+
112
+ const handleUp = () => {
113
+ document.removeEventListener('mousemove', handleMove);
114
+ document.removeEventListener('mouseup', handleUp);
115
+ onAfterChange?.(isRange ? currentValue : currentValue);
116
+ };
117
+
118
+ document.addEventListener('mousemove', handleMove);
119
+ document.addEventListener('mouseup', handleUp);
120
+ };
121
+
122
+ const handleTrackClick = (e) => {
123
+ if (disabled) return;
124
+ const sliderEl = e.currentTarget;
125
+ const rect = sliderEl.getBoundingClientRect();
126
+
127
+ let ratio;
128
+ if (vertical) {
129
+ const y = e.clientY - rect.top;
130
+ ratio = reverse ? y / rect.height : (1 - y / rect.height);
131
+ } else {
132
+ const x = e.clientX - rect.left;
133
+ ratio = reverse ? (1 - x / rect.width) : x / rect.width;
134
+ }
135
+
136
+ const rawVal = denormalizeValue(ratio);
137
+ const steppedVal = getStepValue(rawVal);
138
+
139
+ if (isRange) {
140
+ const dist0 = Math.abs(currentValue[0] - steppedVal);
141
+ const dist1 = Math.abs(currentValue[1] - steppedVal);
142
+ const newValues = [...currentValue];
143
+ newValues[dist0 < dist1 ? 0 : 1] = steppedVal;
144
+ onChange?.(newValues);
145
+ } else {
146
+ onChange?.(steppedVal);
147
+ }
148
+ };
149
+
150
+ const renderTooltip = (val) => {
151
+ if (!tooltip || tooltipVisible === false) return null;
152
+ const positionClass = {
153
+ top: 'ed-bottom-full ed-left-1/2 ed--translate-x-1/2 ed-mb-2',
154
+ bottom: 'ed-top-full ed-left-1/2 ed--translate-x-1/2 ed-mt-2',
155
+ left: 'ed-right-full ed-top-1/2 ed--translate-y-1/2 ed-mr-2',
156
+ right: 'ed-left-full ed-top-1/2 ed--translate-y-1/2 ed-ml-2',
157
+ };
158
+ return ED.createElement('div', {
159
+ className: [
160
+ 'ed-absolute ed-px-2 ed-py-1 ed-text-xs ed-font-medium ed-text-white ed-bg-gray-900 ed-rounded ed-whitespace-nowrap ed-pointer-events-none ed-z-10',
161
+ positionClass[tooltipPlacement] || positionClass.top,
162
+ ].join(' '),
163
+ children: typeof val === 'number' ? Math.round(val * 100) / 100 : val,
164
+ });
165
+ };
166
+
167
+ const markElements = Object.keys(marks).length > 0
168
+ ? Object.entries(marks).map(([key, mark]) => {
169
+ const markVal = Number(key);
170
+ const markPercent = percent(markVal);
171
+ const isInRange = isRange
172
+ ? markVal >= currentValue[0] && markVal <= currentValue[1]
173
+ : markVal <= currentValue;
174
+
175
+ return ED.createElement('div', {
176
+ key: key,
177
+ className: 'ed-absolute',
178
+ style: vertical
179
+ ? { bottom: `${markPercent}%` }
180
+ : { left: `${markPercent}%` },
181
+ children: [
182
+ ED.createElement('div', {
183
+ key: 'dot',
184
+ className: [
185
+ 'ed-w-1 ed-h-1 ed-rounded-full ed-transform ed--translate-x-1/2',
186
+ isInRange ? 'ed-bg-blue-500' : 'ed-bg-gray-300',
187
+ ].join(' '),
188
+ }),
189
+ typeof mark === 'object' && mark.label
190
+ ? ED.createElement('span', {
191
+ key: 'label',
192
+ className: `ed-transform ed--translate-x-1/2 ed-mt-2 ed-block ${sizeConfig.markFont} ed-text-gray-500`,
193
+ }, mark.label)
194
+ : typeof mark === 'string'
195
+ ? ED.createElement('span', {
196
+ key: 'label',
197
+ className: `ed-transform ed--translate-x-1/2 ed-mt-2 ed-block ${sizeConfig.markFont} ed-text-gray-500`,
198
+ }, mark)
199
+ : null,
200
+ ].filter(Boolean),
201
+ });
202
+ })
203
+ : null;
204
+
205
+ const dotElements = dots && Object.keys(marks).length === 0
206
+ ? Array.from({ length: Math.floor((max - min) / step) + 1 }, (_, i) => {
207
+ const dotVal = min + i * step;
208
+ const dotPercent = percent(dotVal);
209
+ const isInRange = isRange
210
+ ? dotVal >= currentValue[0] && dotVal <= currentValue[1]
211
+ : dotVal <= currentValue;
212
+ return ED.createElement('div', {
213
+ key: i,
214
+ className: [
215
+ 'ed-absolute ed-w-2 ed-h-2 ed-rounded-full ed-transform ed--translate-x-1/2 ed--translate-y-1/2',
216
+ isInRange ? 'ed-bg-blue-500' : 'ed-bg-gray-300',
217
+ ].join(' '),
218
+ style: vertical
219
+ ? { bottom: `${dotPercent}%`, left: '50%', transform: 'translateX(-50%) translateY(50%)' }
220
+ : { left: `${dotPercent}%`, top: '50%', transform: 'translateX(-50%) translateY(-50%)' },
221
+ });
222
+ })
223
+ : null;
224
+
225
+ const renderHandle = (val, index) => {
226
+ const handlePercent = percent(val);
227
+ return ED.createElement('div', {
228
+ key: `handle-${index}`,
229
+ className: [
230
+ 'ed-absolute ed-bg-white ed-border-2 ed-border-blue-500 ed-rounded-full',
231
+ 'ed-shadow-md ed-cursor-grab ed-active:ed-cursor-grabbing ed-z-10',
232
+ 'hover:ed-shadow-lg ed-transition-shadow ed-duration-150',
233
+ 'focus:ed-outline-none focus:ed-ring-2 focus:ed-ring-blue-500 focus:ed-ring-offset-2',
234
+ disabled ? 'ed-cursor-not-allowed ed-opacity-50' : '',
235
+ ].join(' '),
236
+ style: {
237
+ width: sizeConfig.handleSize,
238
+ height: sizeConfig.handleSize,
239
+ ...(vertical
240
+ ? { bottom: `calc(${handlePercent}% - ${sizeConfig.handleSize / 2}px)`, left: `calc(50% - ${sizeConfig.handleSize / 2}px)` }
241
+ : { left: `calc(${handlePercent}% - ${sizeConfig.handleSize / 2}px)`, top: `calc(50% - ${sizeConfig.handleSize / 2}px)` }),
242
+ ...handleStyle,
243
+ },
244
+ onMouseDown: (e) => handleMouseDown(index, e),
245
+ role: 'slider',
246
+ 'aria-valuenow': val,
247
+ 'aria-valuemin': min,
248
+ 'aria-valuemax': max,
249
+ 'aria-label': ariaLabelForHandle || ariaLabel,
250
+ tabIndex: disabled ? -1 : (tabIndex || 0),
251
+ onFocus,
252
+ onBlur,
253
+ children: renderTooltip(val),
254
+ });
255
+ };
256
+
257
+ const trackElement = included
258
+ ? ED.createElement('div', {
259
+ className: 'ed-absolute ed-bg-blue-500 ed-rounded-full',
260
+ style: {
261
+ ...(vertical
262
+ ? { bottom: `${leftPercent}%`, height: `${rightPercent - leftPercent}%`, width: sizeConfig.trackHeight, left: `calc(50% - ${sizeConfig.trackHeight / 2}px)` }
263
+ : { left: `${leftPercent}%`, width: `${rightPercent - leftPercent}%`, height: sizeConfig.trackHeight, top: `calc(50% - ${sizeConfig.trackHeight / 2}px)` }),
264
+ ...trackStyle,
265
+ },
266
+ })
267
+ : null;
268
+
269
+ const railElement = ED.createElement('div', {
270
+ className: 'ed-absolute ed-bg-gray-200 ed-rounded-full',
271
+ style: {
272
+ ...(vertical
273
+ ? { width: sizeConfig.trackHeight, left: `calc(50% - ${sizeConfig.trackHeight / 2}px)`, top: 0, bottom: 0 }
274
+ : { height: sizeConfig.trackHeight, top: `calc(50% - ${sizeConfig.trackHeight / 2}px)`, left: 0, right: 0 }),
275
+ ...railStyle,
276
+ },
277
+ });
278
+
279
+ const sliderClasses = [
280
+ 'ed-relative ed-select-none',
281
+ vertical ? 'ed-h-full' : 'ed-w-full',
282
+ disabled ? 'ed-opacity-50 ed-pointer-events-none' : '',
283
+ className,
284
+ ].filter(Boolean).join(' ');
285
+
286
+ const sliderStyle = vertical
287
+ ? { width: sizeConfig.handleSize + 10, height: '100%', ...style }
288
+ : { height: sizeConfig.handleSize + 10, ...style };
289
+
290
+ return ED.createElement('div', {
291
+ id,
292
+ className: sliderClasses,
293
+ style: sliderStyle,
294
+ onClick: handleTrackClick,
295
+ children: [
296
+ railElement,
297
+ trackElement,
298
+ dotElements,
299
+ isRange
300
+ ? [renderHandle(currentValue[0], 0), renderHandle(currentValue[1], 1)]
301
+ : renderHandle(currentValue, 0),
302
+ markElements,
303
+ ].flat().filter(Boolean),
304
+ });
305
+ }
306
+
307
+ Slider.displayName = 'Slider';
308
+ Slider.SIZES = SLIDER_SIZES;
309
+
310
+ module.exports = Slider;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Spinner Component for ElementDrawing Framework
3
+ * Supports sizes, color variants, overlay, fullscreen, custom SVG, dot spinner, bar spinner
4
+ */
5
+ const ED = require('../core/element');
6
+
7
+ const SPINNER_SIZES = {
8
+ xs: { main: 'ed-w-4 ed-h-4', border: 'ed-border-2', dot: 'ed-w-1 ed-h-1', bar: 'ed-w-0.5 ed-h-3', text: 'ed-text-xs' },
9
+ sm: { main: 'ed-w-6 ed-h-6', border: 'ed-border-2', dot: 'ed-w-1.5 ed-h-1.5', bar: 'ed-w-1 ed-h-4', text: 'ed-text-xs' },
10
+ md: { main: 'ed-w-8 ed-h-8', border: 'ed-border-[3px]', dot: 'ed-w-2 ed-h-2', bar: 'ed-w-1 ed-h-5', text: 'ed-text-sm' },
11
+ lg: { main: 'ed-w-12 ed-h-12', border: 'ed-border-4', dot: 'ed-w-2.5 ed-h-2.5', bar: 'ed-w-1.5 ed-h-7', text: 'ed-text-base' },
12
+ xl: { main: 'ed-w-16 ed-h-16', border: 'ed-border-4', dot: 'ed-w-3 ed-h-3', bar: 'ed-w-2 ed-h-9', text: 'ed-text-lg' },
13
+ };
14
+
15
+ const SPINNER_COLORS = {
16
+ blue: { border: 'ed-border-blue-500', text: 'ed-text-blue-500', dot: 'ed-bg-blue-500', bar: 'ed-bg-blue-500' },
17
+ green: { border: 'ed-border-green-500', text: 'ed-text-green-500', dot: 'ed-bg-green-500', bar: 'ed-bg-green-500' },
18
+ red: { border: 'ed-border-red-500', text: 'ed-text-red-500', dot: 'ed-bg-red-500', bar: 'ed-bg-red-500' },
19
+ yellow: { border: 'ed-border-yellow-500', text: 'ed-text-yellow-500', dot: 'ed-bg-yellow-500', bar: 'ed-bg-yellow-500' },
20
+ purple: { border: 'ed-border-purple-500', text: 'ed-text-purple-500', dot: 'ed-bg-purple-500', bar: 'ed-bg-purple-500' },
21
+ gray: { border: 'ed-border-gray-500', text: 'ed-text-gray-500', dot: 'ed-bg-gray-500', bar: 'ed-bg-gray-500' },
22
+ white: { border: 'ed-border-white', text: 'ed-text-white', dot: 'ed-bg-white', bar: 'ed-bg-white' },
23
+ };
24
+
25
+ function CircleSpinner(props) {
26
+ const { size = 'md', color = 'blue', sizeConfig, colorConfig } = props;
27
+ const sConfig = sizeConfig || SPINNER_SIZES[size] || SPINNER_SIZES.md;
28
+ const cConfig = colorConfig || SPINNER_COLORS[color] || SPINNER_COLORS.blue;
29
+
30
+ return ED.createElement('div', {
31
+ className: [
32
+ sConfig.main,
33
+ cConfig.border,
34
+ 'ed-border-t-transparent',
35
+ 'ed-rounded-full ed-animate-spin',
36
+ ].join(' '),
37
+ role: 'status',
38
+ 'aria-label': 'Loading',
39
+ });
40
+ }
41
+
42
+ function DotSpinner(props) {
43
+ const { size = 'md', color = 'blue', sizeConfig, colorConfig } = props;
44
+ const sConfig = sizeConfig || SPINNER_SIZES[size] || SPINNER_SIZES.md;
45
+ const cConfig = colorConfig || SPINNER_COLORS[color] || SPINNER_COLORS.blue;
46
+
47
+ return ED.createElement('div', {
48
+ className: 'ed-flex ed-items-center ed-gap-1',
49
+ role: 'status',
50
+ 'aria-label': 'Loading',
51
+ children: [0, 1, 2].map(i =>
52
+ ED.createElement('div', {
53
+ key: i,
54
+ className: [
55
+ sConfig.dot,
56
+ cConfig.dot,
57
+ 'ed-rounded-full',
58
+ ].join(' '),
59
+ style: {
60
+ animation: `ed-bounce 1.4s ease-in-out ${i * 0.16}s infinite both`,
61
+ },
62
+ })
63
+ ),
64
+ });
65
+ }
66
+
67
+ function BarSpinner(props) {
68
+ const { size = 'md', color = 'blue', sizeConfig, colorConfig } = props;
69
+ const sConfig = sizeConfig || SPINNER_SIZES[size] || SPINNER_SIZES.md;
70
+ const cConfig = colorConfig || SPINNER_COLORS[color] || SPINNER_COLORS.blue;
71
+
72
+ return ED.createElement('div', {
73
+ className: 'ed-flex ed-items-end ed-gap-0.5',
74
+ style: { height: sConfig.bar?.split(' ').find(s => s.startsWith('ed-h-'))?.replace('ed-h-', '') + 'px' || '20px' },
75
+ role: 'status',
76
+ 'aria-label': 'Loading',
77
+ children: [0, 1, 2, 3].map(i =>
78
+ ED.createElement('div', {
79
+ key: i,
80
+ className: [
81
+ sConfig.bar,
82
+ cConfig.bar,
83
+ 'ed-rounded-sm',
84
+ ].join(' '),
85
+ style: {
86
+ animation: `ed-bar-stretch 1.2s ease-in-out ${i * 0.1}s infinite`,
87
+ },
88
+ })
89
+ ),
90
+ });
91
+ }
92
+
93
+ function Spinner(props) {
94
+ const {
95
+ variant = 'circle',
96
+ size = 'md',
97
+ color = 'blue',
98
+ overlay = false,
99
+ fullscreen = false,
100
+ className = '',
101
+ style = {},
102
+ label,
103
+ labelPosition = 'bottom',
104
+ customSvg,
105
+ spinning = true,
106
+ children,
107
+ delay = 0,
108
+ tip,
109
+ } = props;
110
+
111
+ if (!spinning) return children || null;
112
+
113
+ const sizeConfig = SPINNER_SIZES[size] || SPINNER_SIZES.md;
114
+ const colorConfig = SPINNER_COLORS[color] || SPINNER_COLORS.blue;
115
+
116
+ const renderSpinner = () => {
117
+ if (customSvg) {
118
+ return ED.createElement('div', {
119
+ className: `ed-animate-spin ${sizeConfig.main}`,
120
+ role: 'status',
121
+ 'aria-label': 'Loading',
122
+ children: customSvg,
123
+ });
124
+ }
125
+
126
+ switch (variant) {
127
+ case 'dot':
128
+ return ED.createElement(DotSpinner, { size, color, sizeConfig, colorConfig });
129
+ case 'bar':
130
+ return ED.createElement(BarSpinner, { size, color, sizeConfig, colorConfig });
131
+ case 'circle':
132
+ default:
133
+ return ED.createElement(CircleSpinner, { size, color, sizeConfig, colorConfig });
134
+ }
135
+ };
136
+
137
+ const labelElement = label || tip
138
+ ? ED.createElement('span', {
139
+ className: `${sizeConfig.text} ${colorConfig.text} ed-font-medium`,
140
+ }, label || tip)
141
+ : null;
142
+
143
+ const spinnerContent = ED.createElement('div', {
144
+ className: [
145
+ 'ed-inline-flex ed-items-center ed-gap-2',
146
+ labelPosition === 'bottom' ? 'ed-flex-col' : labelPosition === 'right' ? 'ed-flex-row' : 'ed-flex-col',
147
+ className,
148
+ ].filter(Boolean).join(' '),
149
+ style,
150
+ children: [
151
+ labelPosition === 'bottom' ? renderSpinner() : labelElement,
152
+ labelPosition === 'bottom' ? labelElement : renderSpinner(),
153
+ ],
154
+ });
155
+
156
+ if (fullscreen) {
157
+ return ED.createElement('div', {
158
+ className: 'ed-fixed ed-inset-0 ed-bg-white ed-bg-opacity-80 ed-flex ed-items-center ed-justify-center ed-z-[9999]',
159
+ children: spinnerContent,
160
+ });
161
+ }
162
+
163
+ if (overlay) {
164
+ return ED.createElement('div', {
165
+ className: 'ed-relative',
166
+ children: [
167
+ children,
168
+ ED.createElement('div', {
169
+ key: 'overlay',
170
+ className: 'ed-absolute ed-inset-0 ed-bg-white ed-bg-opacity-70 ed-flex ed-items-center ed-justify-center ed-rounded-inherit ed-z-10',
171
+ children: spinnerContent,
172
+ }),
173
+ ],
174
+ });
175
+ }
176
+
177
+ if (children) {
178
+ return ED.createElement('div', { className: 'ed-relative' }, [
179
+ children,
180
+ ED.createElement('div', {
181
+ key: 'spinner-overlay',
182
+ className: 'ed-absolute ed-inset-0 ed-flex ed-items-center ed-justify-center ed-bg-white ed-bg-opacity-60 ed-z-10',
183
+ children: renderSpinner(),
184
+ }),
185
+ ]);
186
+ }
187
+
188
+ return spinnerContent;
189
+ }
190
+
191
+ Spinner.displayName = 'Spinner';
192
+ Spinner.Circle = CircleSpinner;
193
+ Spinner.Dot = DotSpinner;
194
+ Spinner.Bar = BarSpinner;
195
+ Spinner.SIZES = SPINNER_SIZES;
196
+ Spinner.COLORS = SPINNER_COLORS;
197
+
198
+ module.exports = Spinner;
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Switch Component for ElementDrawing Framework
3
+ * Supports sizes, colors, disabled, loading, icons, labels, custom width/height
4
+ */
5
+ const ED = require('../core/element');
6
+
7
+ const SWITCH_SIZES = {
8
+ sm: { width: 36, height: 20, handleSize: 14, text: 'ed-text-[10px]', translate: 16 },
9
+ md: { width: 44, height: 24, handleSize: 18, text: 'ed-text-xs', translate: 20 },
10
+ lg: { width: 56, height: 28, handleSize: 22, text: 'ed-text-sm', translate: 28 },
11
+ xl: { width: 64, height: 32, handleSize: 26, text: 'ed-text-sm', translate: 32 },
12
+ };
13
+
14
+ const SWITCH_COLORS = {
15
+ blue: 'ed-bg-blue-600',
16
+ green: 'ed-bg-green-600',
17
+ red: 'ed-bg-red-600',
18
+ yellow: 'ed-bg-yellow-500',
19
+ purple: 'ed-bg-purple-600',
20
+ cyan: 'ed-bg-cyan-600',
21
+ orange: 'ed-bg-orange-500',
22
+ pink: 'ed-bg-pink-600',
23
+ };
24
+
25
+ function Switch(props) {
26
+ const {
27
+ checked,
28
+ defaultChecked = false,
29
+ onChange,
30
+ disabled = false,
31
+ loading = false,
32
+ size = 'md',
33
+ color = 'blue',
34
+ checkedIcon,
35
+ uncheckedIcon,
36
+ checkedChildren,
37
+ uncheckedChildren,
38
+ className = '',
39
+ style = {},
40
+ width: customWidth,
41
+ height: customHeight,
42
+ id,
43
+ name,
44
+ value,
45
+ autoFocus = false,
46
+ tabIndex,
47
+ onFocus,
48
+ onBlur,
49
+ ariaLabel,
50
+ beforeChange,
51
+ label,
52
+ labelPosition = 'right',
53
+ } = props;
54
+
55
+ const isChecked = checked !== undefined ? checked : defaultChecked;
56
+ const sizeConfig = SWITCH_SIZES[size] || SWITCH_SIZES.md;
57
+ const colorClass = SWITCH_COLORS[color] || SWITCH_COLORS.blue;
58
+
59
+ const switchWidth = customWidth || sizeConfig.width;
60
+ const switchHeight = customHeight || sizeConfig.height;
61
+ const handleSize = sizeConfig.handleSize;
62
+ const translateX = isChecked ? (switchWidth - handleSize - 6) : 0;
63
+
64
+ const trackClasses = [
65
+ 'ed-relative ed-inline-flex ed-items-center ed-rounded-full ed-transition-colors ed-duration-200 ed-cursor-pointer',
66
+ isChecked ? colorClass : 'ed-bg-gray-300',
67
+ disabled || loading ? 'ed-opacity-50 ed-cursor-not-allowed' : '',
68
+ className,
69
+ ].filter(Boolean).join(' ');
70
+
71
+ const handleClasses = [
72
+ 'ed-absolute ed-bg-white ed-rounded-full ed-shadow-sm ed-transition-transform ed-duration-200 ed-flex ed-items-center ed-justify-center',
73
+ loading ? '' : '',
74
+ ].join(' ');
75
+
76
+ const loadingSpinner = loading
77
+ ? ED.createElement('svg', {
78
+ className: `ed-animate-spin ed-text-${isChecked ? color : 'gray'}-500`,
79
+ style: { width: handleSize * 0.5, height: handleSize * 0.5 },
80
+ fill: 'none',
81
+ viewBox: '0 0 24 24',
82
+ children: [
83
+ ED.createElement('circle', {
84
+ key: 'bg',
85
+ className: 'ed-opacity-25',
86
+ cx: '12', cy: '12', r: '10',
87
+ stroke: 'currentColor', strokeWidth: '4',
88
+ }),
89
+ ED.createElement('path', {
90
+ key: 'fg',
91
+ className: 'ed-opacity-75',
92
+ fill: 'currentColor',
93
+ d: 'M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z',
94
+ }),
95
+ ],
96
+ })
97
+ : null;
98
+
99
+ const checkedIconElement = checkedIcon && isChecked && !loading
100
+ ? ED.createElement('span', {
101
+ className: 'ed-text-white',
102
+ style: { width: handleSize * 0.5, height: handleSize * 0.5 },
103
+ children: typeof checkedIcon === 'string' ? ED.createElement('i', { className: checkedIcon }) : checkedIcon,
104
+ })
105
+ : null;
106
+
107
+ const uncheckedIconElement = uncheckedIcon && !isChecked && !loading
108
+ ? ED.createElement('span', {
109
+ className: 'ed-text-gray-500',
110
+ style: { width: handleSize * 0.5, height: handleSize * 0.5 },
111
+ children: typeof uncheckedIcon === 'string' ? ED.createElement('i', { className: uncheckedIcon }) : uncheckedIcon,
112
+ })
113
+ : null;
114
+
115
+ const checkedText = checkedChildren && isChecked
116
+ ? ED.createElement('span', {
117
+ className: `${sizeConfig.text} ed-text-white ed-font-medium ed-pl-2 ed-pr-${handleSize / 4 + 2} ed-select-none`,
118
+ style: { paddingRight: handleSize + 4 },
119
+ }, checkedChildren)
120
+ : null;
121
+
122
+ const uncheckedText = uncheckedChildren && !isChecked
123
+ ? ED.createElement('span', {
124
+ className: `${sizeConfig.text} ed-text-gray-500 ed-font-medium ed-pr-2 ed-pl-${handleSize / 4 + 2} ed-select-none`,
125
+ style: { paddingLeft: handleSize + 4 },
126
+ }, uncheckedChildren)
127
+ : null;
128
+
129
+ const handleClick = () => {
130
+ if (disabled || loading) return;
131
+ if (beforeChange) {
132
+ const result = beforeChange(!isChecked);
133
+ if (result === false) return;
134
+ }
135
+ onChange?.(!isChecked);
136
+ };
137
+
138
+ const handleKeyDown = (e) => {
139
+ if (e.key === 'Enter' || e.key === ' ') {
140
+ e.preventDefault();
141
+ handleClick();
142
+ }
143
+ };
144
+
145
+ const switchElement = ED.createElement('button', {
146
+ id,
147
+ className: trackClasses,
148
+ style: {
149
+ width: switchWidth,
150
+ height: switchHeight,
151
+ minWidth: switchWidth,
152
+ ...style,
153
+ },
154
+ onClick: handleClick,
155
+ onKeyDown: handleKeyDown,
156
+ role: 'switch',
157
+ 'aria-checked': isChecked,
158
+ 'aria-label': ariaLabel || label,
159
+ tabIndex: disabled ? -1 : (tabIndex || 0),
160
+ autoFocus,
161
+ onFocus,
162
+ onBlur,
163
+ disabled: disabled || loading,
164
+ children: [
165
+ uncheckedText,
166
+ checkedText,
167
+ ED.createElement('span', {
168
+ key: 'handle',
169
+ className: handleClasses,
170
+ style: {
171
+ width: handleSize,
172
+ height: handleSize,
173
+ left: 3,
174
+ transform: `translateX(${translateX}px)`,
175
+ },
176
+ children: [loadingSpinner, checkedIconElement, uncheckedIconElement].filter(Boolean)[0] || null,
177
+ }),
178
+ ],
179
+ });
180
+
181
+ const labelElement = label
182
+ ? ED.createElement('span', {
183
+ className: `ed-text-sm ed-font-medium ${disabled ? 'ed-text-gray-400' : 'ed-text-gray-700'}`,
184
+ }, label)
185
+ : null;
186
+
187
+ if (label) {
188
+ return ED.createElement('div', {
189
+ className: `ed-inline-flex ed-items-center ed-gap-2 ${labelPosition === 'left' ? 'ed-flex-row-reverse' : ''}`,
190
+ children: [switchElement, labelElement],
191
+ });
192
+ }
193
+
194
+ return switchElement;
195
+ }
196
+
197
+ Switch.displayName = 'Switch';
198
+ Switch.SIZES = SWITCH_SIZES;
199
+ Switch.COLORS = SWITCH_COLORS;
200
+
201
+ module.exports = Switch;