@auvyx/ui 0.0.0-stage → 0.1.1

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 +3223 -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 +285 -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 +250 -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 +758 -0
  70. package/dist/ui/command.css +366 -0
  71. package/dist/ui/command.js +383 -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 +266 -0
  83. package/dist/ui/dropdown-menu.css +193 -0
  84. package/dist/ui/dropdown-menu.js +389 -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 +269 -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 +647 -0
  113. package/dist/ui/sheet.css +219 -0
  114. package/dist/ui/sidebar.css +716 -0
  115. package/dist/ui/sidebar.js +288 -0
  116. package/dist/ui/skeleton.css +13 -0
  117. package/dist/ui/slider.css +184 -0
  118. package/dist/ui/slider.js +207 -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 +289 -0
  123. package/dist/ui/textarea.css +91 -0
  124. package/dist/ui/toast.css +110 -0
  125. package/dist/ui/toast.js +379 -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,383 @@
1
+ /* @auvyx/ui v0.1.1 | MIT License | see LICENSE */
2
+ // Shared core, once per page.
3
+ if (!window.auvyx) (() => {
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 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.auvyx is the API; window.auvyx is the same object, kept as an alias.
160
+ window.auvyx = window.basecoat = {
161
+ register: registerComponent,
162
+ init: initRegisteredComponent,
163
+ initAll: initAllRegisteredComponents,
164
+ refresh: refreshComponent,
165
+ start: startObserver,
166
+ stop: stopObserver,
167
+ theme: {
168
+ get: getTheme,
169
+ set: setTheme,
170
+ toggle: () => setTheme(getTheme() === 'dark' ? 'light' : 'dark'),
171
+ },
172
+ };
173
+
174
+ document.addEventListener('DOMContentLoaded', () => {
175
+ initAllComponents();
176
+ startObserver();
177
+ });
178
+ })();
179
+
180
+ (() => {
181
+ const states = new WeakMap();
182
+
183
+ const isDisabled = (item) =>
184
+ item.hasAttribute('disabled') ||
185
+ item.getAttribute('aria-disabled') === 'true' ||
186
+ item.getAttribute('data-disabled') === 'true';
187
+
188
+ const getElements = (container) => ({
189
+ input: container.querySelector('header input'),
190
+ menu: container.querySelector('[role="menu"]'),
191
+ });
192
+
193
+ const getItems = (menu) => {
194
+ const allItems = Array.from(menu.querySelectorAll('[role="menuitem"]'));
195
+ return {
196
+ allItems,
197
+ items: allItems.filter(item => !isDisabled(item)),
198
+ };
199
+ };
200
+
201
+ const scrollItemIntoMenu = (state, item) => {
202
+ const itemRect = item.getBoundingClientRect();
203
+ const menuRect = state.menu.getBoundingClientRect();
204
+
205
+ if (itemRect.top < menuRect.top) {
206
+ state.menu.scrollTop -= menuRect.top - itemRect.top;
207
+ } else if (itemRect.bottom > menuRect.bottom) {
208
+ state.menu.scrollTop += itemRect.bottom - menuRect.bottom;
209
+ }
210
+ };
211
+
212
+ const setActiveItem = (state, index) => {
213
+ if (state.activeIndex > -1 && state.items[state.activeIndex]) {
214
+ state.items[state.activeIndex].classList.remove('active');
215
+ }
216
+
217
+ state.activeIndex = index;
218
+
219
+ if (state.activeIndex > -1) {
220
+ const activeItem = state.items[state.activeIndex];
221
+ activeItem.classList.add('active');
222
+ if (activeItem.id) {
223
+ state.input.setAttribute('aria-activedescendant', activeItem.id);
224
+ } else {
225
+ state.input.removeAttribute('aria-activedescendant');
226
+ }
227
+ } else {
228
+ state.input.removeAttribute('aria-activedescendant');
229
+ }
230
+ };
231
+
232
+ const filterItems = (state) => {
233
+ if (state.manualFilter) {
234
+ setActiveItem(state, -1);
235
+ state.visibleItems = state.items.filter(item => item.getAttribute('aria-hidden') !== 'true');
236
+ if (state.visibleItems.length > 0) {
237
+ setActiveItem(state, state.items.indexOf(state.visibleItems[0]));
238
+ }
239
+ return;
240
+ }
241
+
242
+ const searchTerm = state.input.value.trim().toLowerCase();
243
+
244
+ setActiveItem(state, -1);
245
+ state.visibleItems = [];
246
+
247
+ state.allItems.forEach(item => {
248
+ if (item.hasAttribute('data-force')) {
249
+ item.setAttribute('aria-hidden', 'false');
250
+ if (state.items.includes(item)) state.visibleItems.push(item);
251
+ return;
252
+ }
253
+
254
+ const itemText = (item.dataset.filter || item.textContent).trim().toLowerCase();
255
+ const keywordList = (item.dataset.keywords || '')
256
+ .toLowerCase()
257
+ .split(/[\s,]+/)
258
+ .filter(Boolean);
259
+ const matchesKeyword = keywordList.some(keyword => keyword.includes(searchTerm));
260
+ const matches = itemText.includes(searchTerm) || matchesKeyword;
261
+ item.setAttribute('aria-hidden', String(!matches));
262
+ if (matches && state.items.includes(item)) state.visibleItems.push(item);
263
+ });
264
+
265
+ if (state.visibleItems.length > 0) {
266
+ setActiveItem(state, state.items.indexOf(state.visibleItems[0]));
267
+ scrollItemIntoMenu(state, state.visibleItems[0]);
268
+ }
269
+ };
270
+
271
+ const refreshCommand = (container) => {
272
+ const state = states.get(container);
273
+ if (!state) return;
274
+
275
+ const elements = getElements(container);
276
+ if (!elements.input || !elements.menu) {
277
+ const missing = [];
278
+ if (!elements.input) missing.push('input');
279
+ if (!elements.menu) missing.push('menu');
280
+ console.error(`Command component refresh failed. Missing element(s): ${missing.join(', ')}`, container);
281
+ return;
282
+ }
283
+
284
+ Object.assign(state, elements, getItems(elements.menu));
285
+ state.manualFilter = container.dataset.filter === 'manual';
286
+ filterItems(state);
287
+ };
288
+
289
+ const handleKeyNavigation = (event, state) => {
290
+ if (!['ArrowDown', 'ArrowUp', 'Enter', 'Home', 'End'].includes(event.key)) return;
291
+
292
+ if (event.key === 'Enter') {
293
+ event.preventDefault();
294
+ if (state.activeIndex > -1) state.items[state.activeIndex]?.click();
295
+ return;
296
+ }
297
+
298
+ if (state.visibleItems.length === 0) return;
299
+
300
+ event.preventDefault();
301
+
302
+ const currentVisibleIndex = state.activeIndex > -1 ? state.visibleItems.indexOf(state.items[state.activeIndex]) : -1;
303
+ let nextVisibleIndex = currentVisibleIndex;
304
+
305
+ if (event.key === 'ArrowDown' && currentVisibleIndex < state.visibleItems.length - 1) nextVisibleIndex = currentVisibleIndex + 1;
306
+ if (event.key === 'ArrowUp') nextVisibleIndex = currentVisibleIndex > 0 ? currentVisibleIndex - 1 : 0;
307
+ if (event.key === 'Home') nextVisibleIndex = 0;
308
+ if (event.key === 'End') nextVisibleIndex = state.visibleItems.length - 1;
309
+
310
+ if (nextVisibleIndex !== currentVisibleIndex) {
311
+ const newActiveItem = state.visibleItems[nextVisibleIndex];
312
+ setActiveItem(state, state.items.indexOf(newActiveItem));
313
+ scrollItemIntoMenu(state, newActiveItem);
314
+ }
315
+ };
316
+
317
+ const initCommand = (container) => {
318
+ if (container.dataset.commandInitialized) return;
319
+
320
+ const state = { activeIndex: -1, allItems: [], items: [], visibleItems: [], manualFilter: false };
321
+ states.set(container, state);
322
+
323
+ container.refresh = () => refreshCommand(container);
324
+
325
+ const elements = getElements(container);
326
+ if (!elements.input || !elements.menu) {
327
+ const missing = [];
328
+ if (!elements.input) missing.push('input');
329
+ if (!elements.menu) missing.push('menu');
330
+ console.error(`Command component initialization failed. Missing element(s): ${missing.join(', ')}`, container);
331
+ states.delete(container);
332
+ delete container.refresh;
333
+ return;
334
+ }
335
+ Object.assign(state, elements);
336
+
337
+ const handleInput = () => filterItems(state);
338
+ const handleInputKeydown = (event) => handleKeyNavigation(event, state);
339
+ const handleMenuMousemove = (event) => {
340
+ const menuItem = event.target.closest('[role="menuitem"]');
341
+ if (menuItem && state.visibleItems.includes(menuItem)) {
342
+ const index = state.items.indexOf(menuItem);
343
+ if (index !== state.activeIndex) setActiveItem(state, index);
344
+ }
345
+ };
346
+ const handleMenuClick = (event) => {
347
+ const clickedItem = event.target.closest('[role="menuitem"]');
348
+ if (clickedItem && state.visibleItems.includes(clickedItem)) {
349
+ const dialog = container.closest('dialog.command-dialog');
350
+ if (dialog && !clickedItem.hasAttribute('data-keep-command-open')) dialog.close();
351
+ }
352
+ };
353
+
354
+ state.input.addEventListener('input', handleInput);
355
+ state.input.addEventListener('keydown', handleInputKeydown);
356
+ state.menu.addEventListener('mousemove', handleMenuMousemove);
357
+ state.menu.addEventListener('click', handleMenuClick);
358
+
359
+ container._destroy = () => {
360
+ state.input.removeEventListener('input', handleInput);
361
+ state.input.removeEventListener('keydown', handleInputKeydown);
362
+ state.menu.removeEventListener('mousemove', handleMenuMousemove);
363
+ state.menu.removeEventListener('click', handleMenuClick);
364
+ states.delete(container);
365
+ delete container.refresh;
366
+ };
367
+
368
+ refreshCommand(container);
369
+ container.dataset.commandInitialized = 'true';
370
+ container.dispatchEvent(new CustomEvent('basecoat:initialized'));
371
+ };
372
+
373
+ if (window.auvyx) {
374
+ window.auvyx.register('command', {
375
+ selector: '.command:not([data-command-initialized])',
376
+ init: initCommand,
377
+ refresh: refreshCommand,
378
+ });
379
+ }
380
+ })();
381
+
382
+ // Loaded after the DOM is ready: initialize now.
383
+ if (document.readyState !== 'loading') { window.auvyx.initAll(); window.auvyx.start(); }
@@ -0,0 +1,17 @@
1
+ /* @auvyx/ui context-menu | order 54 */
2
+ @layer components {
3
+ .context-menu {
4
+ display: block;
5
+ > .dropdown-menu {
6
+ display: contents;
7
+ }
8
+ }
9
+ }
10
+
11
+ @layer components {
12
+ .context-menu > .dropdown-menu > [data-popover] {
13
+ min-width: calc(var(--spacing) * 36);
14
+ --tw-shadow: var(--shadow-md, 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)));
15
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
16
+ }
17
+ }
@@ -0,0 +1,52 @@
1
+ /* @auvyx/ui v0.1.1 | MIT License | see LICENSE */
2
+ (() => {
3
+ // ../elements/src/context-menu.js
4
+ var AuvyxContextMenu = class extends HTMLElement {
5
+ connectedCallback() {
6
+ if (this._init) return;
7
+ this._init = true;
8
+ this.menu = this.querySelector("[data-popover][popover]");
9
+ if (!this.menu) return;
10
+ let pressed = false;
11
+ this.addEventListener("pointerdown", (e) => {
12
+ if (e.button === 2) pressed = true;
13
+ });
14
+ this.addEventListener("contextmenu", (event) => {
15
+ if (this.menu.contains(event.target)) return;
16
+ event.preventDefault();
17
+ const { clientX: x, clientY: y } = event;
18
+ if (!pressed) return this.openAt(x, y);
19
+ window.addEventListener("pointerup", () => {
20
+ pressed = false;
21
+ setTimeout(() => this.openAt(x, y));
22
+ }, { once: true, capture: true });
23
+ });
24
+ this.addEventListener("keydown", (event) => {
25
+ if (this.menu.contains(event.target)) return;
26
+ if (event.key === "ContextMenu" || event.shiftKey && event.key === "F10") {
27
+ event.preventDefault();
28
+ const r = event.target.getBoundingClientRect();
29
+ this.openAt(r.left, r.bottom);
30
+ }
31
+ });
32
+ }
33
+ openAt(x, y) {
34
+ const menu = this.menu;
35
+ if (menu.matches(":popover-open")) menu.hidePopover();
36
+ Object.assign(menu.style, {
37
+ position: "fixed",
38
+ inset: "auto",
39
+ margin: "0",
40
+ positionArea: "none",
41
+ positionTryFallbacks: "none",
42
+ left: `${x}px`,
43
+ top: `${y}px`
44
+ });
45
+ menu.showPopover();
46
+ const r = menu.getBoundingClientRect();
47
+ if (r.right > innerWidth - 4) menu.style.left = `${Math.max(4, x - r.width)}px`;
48
+ if (r.bottom > innerHeight - 4) menu.style.top = `${Math.max(4, y - r.height)}px`;
49
+ }
50
+ };
51
+ if (!customElements.get("auvyx-context-menu")) customElements.define("auvyx-context-menu", AuvyxContextMenu);
52
+ })();
@@ -0,0 +1,56 @@
1
+ /* @auvyx/ui v0.1.1 | MIT License | see LICENSE */
2
+ // Copy to clipboard (fork addition).
3
+ //
4
+ // Any element with [data-copy] copies text on click:
5
+ // data-copy="" → the <pre> of the closest .code-block
6
+ // data-copy="#selector" → the textContent of that element
7
+ // data-copy-text="…" → this literal text
8
+ // While copied, the button gets [data-copied] for 2s and its aria-label
9
+ // becomes the value of data-copied-label (default "Copied").
10
+ // One delegated listener, so it also works for content added later.
11
+ (() => {
12
+ if (window.__basecoatCopy) return;
13
+ window.__basecoatCopy = true;
14
+
15
+ const sourceText = (button) => {
16
+ if (button.hasAttribute('data-copy-text')) return button.getAttribute('data-copy-text');
17
+ const target = button.getAttribute('data-copy');
18
+ const el = target ? document.querySelector(target) : button.closest('.code-block')?.querySelector(':scope > pre');
19
+ return el ? el.textContent : null;
20
+ };
21
+
22
+ const selectFallback = (button) => {
23
+ const target = button.getAttribute('data-copy');
24
+ const el = target ? document.querySelector(target) : button.closest('.code-block')?.querySelector(':scope > pre');
25
+ if (!el) return;
26
+ const range = document.createRange();
27
+ range.selectNodeContents(el);
28
+ const selection = window.getSelection();
29
+ selection.removeAllRanges();
30
+ selection.addRange(range);
31
+ };
32
+
33
+ document.addEventListener('click', async (event) => {
34
+ const button = event.target.closest('[data-copy], [data-copy-text]');
35
+ if (!button || button.disabled) return;
36
+ const text = sourceText(button);
37
+ if (text == null) return;
38
+
39
+ try {
40
+ await navigator.clipboard.writeText(text.replace(/\n$/, ''));
41
+ } catch {
42
+ selectFallback(button);
43
+ return;
44
+ }
45
+
46
+ const label = button.getAttribute('aria-label');
47
+ if (!button.hasAttribute('data-copied')) button.dataset.copyLabel = label ?? '';
48
+ button.setAttribute('data-copied', '');
49
+ button.setAttribute('aria-label', button.getAttribute('data-copied-label') || 'Copied');
50
+ clearTimeout(button._copyTimer);
51
+ button._copyTimer = setTimeout(() => {
52
+ button.removeAttribute('data-copied');
53
+ if (button.dataset.copyLabel) button.setAttribute('aria-label', button.dataset.copyLabel);
54
+ }, 2000);
55
+ });
56
+ })();
@@ -0,0 +1,114 @@
1
+ /* @auvyx/ui data-table | order 56 */
2
+ @layer components {
3
+ .data-table {
4
+ display: flex;
5
+ width: 100%;
6
+ min-width: 0;
7
+ flex-direction: column;
8
+ > header, > footer {
9
+ display: flex;
10
+ flex-wrap: wrap;
11
+ align-items: center;
12
+ }
13
+ > header > input {
14
+ width: auto;
15
+ min-width: 0;
16
+ flex: 1 1 12rem;
17
+ }
18
+ > div:has(> table) {
19
+ position: relative;
20
+ width: 100%;
21
+ overflow-x: auto;
22
+ }
23
+ th[aria-sort] > button {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ white-space: nowrap;
27
+ --tw-outline-style: none;
28
+ outline-style: none;
29
+ &::after {
30
+ margin-inline-start: var(--spacing);
31
+ display: inline-block;
32
+ width: calc(var(--spacing) * 3.5);
33
+ height: calc(var(--spacing) * 3.5);
34
+ flex-shrink: 0;
35
+ opacity: 50%;
36
+ --tw-content: '';
37
+ content: var(--tw-content);
38
+ mask: var(--data-table-sort-icon) center / contain no-repeat;
39
+ background-color: currentColor;
40
+ }
41
+ }
42
+ th[aria-sort='ascending'] > button::after, th[aria-sort='descending'] > button::after {
43
+ opacity: 100%;
44
+ }
45
+ th[aria-sort='descending'] > button::after {
46
+ rotate: 180deg;
47
+ }
48
+ [data-page-status], [data-selected-count] {
49
+ --tw-numeric-spacing: tabular-nums;
50
+ font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
51
+ }
52
+ [data-selected-count] {
53
+ flex: 1;
54
+ }
55
+ }
56
+ }
57
+
58
+ @layer components {
59
+ .data-table {
60
+ gap: calc(var(--spacing) * 4);
61
+ }
62
+ .data-table > header {
63
+ gap: calc(var(--spacing) * 2);
64
+ }
65
+ .data-table > header > input[type='search'] {
66
+ max-width: var(--container-sm);
67
+ }
68
+ .data-table > div:has(> table) {
69
+ border-radius: var(--auvyx-r-field);
70
+ border-style: var(--tw-border-style);
71
+ border-width: 1px;
72
+ }
73
+ .data-table th[aria-sort] > button {
74
+ margin-inline-start: calc(var(--spacing) * -2);
75
+ height: calc(var(--spacing) * 8);
76
+ border-radius: var(--auvyx-r-control);
77
+ padding-inline: calc(var(--spacing) * 2);
78
+ &:hover {
79
+ @media (hover: hover) {
80
+ background-color: var(--color-muted);
81
+ }
82
+ }
83
+ &:hover {
84
+ @media (hover: hover) {
85
+ color: var(--color-foreground);
86
+ }
87
+ }
88
+ &:focus-visible {
89
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
90
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
91
+ }
92
+ &:focus-visible {
93
+ --tw-ring-color: var(--color-ring);
94
+ @supports (color: color-mix(in lab, red, red)) {
95
+ --tw-ring-color: color-mix(in oklab, var(--color-ring) 50%, transparent);
96
+ }
97
+ }
98
+ }
99
+ .data-table {
100
+ --data-table-sort-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>');
101
+ }
102
+ .data-table th:is([aria-sort='ascending'], [aria-sort='descending']) {
103
+ --data-table-sort-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12 7-7 7 7"/><path d="M12 19V5"/></svg>');
104
+ }
105
+ .data-table tbody tr[aria-selected='true'] {
106
+ background-color: var(--color-muted);
107
+ }
108
+ .data-table > footer {
109
+ gap: calc(var(--spacing) * 2);
110
+ font-size: var(--text-sm);
111
+ line-height: var(--tw-leading, var(--text-sm--line-height));
112
+ color: var(--color-muted-foreground);
113
+ }
114
+ }