@auvyx/ui 0.0.0-stage → 0.1.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 (143) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +43 -2
  3. package/dist/auvyx.bundle.css +5941 -0
  4. package/dist/auvyx.chart.min.js +2 -0
  5. package/dist/auvyx.corners.min.js +2 -0
  6. package/dist/auvyx.css +10880 -0
  7. package/dist/auvyx.js +3222 -0
  8. package/dist/auvyx.min.css +2 -0
  9. package/dist/auvyx.min.js +19 -0
  10. package/dist/custom-elements.json +948 -0
  11. package/dist/elements/calendar.min.js +2 -0
  12. package/dist/elements/context-menu.min.js +2 -0
  13. package/dist/elements/data-table.min.js +2 -0
  14. package/dist/elements/date-picker.min.js +2 -0
  15. package/dist/elements/index.min.js +2 -0
  16. package/dist/elements/menu.min.js +2 -0
  17. package/dist/elements/resizable.min.js +2 -0
  18. package/dist/js/accordion.min.js +2 -0
  19. package/dist/js/calendar.min.js +2 -0
  20. package/dist/js/combobox.min.js +2 -0
  21. package/dist/js/command.min.js +2 -0
  22. package/dist/js/context-menu.min.js +2 -0
  23. package/dist/js/copy.min.js +2 -0
  24. package/dist/js/data-table.min.js +2 -0
  25. package/dist/js/date-picker.min.js +2 -0
  26. package/dist/js/drawer.min.js +2 -0
  27. package/dist/js/dropdown-menu.min.js +2 -0
  28. package/dist/js/manifest.json +114 -0
  29. package/dist/js/menu.min.js +2 -0
  30. package/dist/js/popover.min.js +2 -0
  31. package/dist/js/resizable.min.js +2 -0
  32. package/dist/js/select.min.js +2 -0
  33. package/dist/js/sidebar.min.js +2 -0
  34. package/dist/js/slider.min.js +2 -0
  35. package/dist/js/tabs.min.js +2 -0
  36. package/dist/js/toast.min.js +19 -0
  37. package/dist/styles/auvyx-luma.min.css +3 -0
  38. package/dist/styles/auvyx-lyra.min.css +3 -0
  39. package/dist/styles/auvyx-maia.min.css +3 -0
  40. package/dist/styles/auvyx-mira.min.css +3 -0
  41. package/dist/styles/auvyx-nova.min.css +3 -0
  42. package/dist/styles/auvyx-rhea.min.css +3 -0
  43. package/dist/styles/auvyx-sera.min.css +3 -0
  44. package/dist/styles/auvyx-vega.min.css +3 -0
  45. package/dist/types/elements.d.ts +262 -0
  46. package/dist/types/react.d.ts +181 -0
  47. package/dist/types/vue.d.ts +155 -0
  48. package/dist/ui/accordion.css +91 -0
  49. package/dist/ui/accordion.js +249 -0
  50. package/dist/ui/accordion.native.css +24 -0
  51. package/dist/ui/alert.css +119 -0
  52. package/dist/ui/aspect-ratio.css +38 -0
  53. package/dist/ui/attachment.css +211 -0
  54. package/dist/ui/avatar.css +204 -0
  55. package/dist/ui/badge.css +183 -0
  56. package/dist/ui/base.css +1200 -0
  57. package/dist/ui/breadcrumb.css +90 -0
  58. package/dist/ui/bubble.css +171 -0
  59. package/dist/ui/button-group.css +110 -0
  60. package/dist/ui/button.css +328 -0
  61. package/dist/ui/calendar.css +260 -0
  62. package/dist/ui/calendar.js +317 -0
  63. package/dist/ui/card.css +126 -0
  64. package/dist/ui/carousel.css +172 -0
  65. package/dist/ui/chart.css +130 -0
  66. package/dist/ui/checkbox.css +119 -0
  67. package/dist/ui/code-block.css +235 -0
  68. package/dist/ui/combobox.css +500 -0
  69. package/dist/ui/combobox.js +757 -0
  70. package/dist/ui/command.css +366 -0
  71. package/dist/ui/command.js +382 -0
  72. package/dist/ui/context-menu.css +17 -0
  73. package/dist/ui/context-menu.js +52 -0
  74. package/dist/ui/copy.js +56 -0
  75. package/dist/ui/data-table.css +114 -0
  76. package/dist/ui/data-table.js +140 -0
  77. package/dist/ui/date-picker.css +14 -0
  78. package/dist/ui/date-picker.js +101 -0
  79. package/dist/ui/dialog.css +363 -0
  80. package/dist/ui/dialog.native.css +6 -0
  81. package/dist/ui/drawer.css +238 -0
  82. package/dist/ui/drawer.js +265 -0
  83. package/dist/ui/dropdown-menu.css +193 -0
  84. package/dist/ui/dropdown-menu.js +388 -0
  85. package/dist/ui/empty.css +111 -0
  86. package/dist/ui/field.css +293 -0
  87. package/dist/ui/hover-card.css +85 -0
  88. package/dist/ui/input-group.css +269 -0
  89. package/dist/ui/input-otp.css +101 -0
  90. package/dist/ui/input.css +117 -0
  91. package/dist/ui/item.css +223 -0
  92. package/dist/ui/kbd.css +50 -0
  93. package/dist/ui/label.css +48 -0
  94. package/dist/ui/manifest.json +667 -0
  95. package/dist/ui/marker.css +66 -0
  96. package/dist/ui/menu.js +161 -0
  97. package/dist/ui/menubar.css +55 -0
  98. package/dist/ui/message-scroller.css +83 -0
  99. package/dist/ui/message.css +69 -0
  100. package/dist/ui/native-select.css +130 -0
  101. package/dist/ui/navigation-menu.css +166 -0
  102. package/dist/ui/popover.css +154 -0
  103. package/dist/ui/popover.js +268 -0
  104. package/dist/ui/popover.native.css +84 -0
  105. package/dist/ui/progress.css +29 -0
  106. package/dist/ui/questionnaire.css +188 -0
  107. package/dist/ui/radio-group.css +115 -0
  108. package/dist/ui/resizable.css +94 -0
  109. package/dist/ui/resizable.js +95 -0
  110. package/dist/ui/scroll-area.css +43 -0
  111. package/dist/ui/select.css +289 -0
  112. package/dist/ui/select.js +646 -0
  113. package/dist/ui/sheet.css +219 -0
  114. package/dist/ui/sidebar.css +716 -0
  115. package/dist/ui/sidebar.js +287 -0
  116. package/dist/ui/skeleton.css +13 -0
  117. package/dist/ui/slider.css +184 -0
  118. package/dist/ui/slider.js +206 -0
  119. package/dist/ui/switch.css +156 -0
  120. package/dist/ui/table.css +157 -0
  121. package/dist/ui/tabs.css +240 -0
  122. package/dist/ui/tabs.js +288 -0
  123. package/dist/ui/textarea.css +91 -0
  124. package/dist/ui/toast.css +110 -0
  125. package/dist/ui/toast.js +378 -0
  126. package/dist/ui/toggle-group.css +42 -0
  127. package/dist/ui/toggle.css +150 -0
  128. package/dist/ui/tooltip.css +319 -0
  129. package/dist/ui/trace.css +142 -0
  130. package/dist/ui/typography.css +184 -0
  131. package/dist/wc/accordion.min.js +2 -0
  132. package/dist/wc/combobox.min.js +2 -0
  133. package/dist/wc/command.min.js +2 -0
  134. package/dist/wc/drawer.min.js +2 -0
  135. package/dist/wc/dropdown-menu.min.js +2 -0
  136. package/dist/wc/manifest.json +154 -0
  137. package/dist/wc/popover.min.js +2 -0
  138. package/dist/wc/select.min.js +2 -0
  139. package/dist/wc/sidebar.min.js +2 -0
  140. package/dist/wc/slider.min.js +2 -0
  141. package/dist/wc/tabs.min.js +2 -0
  142. package/dist/wc/toast.min.js +19 -0
  143. package/package.json +62 -4
@@ -0,0 +1,287 @@
1
+ /* @auvyx/ui v0.1.0 | MIT | based on Basecoat (MIT) */
2
+ // Basecoat core, once per page.
3
+ if (!window.basecoat) (() => {
4
+ const componentRegistry = {};
5
+ let observer = null;
6
+
7
+ const registerComponent = (name, selectorOrOptions, initFunction) => {
8
+ const options = typeof selectorOrOptions === 'object'
9
+ ? selectorOrOptions
10
+ : { selector: selectorOrOptions, init: initFunction };
11
+
12
+ componentRegistry[name] = {
13
+ selector: options.selector,
14
+ init: options.init,
15
+ refresh: options.refresh,
16
+ };
17
+ };
18
+
19
+ const initComponent = (element, componentName) => {
20
+ const component = componentRegistry[componentName];
21
+ if (!component) return;
22
+
23
+ try {
24
+ component.init(element);
25
+ if (element.hasAttribute(`data-${componentName}-initialized`)) {
26
+ element.dataset.basecoatComponent = componentName;
27
+ }
28
+ } catch (error) {
29
+ console.error(`Failed to initialize ${componentName}:`, error);
30
+ if (typeof element._destroy === 'function') {
31
+ try {
32
+ element._destroy();
33
+ } catch (destroyError) {
34
+ console.error(`Failed to clean up ${componentName} after initialization error:`, destroyError);
35
+ }
36
+ }
37
+ delete element._destroy;
38
+ element.removeAttribute(`data-${componentName}-initialized`);
39
+ delete element.dataset.basecoatComponent;
40
+ }
41
+ };
42
+
43
+ const destroyComponent = (element) => {
44
+ if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
45
+ const componentName = element.dataset?.basecoatComponent;
46
+
47
+ if (typeof element._destroy === 'function') {
48
+ try {
49
+ element._destroy();
50
+ } catch (error) {
51
+ console.error('Failed to destroy Basecoat component:', error);
52
+ }
53
+ }
54
+
55
+ delete element._destroy;
56
+ if (componentName) element.removeAttribute(`data-${componentName}-initialized`);
57
+ delete element.dataset.basecoatComponent;
58
+ };
59
+
60
+ const destroyRemovedComponents = (node) => {
61
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
62
+ if (node.isConnected) return;
63
+
64
+ if (node.dataset?.basecoatComponent) destroyComponent(node);
65
+ node.querySelectorAll('[data-basecoat-component]').forEach(destroyComponent);
66
+ };
67
+
68
+ const uniqueElements = (elements) => Array.from(new Set(elements));
69
+
70
+ const getComponentElements = (componentName, selector, force = false) => {
71
+ const elements = Array.from(document.querySelectorAll(selector));
72
+ if (force) {
73
+ elements.push(...document.querySelectorAll(`[data-basecoat-component="${componentName}"]`));
74
+ }
75
+ return uniqueElements(elements);
76
+ };
77
+
78
+ const initAllComponents = (options = {}) => {
79
+ const force = options.force === true;
80
+ Object.entries(componentRegistry).forEach(([name, { selector }]) => {
81
+ getComponentElements(name, selector, force).forEach((element) => {
82
+ const wasComponent = element.dataset?.basecoatComponent === name;
83
+ if (force) destroyComponent(element);
84
+ if (wasComponent || element.matches(selector)) initComponent(element, name);
85
+ });
86
+ });
87
+ };
88
+
89
+ const initNewComponents = (node) => {
90
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
91
+
92
+ Object.entries(componentRegistry).forEach(([name, { selector }]) => {
93
+ if (node.matches(selector)) initComponent(node, name);
94
+ node.querySelectorAll(selector).forEach(element => initComponent(element, name));
95
+ });
96
+ };
97
+
98
+ const refreshComponent = (element) => {
99
+ if (!element) return;
100
+ if (typeof element.refresh === 'function') {
101
+ element.refresh();
102
+ return;
103
+ }
104
+
105
+ const componentName = element.dataset?.basecoatComponent;
106
+ const component = componentName ? componentRegistry[componentName] : null;
107
+ if (component?.refresh) {
108
+ component.refresh(element);
109
+ }
110
+ };
111
+
112
+ const startObserver = () => {
113
+ if (observer) return;
114
+
115
+ observer = new MutationObserver((mutations) => {
116
+ mutations.forEach((mutation) => {
117
+ mutation.addedNodes.forEach(initNewComponents);
118
+ mutation.removedNodes.forEach(destroyRemovedComponents);
119
+ });
120
+ });
121
+
122
+ observer.observe(document.body, { childList: true, subtree: true });
123
+ };
124
+
125
+ const stopObserver = () => {
126
+ if (!observer) return;
127
+ observer.disconnect();
128
+ observer = null;
129
+ };
130
+
131
+ const initRegisteredComponent = (componentName, options = {}) => {
132
+ const component = componentRegistry[componentName];
133
+ if (!component) {
134
+ console.warn(`Component '${componentName}' not found in registry`);
135
+ return;
136
+ }
137
+
138
+ const force = options.force === true;
139
+ getComponentElements(componentName, component.selector, force).forEach((element) => {
140
+ const wasComponent = element.dataset?.basecoatComponent === componentName;
141
+ if (force) destroyComponent(element);
142
+ if (wasComponent || element.matches(component.selector)) initComponent(element, componentName);
143
+ });
144
+ };
145
+
146
+ const initAllRegisteredComponents = (options = {}) => {
147
+ initAllComponents(options);
148
+ };
149
+
150
+ const setTheme = (mode) => {
151
+ const dark = mode === 'dark';
152
+ document.documentElement.classList.toggle('dark', dark);
153
+ try { localStorage.setItem('themeMode', dark ? 'dark' : 'light'); } catch (_) {}
154
+ document.dispatchEvent(new CustomEvent('basecoat:themechange', { detail: { mode: dark ? 'dark' : 'light' } }));
155
+ };
156
+
157
+ const getTheme = () => document.documentElement.classList.contains('dark') ? 'dark' : 'light';
158
+
159
+ window.basecoat = {
160
+ register: registerComponent,
161
+ init: initRegisteredComponent,
162
+ initAll: initAllRegisteredComponents,
163
+ refresh: refreshComponent,
164
+ start: startObserver,
165
+ stop: stopObserver,
166
+ theme: {
167
+ get: getTheme,
168
+ set: setTheme,
169
+ toggle: () => setTheme(getTheme() === 'dark' ? 'light' : 'dark'),
170
+ },
171
+ };
172
+
173
+ document.addEventListener('DOMContentLoaded', () => {
174
+ initAllComponents();
175
+ startObserver();
176
+ });
177
+ })();
178
+
179
+ (() => {
180
+ const initSidebar = (sidebarComponent) => {
181
+ if (sidebarComponent.dataset.sidebarInitialized && typeof sidebarComponent.toggle === 'function') return;
182
+
183
+ // In-flow sidebars (inside a dialog, popover or layout cell) have no open state.
184
+ if (sidebarComponent.dataset.position === 'static') {
185
+ sidebarComponent.dataset.sidebarInitialized = 'true';
186
+ return;
187
+ }
188
+
189
+ const initialOpen = sidebarComponent.dataset.initialOpen !== 'false';
190
+ const initialMobileOpen = sidebarComponent.dataset.initialMobileOpen === 'true';
191
+ const breakpoint = parseInt(sidebarComponent.dataset.breakpoint) || 768;
192
+
193
+ let open = breakpoint > 0
194
+ ? (window.innerWidth >= breakpoint ? initialOpen : initialMobileOpen)
195
+ : initialOpen;
196
+
197
+ // Collapsing to an icon rail keeps the sidebar usable on desktop.
198
+ const isIconRail = () => sidebarComponent.dataset.collapsible === 'icon' && (breakpoint <= 0 || window.innerWidth >= breakpoint);
199
+
200
+ const updateState = () => {
201
+ const rail = isIconRail();
202
+ // An icon rail stays visible to assistive tech; only a fully hidden sidebar is aria-hidden.
203
+ sidebarComponent.setAttribute('aria-hidden', String(!open && !rail));
204
+ sidebarComponent.dataset.state = open ? 'open' : 'collapsed';
205
+ if (open || rail) {
206
+ sidebarComponent.removeAttribute('inert');
207
+ } else {
208
+ sidebarComponent.setAttribute('inert', '');
209
+ }
210
+ if (sidebarComponent.id) {
211
+ document.querySelectorAll(`[data-sidebar-toggle="${CSS.escape(sidebarComponent.id)}"]`).forEach((btn) => btn.setAttribute('aria-expanded', String(open)));
212
+ }
213
+ sidebarComponent.dispatchEvent(new CustomEvent('basecoat:sidebar', { detail: { open } }));
214
+ };
215
+
216
+ const setState = (state) => {
217
+ open = Boolean(state);
218
+ updateState();
219
+ };
220
+
221
+ sidebarComponent.open = () => setState(true);
222
+ sidebarComponent.close = () => setState(false);
223
+ sidebarComponent.toggle = () => setState(!open);
224
+
225
+ const handleClick = (event) => {
226
+ const target = event.target;
227
+ const nav = sidebarComponent.querySelector(':scope > nav');
228
+ const isMobile = window.innerWidth < breakpoint;
229
+
230
+ if (isMobile && target.closest('a, button') && !target.closest('[data-keep-mobile-sidebar-open]')) {
231
+ if (document.activeElement) document.activeElement.blur();
232
+ sidebarComponent.close();
233
+ return;
234
+ }
235
+
236
+ // Collapsed icon rail: opening a submenu expands the sidebar first.
237
+ if (!open && isIconRail() && target.closest('summary')) {
238
+ event.preventDefault();
239
+ sidebarComponent.open();
240
+ target.closest('details').open = true;
241
+ return;
242
+ }
243
+
244
+ if (target === sidebarComponent || (nav && !nav.contains(target))) {
245
+ if (document.activeElement) document.activeElement.blur();
246
+ sidebarComponent.close();
247
+ }
248
+ };
249
+
250
+ sidebarComponent.addEventListener('click', handleClick);
251
+
252
+ sidebarComponent._destroy = () => {
253
+ sidebarComponent.removeEventListener('click', handleClick);
254
+ delete sidebarComponent.open;
255
+ delete sidebarComponent.close;
256
+ delete sidebarComponent.toggle;
257
+ };
258
+
259
+ updateState();
260
+ sidebarComponent.dataset.sidebarInitialized = 'true';
261
+ sidebarComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
262
+ };
263
+
264
+ // Declarative toggles: <button data-sidebar-toggle="sidebar-id"> (empty value = first sidebar).
265
+ const sidebarFor = (id) => (id ? document.getElementById(id) : document.querySelector('.sidebar:not([data-position="static"])'));
266
+ document.addEventListener('click', (event) => {
267
+ const button = event.target.closest('[data-sidebar-toggle]');
268
+ if (!button) return;
269
+ sidebarFor(button.getAttribute('data-sidebar-toggle'))?.toggle?.();
270
+ });
271
+ // Ctrl/Cmd+B toggles the first sidebar (opt out with data-shortcut="none").
272
+ document.addEventListener('keydown', (event) => {
273
+ if (event.key.toLowerCase() !== 'b' || !(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey) return;
274
+ if (event.target.closest('input, textarea, select, [contenteditable]')) return;
275
+ const sidebar = sidebarFor('');
276
+ if (!sidebar || sidebar.dataset.shortcut === 'none' || typeof sidebar.toggle !== 'function') return;
277
+ event.preventDefault();
278
+ sidebar.toggle();
279
+ });
280
+
281
+ if (window.basecoat) {
282
+ window.basecoat.register('sidebar', '.sidebar', initSidebar);
283
+ }
284
+ })();
285
+
286
+ // Loaded after the DOM is ready: initialize now.
287
+ if (document.readyState !== 'loading') { window.basecoat.initAll(); window.basecoat.start(); }
@@ -0,0 +1,13 @@
1
+ /* @auvyx/ui skeleton | order 30 */
2
+ @layer components {
3
+ .skeleton {
4
+ animation: var(--animate-pulse);
5
+ }
6
+ }
7
+
8
+ @layer components {
9
+ .skeleton {
10
+ border-radius: var(--radius-md);
11
+ background-color: var(--color-muted);
12
+ }
13
+ }
@@ -0,0 +1,184 @@
1
+ /* @auvyx/ui slider | order 26 */
2
+ @layer components {
3
+ .field > input[type='range'], .input[type='range'] {
4
+ display: flex;
5
+ appearance: none;
6
+ align-items: center;
7
+ padding: 0;
8
+ --tw-outline-style: none;
9
+ outline-style: none;
10
+ &:disabled {
11
+ pointer-events: none;
12
+ }
13
+ &:disabled {
14
+ opacity: 50%;
15
+ }
16
+ --slider-value: 20%;
17
+ &::-webkit-slider-thumb {
18
+ display: block;
19
+ flex-shrink: 0;
20
+ appearance: none;
21
+ }
22
+ &::-webkit-slider-runnable-track {
23
+ width: 100%;
24
+ appearance: none;
25
+ background: linear-gradient(to right, var(--primary) var(--slider-value), var(--muted) var(--slider-value));
26
+ }
27
+ &:dir(rtl)::-webkit-slider-runnable-track {
28
+ background: linear-gradient(to left, var(--primary) var(--slider-value), var(--muted) var(--slider-value));
29
+ }
30
+ &::-moz-range-thumb {
31
+ display: block;
32
+ flex-shrink: 0;
33
+ appearance: none;
34
+ }
35
+ &::-moz-range-track {
36
+ width: 100%;
37
+ appearance: none;
38
+ background: linear-gradient(to right, var(--primary) var(--slider-value), var(--muted) var(--slider-value));
39
+ }
40
+ &:dir(rtl)::-moz-range-track {
41
+ background: linear-gradient(to left, var(--primary) var(--slider-value), var(--muted) var(--slider-value));
42
+ }
43
+ &::-ms-thumb {
44
+ display: block;
45
+ flex-shrink: 0;
46
+ appearance: none;
47
+ }
48
+ &::-ms-track {
49
+ width: 100%;
50
+ appearance: none;
51
+ }
52
+ &::-ms-fill-lower {
53
+ background-color: var(--color-primary);
54
+ }
55
+ &::-ms-fill-upper {
56
+ background-color: var(--color-muted);
57
+ }
58
+ }
59
+ }
60
+
61
+ @layer components {
62
+ .field > input[type='range'], .input[type='range'] {
63
+ height: calc(var(--spacing) * 5);
64
+ --slider-track-height: 0.375rem;
65
+ --slider-thumb-size: 1rem;
66
+ }
67
+ :is(.field > input[type='range'], .input[type='range'])::-webkit-slider-runnable-track {
68
+ height: calc(var(--spacing) * 1.5);
69
+ border-radius: calc(infinity * 1px);
70
+ background-color: var(--color-muted);
71
+ }
72
+ :is(.field > input[type='range'], .input[type='range'])::-moz-range-track {
73
+ height: calc(var(--spacing) * 1.5);
74
+ border-radius: calc(infinity * 1px);
75
+ background-color: var(--color-muted);
76
+ }
77
+ :is(.field > input[type='range'], .input[type='range'])::-ms-track {
78
+ height: calc(var(--spacing) * 1.5);
79
+ border-radius: calc(infinity * 1px);
80
+ background-color: var(--color-muted);
81
+ }
82
+ :is(.field > input[type='range'], .input[type='range'])::-ms-fill-lower, :is(.field > input[type='range'], .input[type='range'])::-ms-fill-upper {
83
+ border-radius: inherit;
84
+ }
85
+ :is(.field > input[type='range'], .input[type='range'])::-webkit-slider-thumb {
86
+ margin-top: calc(calc((var(--slider-thumb-size) - var(--slider-track-height)) / 2) * -1);
87
+ width: calc(var(--spacing) * 4);
88
+ height: calc(var(--spacing) * 4);
89
+ border-radius: calc(infinity * 1px);
90
+ border-style: var(--tw-border-style);
91
+ border-width: 1px;
92
+ border-color: var(--color-primary);
93
+ background-color: var(--color-white);
94
+ --tw-shadow: var(--shadow-sm, 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)));
95
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
96
+ --tw-ring-color: var(--color-ring);
97
+ @supports (color: color-mix(in lab, red, red)) {
98
+ --tw-ring-color: color-mix(in oklab, var(--color-ring) 50%, transparent);
99
+ }
100
+ transition-property: color,box-shadow;
101
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
102
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
103
+ }
104
+ :is(.field > input[type='range'], .input[type='range'])::-moz-range-thumb {
105
+ width: calc(var(--spacing) * 4);
106
+ height: calc(var(--spacing) * 4);
107
+ border-radius: calc(infinity * 1px);
108
+ border-style: var(--tw-border-style);
109
+ border-width: 1px;
110
+ border-color: var(--color-primary);
111
+ background-color: var(--color-white);
112
+ --tw-shadow: var(--shadow-sm, 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)));
113
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
114
+ --tw-ring-color: var(--color-ring);
115
+ @supports (color: color-mix(in lab, red, red)) {
116
+ --tw-ring-color: color-mix(in oklab, var(--color-ring) 50%, transparent);
117
+ }
118
+ transition-property: color,box-shadow;
119
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
120
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
121
+ }
122
+ :is(.field > input[type='range'], .input[type='range'])::-ms-thumb {
123
+ width: calc(var(--spacing) * 4);
124
+ height: calc(var(--spacing) * 4);
125
+ border-radius: calc(infinity * 1px);
126
+ border-style: var(--tw-border-style);
127
+ border-width: 1px;
128
+ border-color: var(--color-primary);
129
+ background-color: var(--color-white);
130
+ --tw-shadow: var(--shadow-sm, 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)));
131
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
132
+ --tw-ring-color: var(--color-ring);
133
+ @supports (color: color-mix(in lab, red, red)) {
134
+ --tw-ring-color: color-mix(in oklab, var(--color-ring) 50%, transparent);
135
+ }
136
+ transition-property: color,box-shadow;
137
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
138
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
139
+ }
140
+ @media (hover: hover) {
141
+ :is(.field > input[type='range'], .input[type='range']):hover::-webkit-slider-thumb {
142
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
143
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
144
+ }
145
+ :is(.field > input[type='range'], .input[type='range']):hover::-moz-range-thumb {
146
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
147
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
148
+ }
149
+ :is(.field > input[type='range'], .input[type='range']):hover::-ms-thumb {
150
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
151
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
152
+ }
153
+ }
154
+ :is(.field > input[type='range'], .input[type='range']):focus-visible::-webkit-slider-thumb {
155
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
156
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
157
+ --tw-outline-style: none;
158
+ outline-style: none;
159
+ @media (forced-colors: active) {
160
+ outline: 2px solid transparent;
161
+ outline-offset: 2px;
162
+ }
163
+ }
164
+ :is(.field > input[type='range'], .input[type='range']):focus-visible::-moz-range-thumb {
165
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
166
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
167
+ --tw-outline-style: none;
168
+ outline-style: none;
169
+ @media (forced-colors: active) {
170
+ outline: 2px solid transparent;
171
+ outline-offset: 2px;
172
+ }
173
+ }
174
+ :is(.field > input[type='range'], .input[type='range']):focus-visible::-ms-thumb {
175
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
176
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
177
+ --tw-outline-style: none;
178
+ outline-style: none;
179
+ @media (forced-colors: active) {
180
+ outline: 2px solid transparent;
181
+ outline-offset: 2px;
182
+ }
183
+ }
184
+ }
@@ -0,0 +1,206 @@
1
+ /* @auvyx/ui v0.1.0 | MIT | based on Basecoat (MIT) */
2
+ // Basecoat core, once per page.
3
+ if (!window.basecoat) (() => {
4
+ const componentRegistry = {};
5
+ let observer = null;
6
+
7
+ const registerComponent = (name, selectorOrOptions, initFunction) => {
8
+ const options = typeof selectorOrOptions === 'object'
9
+ ? selectorOrOptions
10
+ : { selector: selectorOrOptions, init: initFunction };
11
+
12
+ componentRegistry[name] = {
13
+ selector: options.selector,
14
+ init: options.init,
15
+ refresh: options.refresh,
16
+ };
17
+ };
18
+
19
+ const initComponent = (element, componentName) => {
20
+ const component = componentRegistry[componentName];
21
+ if (!component) return;
22
+
23
+ try {
24
+ component.init(element);
25
+ if (element.hasAttribute(`data-${componentName}-initialized`)) {
26
+ element.dataset.basecoatComponent = componentName;
27
+ }
28
+ } catch (error) {
29
+ console.error(`Failed to initialize ${componentName}:`, error);
30
+ if (typeof element._destroy === 'function') {
31
+ try {
32
+ element._destroy();
33
+ } catch (destroyError) {
34
+ console.error(`Failed to clean up ${componentName} after initialization error:`, destroyError);
35
+ }
36
+ }
37
+ delete element._destroy;
38
+ element.removeAttribute(`data-${componentName}-initialized`);
39
+ delete element.dataset.basecoatComponent;
40
+ }
41
+ };
42
+
43
+ const destroyComponent = (element) => {
44
+ if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
45
+ const componentName = element.dataset?.basecoatComponent;
46
+
47
+ if (typeof element._destroy === 'function') {
48
+ try {
49
+ element._destroy();
50
+ } catch (error) {
51
+ console.error('Failed to destroy Basecoat component:', error);
52
+ }
53
+ }
54
+
55
+ delete element._destroy;
56
+ if (componentName) element.removeAttribute(`data-${componentName}-initialized`);
57
+ delete element.dataset.basecoatComponent;
58
+ };
59
+
60
+ const destroyRemovedComponents = (node) => {
61
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
62
+ if (node.isConnected) return;
63
+
64
+ if (node.dataset?.basecoatComponent) destroyComponent(node);
65
+ node.querySelectorAll('[data-basecoat-component]').forEach(destroyComponent);
66
+ };
67
+
68
+ const uniqueElements = (elements) => Array.from(new Set(elements));
69
+
70
+ const getComponentElements = (componentName, selector, force = false) => {
71
+ const elements = Array.from(document.querySelectorAll(selector));
72
+ if (force) {
73
+ elements.push(...document.querySelectorAll(`[data-basecoat-component="${componentName}"]`));
74
+ }
75
+ return uniqueElements(elements);
76
+ };
77
+
78
+ const initAllComponents = (options = {}) => {
79
+ const force = options.force === true;
80
+ Object.entries(componentRegistry).forEach(([name, { selector }]) => {
81
+ getComponentElements(name, selector, force).forEach((element) => {
82
+ const wasComponent = element.dataset?.basecoatComponent === name;
83
+ if (force) destroyComponent(element);
84
+ if (wasComponent || element.matches(selector)) initComponent(element, name);
85
+ });
86
+ });
87
+ };
88
+
89
+ const initNewComponents = (node) => {
90
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
91
+
92
+ Object.entries(componentRegistry).forEach(([name, { selector }]) => {
93
+ if (node.matches(selector)) initComponent(node, name);
94
+ node.querySelectorAll(selector).forEach(element => initComponent(element, name));
95
+ });
96
+ };
97
+
98
+ const refreshComponent = (element) => {
99
+ if (!element) return;
100
+ if (typeof element.refresh === 'function') {
101
+ element.refresh();
102
+ return;
103
+ }
104
+
105
+ const componentName = element.dataset?.basecoatComponent;
106
+ const component = componentName ? componentRegistry[componentName] : null;
107
+ if (component?.refresh) {
108
+ component.refresh(element);
109
+ }
110
+ };
111
+
112
+ const startObserver = () => {
113
+ if (observer) return;
114
+
115
+ observer = new MutationObserver((mutations) => {
116
+ mutations.forEach((mutation) => {
117
+ mutation.addedNodes.forEach(initNewComponents);
118
+ mutation.removedNodes.forEach(destroyRemovedComponents);
119
+ });
120
+ });
121
+
122
+ observer.observe(document.body, { childList: true, subtree: true });
123
+ };
124
+
125
+ const stopObserver = () => {
126
+ if (!observer) return;
127
+ observer.disconnect();
128
+ observer = null;
129
+ };
130
+
131
+ const initRegisteredComponent = (componentName, options = {}) => {
132
+ const component = componentRegistry[componentName];
133
+ if (!component) {
134
+ console.warn(`Component '${componentName}' not found in registry`);
135
+ return;
136
+ }
137
+
138
+ const force = options.force === true;
139
+ getComponentElements(componentName, component.selector, force).forEach((element) => {
140
+ const wasComponent = element.dataset?.basecoatComponent === componentName;
141
+ if (force) destroyComponent(element);
142
+ if (wasComponent || element.matches(component.selector)) initComponent(element, componentName);
143
+ });
144
+ };
145
+
146
+ const initAllRegisteredComponents = (options = {}) => {
147
+ initAllComponents(options);
148
+ };
149
+
150
+ const setTheme = (mode) => {
151
+ const dark = mode === 'dark';
152
+ document.documentElement.classList.toggle('dark', dark);
153
+ try { localStorage.setItem('themeMode', dark ? 'dark' : 'light'); } catch (_) {}
154
+ document.dispatchEvent(new CustomEvent('basecoat:themechange', { detail: { mode: dark ? 'dark' : 'light' } }));
155
+ };
156
+
157
+ const getTheme = () => document.documentElement.classList.contains('dark') ? 'dark' : 'light';
158
+
159
+ window.basecoat = {
160
+ register: registerComponent,
161
+ init: initRegisteredComponent,
162
+ initAll: initAllRegisteredComponents,
163
+ refresh: refreshComponent,
164
+ start: startObserver,
165
+ stop: stopObserver,
166
+ theme: {
167
+ get: getTheme,
168
+ set: setTheme,
169
+ toggle: () => setTheme(getTheme() === 'dark' ? 'light' : 'dark'),
170
+ },
171
+ };
172
+
173
+ document.addEventListener('DOMContentLoaded', () => {
174
+ initAllComponents();
175
+ startObserver();
176
+ });
177
+ })();
178
+
179
+ (() => {
180
+ function updateRange(element) {
181
+ const min = parseFloat(element.min || '0');
182
+ const max = parseFloat(element.max || '100');
183
+ const value = parseFloat(element.value || '0');
184
+ const percent = max === min ? 0 : ((value - min) / (max - min)) * 100;
185
+ element.style.setProperty('--slider-value', `${percent}%`);
186
+ }
187
+
188
+ function initRange(element) {
189
+ if (element.dataset.rangeInitialized) return;
190
+
191
+ updateRange(element);
192
+ const handleInput = () => updateRange(element);
193
+ element.addEventListener('input', handleInput);
194
+ element._destroy = () => {
195
+ element.removeEventListener('input', handleInput);
196
+ };
197
+ element.dataset.rangeInitialized = 'true';
198
+ }
199
+
200
+ if (window.basecoat) {
201
+ window.basecoat.register('range', 'input[type="range"]:not([data-range-initialized])', initRange);
202
+ }
203
+ })();
204
+
205
+ // Loaded after the DOM is ready: initialize now.
206
+ if (document.readyState !== 'loading') { window.basecoat.initAll(); window.basecoat.start(); }