@colletdev/core 0.1.3

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 (119) hide show
  1. package/README.md +77 -0
  2. package/custom-elements.json +6037 -0
  3. package/generated/.gitattributes +2 -0
  4. package/generated/index.d.ts +120 -0
  5. package/generated/index.js +521 -0
  6. package/generated/styles.js +2845 -0
  7. package/package.json +56 -0
  8. package/src/elements/accordion.d.ts +20 -0
  9. package/src/elements/accordion.js +92 -0
  10. package/src/elements/activity_group.d.ts +19 -0
  11. package/src/elements/activity_group.js +27 -0
  12. package/src/elements/alert.d.ts +24 -0
  13. package/src/elements/alert.js +40 -0
  14. package/src/elements/autocomplete.d.ts +30 -0
  15. package/src/elements/autocomplete.js +671 -0
  16. package/src/elements/avatar.d.ts +18 -0
  17. package/src/elements/avatar.js +28 -0
  18. package/src/elements/backdrop.d.ts +14 -0
  19. package/src/elements/backdrop.js +28 -0
  20. package/src/elements/badge.d.ts +21 -0
  21. package/src/elements/badge.js +42 -0
  22. package/src/elements/breadcrumb.d.ts +17 -0
  23. package/src/elements/breadcrumb.js +41 -0
  24. package/src/elements/button.d.ts +24 -0
  25. package/src/elements/button.js +36 -0
  26. package/src/elements/card.d.ts +21 -0
  27. package/src/elements/card.js +67 -0
  28. package/src/elements/carousel.d.ts +23 -0
  29. package/src/elements/carousel.js +895 -0
  30. package/src/elements/chat_input.d.ts +22 -0
  31. package/src/elements/chat_input.js +78 -0
  32. package/src/elements/checkbox.d.ts +21 -0
  33. package/src/elements/checkbox.js +114 -0
  34. package/src/elements/code_block.d.ts +21 -0
  35. package/src/elements/code_block.js +27 -0
  36. package/src/elements/collapsible.d.ts +20 -0
  37. package/src/elements/collapsible.js +93 -0
  38. package/src/elements/date_picker.d.ts +30 -0
  39. package/src/elements/date_picker.js +528 -0
  40. package/src/elements/dialog.d.ts +20 -0
  41. package/src/elements/dialog.js +314 -0
  42. package/src/elements/drawer.d.ts +20 -0
  43. package/src/elements/drawer.js +318 -0
  44. package/src/elements/fab.d.ts +22 -0
  45. package/src/elements/fab.js +36 -0
  46. package/src/elements/file_upload.d.ts +26 -0
  47. package/src/elements/file_upload.js +59 -0
  48. package/src/elements/listbox.d.ts +19 -0
  49. package/src/elements/listbox.js +250 -0
  50. package/src/elements/menu.d.ts +20 -0
  51. package/src/elements/menu.js +224 -0
  52. package/src/elements/message_bubble.d.ts +23 -0
  53. package/src/elements/message_bubble.js +29 -0
  54. package/src/elements/message_group.d.ts +18 -0
  55. package/src/elements/message_group.js +28 -0
  56. package/src/elements/message_part.d.ts +35 -0
  57. package/src/elements/message_part.js +153 -0
  58. package/src/elements/pagination.d.ts +22 -0
  59. package/src/elements/pagination.js +36 -0
  60. package/src/elements/popover.d.ts +26 -0
  61. package/src/elements/popover.js +191 -0
  62. package/src/elements/profile_menu.d.ts +20 -0
  63. package/src/elements/profile_menu.js +213 -0
  64. package/src/elements/progress.d.ts +18 -0
  65. package/src/elements/progress.js +31 -0
  66. package/src/elements/radio_group.d.ts +22 -0
  67. package/src/elements/radio_group.js +70 -0
  68. package/src/elements/scrollbar.d.ts +19 -0
  69. package/src/elements/scrollbar.js +299 -0
  70. package/src/elements/search_bar.d.ts +27 -0
  71. package/src/elements/search_bar.js +98 -0
  72. package/src/elements/select.d.ts +26 -0
  73. package/src/elements/select.js +485 -0
  74. package/src/elements/sidebar.d.ts +21 -0
  75. package/src/elements/sidebar.js +322 -0
  76. package/src/elements/skeleton.d.ts +17 -0
  77. package/src/elements/skeleton.js +31 -0
  78. package/src/elements/slider.d.ts +28 -0
  79. package/src/elements/slider.js +93 -0
  80. package/src/elements/speed_dial.d.ts +23 -0
  81. package/src/elements/speed_dial.js +370 -0
  82. package/src/elements/spinner.d.ts +15 -0
  83. package/src/elements/spinner.js +28 -0
  84. package/src/elements/split_button.d.ts +23 -0
  85. package/src/elements/split_button.js +281 -0
  86. package/src/elements/stepper.d.ts +20 -0
  87. package/src/elements/stepper.js +31 -0
  88. package/src/elements/switch.d.ts +22 -0
  89. package/src/elements/switch.js +129 -0
  90. package/src/elements/table.d.ts +29 -0
  91. package/src/elements/table.js +371 -0
  92. package/src/elements/tabs.d.ts +19 -0
  93. package/src/elements/tabs.js +139 -0
  94. package/src/elements/text.d.ts +26 -0
  95. package/src/elements/text.js +32 -0
  96. package/src/elements/text_input.d.ts +36 -0
  97. package/src/elements/text_input.js +121 -0
  98. package/src/elements/thinking.d.ts +17 -0
  99. package/src/elements/thinking.js +28 -0
  100. package/src/elements/toast.d.ts +23 -0
  101. package/src/elements/toast.js +209 -0
  102. package/src/elements/toggle_group.d.ts +22 -0
  103. package/src/elements/toggle_group.js +176 -0
  104. package/src/elements/tooltip.d.ts +18 -0
  105. package/src/elements/tooltip.js +64 -0
  106. package/src/markdown.d.ts +24 -0
  107. package/src/markdown.js +66 -0
  108. package/src/runtime.d.ts +35 -0
  109. package/src/runtime.js +790 -0
  110. package/src/server.d.ts +69 -0
  111. package/src/server.js +176 -0
  112. package/src/streaming-markdown.js +43 -0
  113. package/src/vite-plugin.d.ts +46 -0
  114. package/src/vite-plugin.js +221 -0
  115. package/wasm/package.json +16 -0
  116. package/wasm/wasm_api.d.ts +72 -0
  117. package/wasm/wasm_api.js +593 -0
  118. package/wasm/wasm_api_bg.wasm +0 -0
  119. package/wasm/wasm_api_bg.wasm.d.ts +10 -0
@@ -0,0 +1,322 @@
1
+ // Custom behavior for <cx-sidebar> — nav item click, group toggle, mobile drawer.
2
+ // Mirrors static/_behaviors/sidebar.js but operates inside shadow DOM.
3
+ // Includes full drawer open/close for the mobile hamburger menu because the
4
+ // Shadow DOM boundary prevents the separate dialog.js behavior from finding
5
+ // the drawer via document.getElementById().
6
+ // Source: crates/wasm-api/src/sidebar.rs
7
+
8
+ // ─── Animation Constants (matches drawer.js design tokens) ───
9
+
10
+ const OFF_SCREEN = {
11
+ right: 'translateX(100%)',
12
+ left: 'translateX(-100%)',
13
+ top: 'translateY(-100%)',
14
+ bottom: 'translateY(100%)',
15
+ };
16
+
17
+ const ENTER_DURATION = 400;
18
+ const ENTER_EASING = 'cubic-bezier(0.2, 0, 0, 1)'; // --ease-snappy
19
+ const EXIT_DURATION = 250;
20
+ const EXIT_EASING = 'cubic-bezier(0.4, 0, 1, 1)';
21
+ const BACKDROP_ENTER_DURATION = 300;
22
+ const BACKDROP_EASING = 'ease-out';
23
+
24
+ // ─── Static Stylesheet (dialog backdrop override) ───
25
+
26
+ let _sheet;
27
+ function getSheet() {
28
+ if (!_sheet) {
29
+ _sheet = new CSSStyleSheet();
30
+ _sheet.replaceSync([
31
+ 'dialog[data-dialog]::backdrop {',
32
+ ' background: transparent !important;',
33
+ ' backdrop-filter: none !important;',
34
+ ' -webkit-backdrop-filter: none !important;',
35
+ '}',
36
+ '[data-dialog-panel] { position: relative; z-index: 1; }',
37
+ ].join('\n'));
38
+ }
39
+ return _sheet;
40
+ }
41
+
42
+ export function defineCxSidebar(wasmFn, baseClass) {
43
+ class CxSidebar extends baseClass {
44
+ static observedAttributes = ['id', 'label', 'groups', 'separators-after', 'side', 'state', 'shape', 'size'];
45
+ static _booleanAttrs = new Set([]);
46
+
47
+ connectedCallback() {
48
+ if (!this._isInitialized) {
49
+ this._markInitialized();
50
+ const shadow = this._shadow;
51
+
52
+ // Adopt stylesheet for dialog ::backdrop override
53
+ const sheet = getSheet();
54
+ if (!shadow.adoptedStyleSheets.includes(sheet)) {
55
+ shadow.adoptedStyleSheets = [...shadow.adoptedStyleSheets, sheet];
56
+ }
57
+
58
+ // ─── Click Delegation ───
59
+ shadow.addEventListener('click', (e) => {
60
+ // Mobile hamburger trigger → open drawer
61
+ const dialogTrigger = e.target.closest('[data-dialog-trigger]');
62
+ if (dialogTrigger) {
63
+ const targetId = dialogTrigger.getAttribute('data-dialog-target');
64
+ if (targetId) {
65
+ const dialog = shadow.getElementById(targetId);
66
+ if (dialog) this.#openDrawer(dialog);
67
+ }
68
+ return;
69
+ }
70
+
71
+ // Drawer close button
72
+ if (e.target.closest('[data-dialog-close]')) {
73
+ this.#closeDrawer();
74
+ return;
75
+ }
76
+
77
+ // Drawer backdrop click (click on dialog element itself, not children)
78
+ const dialog = shadow.querySelector('dialog[data-dialog]');
79
+ if (dialog && dialog.open && e.target === dialog) {
80
+ this.#closeDrawer();
81
+ return;
82
+ }
83
+
84
+ // Nav item click
85
+ const item = e.target.closest('[data-sidebar-item]');
86
+ if (item && !item.hasAttribute('aria-disabled')) {
87
+ const label = item.getAttribute('data-sidebar-label') || '';
88
+ const href = item.getAttribute('href') || '';
89
+ // Mark active across both desktop and mobile nav
90
+ shadow.querySelectorAll('[data-sidebar-item]').forEach(el => {
91
+ el.removeAttribute('data-active');
92
+ el.removeAttribute('aria-current');
93
+ });
94
+ item.setAttribute('data-active', '');
95
+ item.setAttribute('aria-current', 'page');
96
+ this._emit('cx-navigate', { label, href });
97
+ // Close mobile drawer if open
98
+ this.#closeDrawer();
99
+ return;
100
+ }
101
+
102
+ // Collapsible group trigger
103
+ const groupTrigger = e.target.closest('[data-sidebar-group-trigger]');
104
+ if (groupTrigger) {
105
+ const expanded = groupTrigger.getAttribute('aria-expanded') === 'true';
106
+ const newExpanded = !expanded;
107
+ groupTrigger.setAttribute('aria-expanded', String(newExpanded));
108
+ const panelId = groupTrigger.getAttribute('aria-controls');
109
+ if (panelId) {
110
+ const panel = shadow.getElementById(panelId);
111
+ if (panel) {
112
+ if (newExpanded) {
113
+ panel.removeAttribute('data-collapsed');
114
+ } else {
115
+ panel.setAttribute('data-collapsed', '');
116
+ }
117
+ }
118
+ }
119
+ // Sync collapsed group IDs to _props for re-render survival
120
+ const collapsedIds = [];
121
+ shadow.querySelectorAll('[data-sidebar-group-trigger][aria-expanded="false"]').forEach(t => {
122
+ const label = t.textContent.trim();
123
+ if (label) collapsedIds.push(label);
124
+ });
125
+ this._props._collapsed_groups = collapsedIds.join(',');
126
+ return;
127
+ }
128
+
129
+ // Toggle sidebar state (expand/narrow)
130
+ const toggle = e.target.closest('[data-sidebar-toggle]');
131
+ if (toggle) {
132
+ const sidebar = shadow.querySelector('[data-sidebar]');
133
+ if (sidebar) {
134
+ const state = sidebar.getAttribute('data-sidebar-state') || 'expanded';
135
+ const next = state === 'expanded' ? 'narrow' : 'expanded';
136
+ sidebar.setAttribute('data-sidebar-state', next);
137
+ sidebar.style.setProperty('--sidebar-width', next === 'narrow' ? '4rem' : '16rem');
138
+ // Sync host prop — single source of truth for WASM re-renders.
139
+ this._props.state = next;
140
+ this._emit('cx-toggle', { state: next });
141
+ }
142
+ return;
143
+ }
144
+ });
145
+
146
+ // ─── Keyboard ───
147
+ shadow.addEventListener('keydown', (e) => {
148
+ if (e.key === 'Escape') {
149
+ // If drawer is open, close it first
150
+ const dialog = shadow.querySelector('dialog[data-dialog]');
151
+ if (dialog && dialog.open) {
152
+ e.preventDefault();
153
+ this.#closeDrawer();
154
+ return;
155
+ }
156
+ // Otherwise collapse open group
157
+ const group = e.target.closest('[data-sidebar-group]');
158
+ if (group) {
159
+ const btn = group.querySelector('[data-sidebar-group-trigger]');
160
+ if (btn && btn.getAttribute('aria-expanded') === 'true') {
161
+ btn.setAttribute('aria-expanded', 'false');
162
+ const panelId = btn.getAttribute('aria-controls');
163
+ if (panelId) {
164
+ const panel = shadow.getElementById(panelId);
165
+ if (panel) panel.setAttribute('data-collapsed', '');
166
+ }
167
+ // Sync collapsed group IDs to _props for re-render survival
168
+ const collapsedIds = [];
169
+ shadow.querySelectorAll('[data-sidebar-group-trigger][aria-expanded="false"]').forEach(t => {
170
+ const label = t.textContent.trim();
171
+ if (label) collapsedIds.push(label);
172
+ });
173
+ this._props._collapsed_groups = collapsedIds.join(',');
174
+ btn.focus();
175
+ }
176
+ }
177
+ }
178
+ });
179
+
180
+ // Prevent native dialog cancel (would close without animation)
181
+ shadow.addEventListener('cancel', (e) => {
182
+ e.preventDefault();
183
+ this.#closeDrawer();
184
+ });
185
+ } // end _isInitialized guard
186
+ super.connectedCallback();
187
+ }
188
+
189
+ disconnectedCallback() {
190
+ this._unlockScroll();
191
+ super.disconnectedCallback();
192
+ }
193
+
194
+ // ─── Overlay Management ───
195
+
196
+ #ensureOverlay(dialog) {
197
+ if (dialog.querySelector('[data-cx-overlay]')) return;
198
+ const el = document.createElement('div');
199
+ el.setAttribute('data-cx-overlay', '');
200
+ Object.assign(el.style, {
201
+ position: 'fixed',
202
+ inset: '0',
203
+ backgroundColor: 'oklch(0 0 0 / 0.3)',
204
+ backdropFilter: 'blur(4px)',
205
+ WebkitBackdropFilter: 'blur(4px)',
206
+ pointerEvents: 'none',
207
+ zIndex: '0',
208
+ opacity: '0',
209
+ });
210
+ dialog.prepend(el);
211
+ }
212
+
213
+ // ─── Open Drawer ───
214
+
215
+ #openDrawer(dialog) {
216
+ if (!dialog || dialog.open) return;
217
+
218
+ this.#ensureOverlay(dialog);
219
+ dialog.showModal();
220
+ this._lockScroll();
221
+
222
+ // rAF: dialog must be composited in top layer before animation
223
+ requestAnimationFrame(() => {
224
+ const panel = dialog.querySelector('[data-dialog-panel]');
225
+ const overlay = dialog.querySelector('[data-cx-overlay]');
226
+ const side = dialog.getAttribute('data-drawer') || 'left';
227
+ const offscreen = OFF_SCREEN[side] || OFF_SCREEN.left;
228
+ const reducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
229
+
230
+ if (panel) {
231
+ panel.animate(
232
+ [{ transform: offscreen }, { transform: 'none' }],
233
+ { duration: reducedMotion ? 0 : ENTER_DURATION, easing: ENTER_EASING, fill: 'forwards' }
234
+ );
235
+ }
236
+
237
+ if (overlay) {
238
+ overlay.animate(
239
+ [{ opacity: 0 }, { opacity: 1 }],
240
+ { duration: reducedMotion ? 0 : BACKDROP_ENTER_DURATION, easing: BACKDROP_EASING, fill: 'forwards' }
241
+ );
242
+ }
243
+
244
+ dialog.setAttribute('data-open', '');
245
+ });
246
+ }
247
+
248
+ // ─── Close Drawer ───
249
+
250
+ #closeDrawer() {
251
+ const dialog = this._shadow.querySelector('dialog[data-dialog]');
252
+ if (!dialog || !dialog.open) return;
253
+ if (dialog.hasAttribute('data-closing')) return;
254
+
255
+ dialog.setAttribute('data-closing', '');
256
+ dialog.removeAttribute('data-open');
257
+
258
+ const panel = dialog.querySelector('[data-dialog-panel]');
259
+ const overlay = dialog.querySelector('[data-cx-overlay]');
260
+ const side = dialog.getAttribute('data-drawer') || 'left';
261
+ const offscreen = OFF_SCREEN[side] || OFF_SCREEN.left;
262
+ const reducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
263
+ const dur = reducedMotion ? 0 : EXIT_DURATION;
264
+
265
+ const cleanup = () => {
266
+ dialog.removeAttribute('data-closing');
267
+ dialog.close();
268
+ this._unlockScroll();
269
+ this._emit('cx-close', {});
270
+ // Return focus to hamburger trigger
271
+ const trigger = this._shadow.querySelector('[data-dialog-trigger]');
272
+ if (trigger) trigger.focus();
273
+ };
274
+
275
+ if (overlay) {
276
+ overlay.animate(
277
+ [{ opacity: 1 }, { opacity: 0 }],
278
+ { duration: dur, easing: EXIT_EASING, fill: 'forwards' }
279
+ );
280
+ }
281
+
282
+ if (panel) {
283
+ const anim = panel.animate(
284
+ [{ transform: 'none' }, { transform: offscreen }],
285
+ { duration: dur, easing: EXIT_EASING, fill: 'forwards' }
286
+ );
287
+ anim.finished.then(cleanup).catch(cleanup);
288
+ } else {
289
+ cleanup();
290
+ }
291
+ }
292
+
293
+ // ── Public imperative API ──
294
+ open() { this.#openDrawer(this._shadow.querySelector('dialog[data-dialog]')); }
295
+ close() { this.#closeDrawer(); }
296
+
297
+ // ─── Render ───
298
+
299
+ _doRender() {
300
+ try {
301
+ this._props.slotted = true;
302
+ const result = wasmFn(this._props);
303
+ this._injectHtml(result);
304
+
305
+ // Re-adopt stylesheet (may be stripped by base _scheduleRender)
306
+ const sheet = getSheet();
307
+ if (!this._shadow.adoptedStyleSheets.includes(sheet)) {
308
+ this._shadow.adoptedStyleSheets = [...this._shadow.adoptedStyleSheets, sheet];
309
+ }
310
+
311
+ // Inject overlay into drawer dialog (destroyed by innerHTML, re-created)
312
+ const dialog = this._shadow.querySelector('dialog[data-dialog]');
313
+ if (dialog) this.#ensureOverlay(dialog);
314
+ } catch (e) {
315
+ console.error('[cx-sidebar]', e);
316
+ }
317
+ }
318
+ }
319
+
320
+ customElements.define('cx-sidebar', CxSidebar);
321
+ return CxSidebar;
322
+ }
@@ -0,0 +1,17 @@
1
+ // Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
2
+ // Source: crates/wasm-api/src/skeleton.rs
3
+
4
+ export interface CxSkeletonAttributes {
5
+ id?: string;
6
+ variant?: 'text' | 'circle' | 'rectangular';
7
+ animation?: 'pulse' | 'wave';
8
+ label?: string;
9
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
10
+ lines?: number;
11
+ }
12
+
13
+ declare global {
14
+ interface HTMLElementTagNameMap {
15
+ 'cx-skeleton': HTMLElement & CxSkeletonAttributes;
16
+ }
17
+ }
@@ -0,0 +1,31 @@
1
+ // Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
2
+ // Source: crates/wasm-api/src/skeleton.rs
3
+
4
+ export function defineCxSkeleton(wasmFn, baseClass) {
5
+ class CxSkeleton extends baseClass {
6
+ static observedAttributes = ['id', 'variant', 'animation', 'label', 'size', 'lines'];
7
+ static _booleanAttrs = new Set([]);
8
+ static _numericAttrs = new Set(['lines']);
9
+ static _focusable = false;
10
+ static _hostDisplay = 'block';
11
+
12
+ set lines(v) { this._setProp('lines', v); }
13
+ get lines() { return this._props.lines; }
14
+
15
+ connectedCallback() {
16
+ super.connectedCallback();
17
+ }
18
+
19
+ _doRender() {
20
+ try {
21
+ const result = wasmFn(this._props);
22
+ this._injectHtml(result);
23
+ } catch (e) {
24
+ console.error('[cx-skeleton]', e);
25
+ }
26
+ }
27
+ }
28
+
29
+ customElements.define('cx-skeleton', CxSkeleton);
30
+ return CxSkeleton;
31
+ }
@@ -0,0 +1,28 @@
1
+ // Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
2
+ // Source: crates/wasm-api/src/slider.rs
3
+
4
+ export interface CxSliderAttributes {
5
+ id?: string;
6
+ label?: string;
7
+ ariaLabel?: string;
8
+ value?: number;
9
+ min?: number;
10
+ max?: number;
11
+ step?: number;
12
+ orientation?: 'horizontal' | 'vertical';
13
+ shape?: 'sharp' | 'rounded' | 'pill';
14
+ intent?: 'neutral' | 'primary' | 'info' | 'success' | 'warning' | 'danger';
15
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
16
+ showValue?: boolean;
17
+ valueText?: string;
18
+ helperText?: string;
19
+ errorText?: string;
20
+ disabled?: boolean;
21
+ unlabeled?: boolean;
22
+ }
23
+
24
+ declare global {
25
+ interface HTMLElementTagNameMap {
26
+ 'cx-slider': HTMLElement & CxSliderAttributes;
27
+ }
28
+ }
@@ -0,0 +1,93 @@
1
+ // Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
2
+ // Source: crates/wasm-api/src/slider.rs
3
+
4
+ export function defineCxSlider(wasmFn, baseClass) {
5
+ class CxSlider extends baseClass {
6
+ static observedAttributes = ['id', 'label', 'aria-label', 'value', 'min', 'max', 'step', 'orientation', 'shape', 'intent', 'size', 'show-value', 'value-text', 'helper-text', 'error-text', 'disabled', 'unlabeled'];
7
+ static _booleanAttrs = new Set(['show-value', 'disabled', 'unlabeled']);
8
+ static _numericAttrs = new Set(['value', 'min', 'max', 'step']);
9
+
10
+
11
+ // Sync the fill bar (--slider-percent CSS var) + value display text
12
+ #syncVisual(input) {
13
+ if (!input) return;
14
+ const min = parseFloat(input.min) || 0;
15
+ const max = parseFloat(input.max) || 100;
16
+ const val = parseFloat(input.value) || 0;
17
+ const range = max - min;
18
+ const pct = range > 0 ? ((val - min) / range) * 100 : 0;
19
+ input.style.setProperty('--slider-percent', String(Math.max(0, Math.min(100, pct))));
20
+ // Update value display text
21
+ const display = this._shadow.querySelector('[data-slider-value]');
22
+ if (display) display.textContent = val % 1 === 0 ? String(val) : val.toFixed(2);
23
+ }
24
+
25
+ connectedCallback() {
26
+ if (!this._isInitialized) {
27
+ this._markInitialized();
28
+ // Delegated event listeners — attach once on shadow root
29
+ this._shadow.addEventListener('input', (e) => {
30
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
31
+ this._setFormValue(e.target.value);
32
+ this._emit('cx-input', { value: e.target.value });
33
+ // Sync fill bar on every input change (drag, keyboard, click)
34
+ this.#syncVisual(e.target);
35
+ }
36
+ });
37
+ this._shadow.addEventListener('change', (e) => {
38
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') {
39
+ this._emit('cx-change', { value: e.target.value });
40
+ }
41
+ });
42
+
43
+ // Forward focus events from inner interactive elements
44
+ this._shadow.addEventListener('focusin', (e) => {
45
+ this._emit('cx-focus', { relatedTarget: e.relatedTarget });
46
+ });
47
+ this._shadow.addEventListener('focusout', (e) => {
48
+ this._emit('cx-blur', { relatedTarget: e.relatedTarget });
49
+ });
50
+
51
+ // Forward keyboard events from inner interactive elements
52
+ this._shadow.addEventListener('keydown', (e) => {
53
+ this._emit('cx-keydown', { key: e.key, code: e.code, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, altKey: e.altKey, metaKey: e.metaKey });
54
+ });
55
+ this._shadow.addEventListener('keyup', (e) => {
56
+ this._emit('cx-keyup', { key: e.key, code: e.code, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, altKey: e.altKey, metaKey: e.metaKey });
57
+ });
58
+ } // end _isInitialized guard
59
+ super.connectedCallback();
60
+ }
61
+
62
+ // ── Public imperative API ──
63
+ focus() { const el = this._shadow.querySelector('input[type="range"]'); if (el) el.focus(); else super.focus(); }
64
+
65
+ _doRender() {
66
+ try {
67
+ const result = wasmFn(this._props);
68
+ this._injectHtml(result);
69
+ // Sync form value after render. Event delegation on shadow root
70
+ // avoids duplicate listeners (innerHTML replaces old DOM nodes,
71
+ // but shadow root persists — delegation handles new inputs).
72
+ const input = this._shadow.querySelector('input, textarea, select');
73
+ if (input) {
74
+ // Sync controlled value prop → internal input property.
75
+ // _injectHtml's focus-preservation restores the OLD typed value
76
+ // to maintain cursor position during re-renders. But when the
77
+ // value prop explicitly changes (e.g., clearing after submit),
78
+ // the controlled value must win.
79
+ if ('value' in this._props) input.value = this._props.value;
80
+ this._setFormValue(input.value || '');
81
+ }
82
+ // Sync fill bar after render
83
+ const rangeInput = this._shadow.querySelector('input[type="range"], input[data-slider]');
84
+ this.#syncVisual(rangeInput);
85
+ } catch (e) {
86
+ console.error('[cx-slider]', e);
87
+ }
88
+ }
89
+ }
90
+
91
+ customElements.define('cx-slider', CxSlider);
92
+ return CxSlider;
93
+ }
@@ -0,0 +1,23 @@
1
+ // Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
2
+ // Source: crates/wasm-api/src/speed_dial.rs
3
+
4
+ export interface CxSpeedDialAttributes {
5
+ id: string;
6
+ icon: string;
7
+ ariaLabel?: string;
8
+ closeIcon?: string;
9
+ actions?: string;
10
+ variant?: 'filled' | 'outline' | 'ghost';
11
+ shape?: 'rounded' | 'pill';
12
+ size?: 'sm' | 'md' | 'lg';
13
+ direction?: 'up' | 'down' | 'left' | 'right';
14
+ backdrop?: boolean;
15
+ tooltips?: boolean;
16
+ disabled?: boolean;
17
+ }
18
+
19
+ declare global {
20
+ interface HTMLElementTagNameMap {
21
+ 'cx-speed-dial': HTMLElement & CxSpeedDialAttributes;
22
+ }
23
+ }