@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.
- package/README.md +77 -0
- package/custom-elements.json +6037 -0
- package/generated/.gitattributes +2 -0
- package/generated/index.d.ts +120 -0
- package/generated/index.js +521 -0
- package/generated/styles.js +2845 -0
- package/package.json +56 -0
- package/src/elements/accordion.d.ts +20 -0
- package/src/elements/accordion.js +92 -0
- package/src/elements/activity_group.d.ts +19 -0
- package/src/elements/activity_group.js +27 -0
- package/src/elements/alert.d.ts +24 -0
- package/src/elements/alert.js +40 -0
- package/src/elements/autocomplete.d.ts +30 -0
- package/src/elements/autocomplete.js +671 -0
- package/src/elements/avatar.d.ts +18 -0
- package/src/elements/avatar.js +28 -0
- package/src/elements/backdrop.d.ts +14 -0
- package/src/elements/backdrop.js +28 -0
- package/src/elements/badge.d.ts +21 -0
- package/src/elements/badge.js +42 -0
- package/src/elements/breadcrumb.d.ts +17 -0
- package/src/elements/breadcrumb.js +41 -0
- package/src/elements/button.d.ts +24 -0
- package/src/elements/button.js +36 -0
- package/src/elements/card.d.ts +21 -0
- package/src/elements/card.js +67 -0
- package/src/elements/carousel.d.ts +23 -0
- package/src/elements/carousel.js +895 -0
- package/src/elements/chat_input.d.ts +22 -0
- package/src/elements/chat_input.js +78 -0
- package/src/elements/checkbox.d.ts +21 -0
- package/src/elements/checkbox.js +114 -0
- package/src/elements/code_block.d.ts +21 -0
- package/src/elements/code_block.js +27 -0
- package/src/elements/collapsible.d.ts +20 -0
- package/src/elements/collapsible.js +93 -0
- package/src/elements/date_picker.d.ts +30 -0
- package/src/elements/date_picker.js +528 -0
- package/src/elements/dialog.d.ts +20 -0
- package/src/elements/dialog.js +314 -0
- package/src/elements/drawer.d.ts +20 -0
- package/src/elements/drawer.js +318 -0
- package/src/elements/fab.d.ts +22 -0
- package/src/elements/fab.js +36 -0
- package/src/elements/file_upload.d.ts +26 -0
- package/src/elements/file_upload.js +59 -0
- package/src/elements/listbox.d.ts +19 -0
- package/src/elements/listbox.js +250 -0
- package/src/elements/menu.d.ts +20 -0
- package/src/elements/menu.js +224 -0
- package/src/elements/message_bubble.d.ts +23 -0
- package/src/elements/message_bubble.js +29 -0
- package/src/elements/message_group.d.ts +18 -0
- package/src/elements/message_group.js +28 -0
- package/src/elements/message_part.d.ts +35 -0
- package/src/elements/message_part.js +153 -0
- package/src/elements/pagination.d.ts +22 -0
- package/src/elements/pagination.js +36 -0
- package/src/elements/popover.d.ts +26 -0
- package/src/elements/popover.js +191 -0
- package/src/elements/profile_menu.d.ts +20 -0
- package/src/elements/profile_menu.js +213 -0
- package/src/elements/progress.d.ts +18 -0
- package/src/elements/progress.js +31 -0
- package/src/elements/radio_group.d.ts +22 -0
- package/src/elements/radio_group.js +70 -0
- package/src/elements/scrollbar.d.ts +19 -0
- package/src/elements/scrollbar.js +299 -0
- package/src/elements/search_bar.d.ts +27 -0
- package/src/elements/search_bar.js +98 -0
- package/src/elements/select.d.ts +26 -0
- package/src/elements/select.js +485 -0
- package/src/elements/sidebar.d.ts +21 -0
- package/src/elements/sidebar.js +322 -0
- package/src/elements/skeleton.d.ts +17 -0
- package/src/elements/skeleton.js +31 -0
- package/src/elements/slider.d.ts +28 -0
- package/src/elements/slider.js +93 -0
- package/src/elements/speed_dial.d.ts +23 -0
- package/src/elements/speed_dial.js +370 -0
- package/src/elements/spinner.d.ts +15 -0
- package/src/elements/spinner.js +28 -0
- package/src/elements/split_button.d.ts +23 -0
- package/src/elements/split_button.js +281 -0
- package/src/elements/stepper.d.ts +20 -0
- package/src/elements/stepper.js +31 -0
- package/src/elements/switch.d.ts +22 -0
- package/src/elements/switch.js +129 -0
- package/src/elements/table.d.ts +29 -0
- package/src/elements/table.js +371 -0
- package/src/elements/tabs.d.ts +19 -0
- package/src/elements/tabs.js +139 -0
- package/src/elements/text.d.ts +26 -0
- package/src/elements/text.js +32 -0
- package/src/elements/text_input.d.ts +36 -0
- package/src/elements/text_input.js +121 -0
- package/src/elements/thinking.d.ts +17 -0
- package/src/elements/thinking.js +28 -0
- package/src/elements/toast.d.ts +23 -0
- package/src/elements/toast.js +209 -0
- package/src/elements/toggle_group.d.ts +22 -0
- package/src/elements/toggle_group.js +176 -0
- package/src/elements/tooltip.d.ts +18 -0
- package/src/elements/tooltip.js +64 -0
- package/src/markdown.d.ts +24 -0
- package/src/markdown.js +66 -0
- package/src/runtime.d.ts +35 -0
- package/src/runtime.js +790 -0
- package/src/server.d.ts +69 -0
- package/src/server.js +176 -0
- package/src/streaming-markdown.js +43 -0
- package/src/vite-plugin.d.ts +46 -0
- package/src/vite-plugin.js +221 -0
- package/wasm/package.json +16 -0
- package/wasm/wasm_api.d.ts +72 -0
- package/wasm/wasm_api.js +593 -0
- package/wasm/wasm_api_bg.wasm +0 -0
- package/wasm/wasm_api_bg.wasm.d.ts +10 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// Custom behavior for <cx-split-button> — primary click, dropdown toggle, keyboard nav.
|
|
2
|
+
//
|
|
3
|
+
// The Rust component renders:
|
|
4
|
+
// <div role="group" aria-label="..."> — button group
|
|
5
|
+
// <button part="primary"> — primary action
|
|
6
|
+
// <button part="trigger" aria-haspopup="menu" aria-expanded="false"
|
|
7
|
+
// data-floating-trigger data-floating-target="{menu_id}"> — dropdown toggle
|
|
8
|
+
// <div id="{menu_id}" role="menu" data-menu data-floating> — dropdown menu
|
|
9
|
+
// <button role="menuitem" data-action-id="{id}"> — each entry
|
|
10
|
+
// <div role="separator"> — dividers
|
|
11
|
+
//
|
|
12
|
+
// This Custom Element wires up:
|
|
13
|
+
// - Click primary → cx-click event
|
|
14
|
+
// - Click trigger → toggle dropdown menu
|
|
15
|
+
// - Click menu item → cx-action event + close
|
|
16
|
+
// - Click outside / focus exit → close
|
|
17
|
+
// - Keyboard: Enter/Space on trigger, ArrowDown opens, arrow nav in menu, Escape closes
|
|
18
|
+
//
|
|
19
|
+
// Source: crates/wasm-api/src/split_button.rs
|
|
20
|
+
|
|
21
|
+
let _sheet;
|
|
22
|
+
function getSheet() {
|
|
23
|
+
if (!_sheet) {
|
|
24
|
+
_sheet = new CSSStyleSheet();
|
|
25
|
+
_sheet.replaceSync([
|
|
26
|
+
':host { display: inline-flex; }',
|
|
27
|
+
// Dropdown uses position:fixed (set by JS) to escape scroll container clipping.
|
|
28
|
+
'[data-floating] {',
|
|
29
|
+
' z-index: 50;',
|
|
30
|
+
'}',
|
|
31
|
+
// Hover effect for menu items
|
|
32
|
+
'[role="menuitem"]:not([aria-disabled="true"]):hover {',
|
|
33
|
+
' background-color: var(--color-secondary);',
|
|
34
|
+
' cursor: pointer;',
|
|
35
|
+
'}',
|
|
36
|
+
// Focus indicator
|
|
37
|
+
'[role="menuitem"]:focus-visible {',
|
|
38
|
+
' background-color: var(--color-secondary);',
|
|
39
|
+
' outline: none;',
|
|
40
|
+
'}',
|
|
41
|
+
].join('\n'));
|
|
42
|
+
}
|
|
43
|
+
return _sheet;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function defineCxSplitButton(wasmFn, baseClass) {
|
|
47
|
+
class CxSplitButton extends baseClass {
|
|
48
|
+
static observedAttributes = ['id', 'label', 'entries', 'variant', 'intent', 'shape', 'size', 'icon-leading', 'disabled', 'primary-disabled', 'trigger-disabled', 'trigger-label'];
|
|
49
|
+
static _booleanAttrs = new Set(['disabled', 'primary-disabled', 'trigger-disabled']);
|
|
50
|
+
|
|
51
|
+
#outsideClick = null;
|
|
52
|
+
|
|
53
|
+
connectedCallback() {
|
|
54
|
+
if (!this._isInitialized) {
|
|
55
|
+
this._markInitialized();
|
|
56
|
+
const shadow = this._shadow;
|
|
57
|
+
|
|
58
|
+
const sheet = getSheet();
|
|
59
|
+
if (!shadow.adoptedStyleSheets.includes(sheet)) {
|
|
60
|
+
shadow.adoptedStyleSheets = [...shadow.adoptedStyleSheets, sheet];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Click handler ──
|
|
64
|
+
shadow.addEventListener('click', (e) => {
|
|
65
|
+
// Trigger button → toggle dropdown
|
|
66
|
+
const trigger = e.target.closest('[data-floating-trigger]');
|
|
67
|
+
if (trigger && !trigger.disabled && !trigger.hasAttribute('aria-disabled')) {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
this.#toggle();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Primary button → emit cx-click
|
|
74
|
+
const primary = e.target.closest('[part="primary"]');
|
|
75
|
+
if (primary && !primary.disabled) {
|
|
76
|
+
this._emit('cx-click', { originalEvent: e });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Menu item → emit cx-action, close
|
|
81
|
+
const menuitem = e.target.closest('[role="menuitem"]');
|
|
82
|
+
if (menuitem && !menuitem.hasAttribute('aria-disabled')) {
|
|
83
|
+
this._emit('cx-action', {
|
|
84
|
+
id: menuitem.getAttribute('data-action-id') || menuitem.id || '',
|
|
85
|
+
label: menuitem.textContent.trim()
|
|
86
|
+
});
|
|
87
|
+
this.#close();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ── Keyboard handler ──
|
|
92
|
+
shadow.addEventListener('keydown', (e) => this.#handleKey(e));
|
|
93
|
+
|
|
94
|
+
// ── Click outside → close ──
|
|
95
|
+
this.#outsideClick = (e) => {
|
|
96
|
+
if (this.#isOpen() && !this.contains(e.target) && !this._shadow.contains(e.target)) {
|
|
97
|
+
this.#close();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
document.addEventListener('mousedown', this.#outsideClick);
|
|
101
|
+
|
|
102
|
+
// ── Focus exit → close ──
|
|
103
|
+
// Must check both light DOM and shadow DOM — Node.contains() doesn't cross shadow boundaries.
|
|
104
|
+
shadow.addEventListener('focusout', () => {
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
if (this.#isOpen()) {
|
|
107
|
+
const active = this._shadow.activeElement || document.activeElement;
|
|
108
|
+
if (!this.contains(active) && !this._shadow.contains(active) && active !== this) {
|
|
109
|
+
this.#close();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}, 0);
|
|
113
|
+
});
|
|
114
|
+
} // end _isInitialized guard
|
|
115
|
+
super.connectedCallback();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
disconnectedCallback() {
|
|
119
|
+
if (this.#outsideClick) {
|
|
120
|
+
document.removeEventListener('mousedown', this.#outsideClick);
|
|
121
|
+
this.#outsideClick = null;
|
|
122
|
+
}
|
|
123
|
+
super.disconnectedCallback();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── DOM accessors ──
|
|
127
|
+
|
|
128
|
+
#getTrigger() {
|
|
129
|
+
return this._shadow.querySelector('[data-floating-trigger]');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#getMenu() {
|
|
133
|
+
return this._shadow.querySelector('[role="menu"]');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#getItems() {
|
|
137
|
+
return Array.from(
|
|
138
|
+
this._shadow.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Menu state ──
|
|
143
|
+
|
|
144
|
+
#isOpen() {
|
|
145
|
+
const menu = this.#getMenu();
|
|
146
|
+
return menu ? menu.hasAttribute('data-open') : false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#open() {
|
|
150
|
+
const trigger = this.#getTrigger();
|
|
151
|
+
const menu = this.#getMenu();
|
|
152
|
+
if (!trigger || !menu || this.#isOpen()) return;
|
|
153
|
+
|
|
154
|
+
// Position with fixed coordinates — escapes overflow:auto/scroll clipping.
|
|
155
|
+
this._positionFloatingFixed(trigger, menu, { matchWidth: true });
|
|
156
|
+
|
|
157
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
158
|
+
menu.setAttribute('data-open', '');
|
|
159
|
+
menu.classList.remove('hidden');
|
|
160
|
+
menu.style.display = 'block';
|
|
161
|
+
menu.style.pointerEvents = 'auto';
|
|
162
|
+
menu.style.opacity = '1';
|
|
163
|
+
|
|
164
|
+
// Focus first menu item
|
|
165
|
+
requestAnimationFrame(() => {
|
|
166
|
+
const items = this.#getItems();
|
|
167
|
+
if (items.length) items[0].focus();
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#close() {
|
|
172
|
+
const trigger = this.#getTrigger();
|
|
173
|
+
const menu = this.#getMenu();
|
|
174
|
+
if (!trigger || !menu) return;
|
|
175
|
+
|
|
176
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
177
|
+
menu.removeAttribute('data-open');
|
|
178
|
+
menu.classList.add('hidden');
|
|
179
|
+
menu.style.display = '';
|
|
180
|
+
menu.style.pointerEvents = '';
|
|
181
|
+
menu.style.opacity = '';
|
|
182
|
+
this._resetFloatingFixed(menu);
|
|
183
|
+
trigger.focus();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#toggle() {
|
|
187
|
+
if (this.#isOpen()) this.#close();
|
|
188
|
+
else this.#open();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── Keyboard navigation ──
|
|
192
|
+
|
|
193
|
+
#handleKey(e) {
|
|
194
|
+
// Trigger: Enter/Space toggle
|
|
195
|
+
const trigger = e.target.closest('[data-floating-trigger]');
|
|
196
|
+
if (trigger) {
|
|
197
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
this.#toggle();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// ArrowDown on trigger opens menu
|
|
203
|
+
if (e.key === 'ArrowDown') {
|
|
204
|
+
e.preventDefault();
|
|
205
|
+
if (!this.#isOpen()) this.#open();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Menu item navigation
|
|
211
|
+
const menuitem = e.target.closest('[role="menuitem"]');
|
|
212
|
+
if (!menuitem) return;
|
|
213
|
+
|
|
214
|
+
const items = this.#getItems();
|
|
215
|
+
const idx = items.indexOf(menuitem);
|
|
216
|
+
if (idx === -1) return;
|
|
217
|
+
|
|
218
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
219
|
+
e.preventDefault();
|
|
220
|
+
const ni = e.key === 'ArrowDown'
|
|
221
|
+
? (idx + 1) % items.length
|
|
222
|
+
: (idx - 1 + items.length) % items.length;
|
|
223
|
+
items[ni].focus();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (e.key === 'Home') {
|
|
228
|
+
e.preventDefault();
|
|
229
|
+
if (items.length) items[0].focus();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (e.key === 'End') {
|
|
234
|
+
e.preventDefault();
|
|
235
|
+
if (items.length) items[items.length - 1].focus();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
240
|
+
e.preventDefault();
|
|
241
|
+
if (!menuitem.hasAttribute('aria-disabled')) menuitem.click();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (e.key === 'Escape') {
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
this.#close();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (e.key === 'Tab') {
|
|
252
|
+
this.#close();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── Public imperative API ──
|
|
257
|
+
open() { this.#open(); }
|
|
258
|
+
close() { this.#close(); }
|
|
259
|
+
|
|
260
|
+
_doRender() {
|
|
261
|
+
try {
|
|
262
|
+
const wasOpen = this.#isOpen();
|
|
263
|
+
|
|
264
|
+
const result = wasmFn(this._props);
|
|
265
|
+
this._injectHtml(result);
|
|
266
|
+
|
|
267
|
+
const sheet = getSheet();
|
|
268
|
+
if (!this._shadow.adoptedStyleSheets.includes(sheet)) {
|
|
269
|
+
this._shadow.adoptedStyleSheets = [...this._shadow.adoptedStyleSheets, sheet];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (wasOpen) this.#open();
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.error('[cx-split-button]', e);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
customElements.define('cx-split-button', CxSplitButton);
|
|
280
|
+
return CxSplitButton;
|
|
281
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/stepper.rs
|
|
3
|
+
|
|
4
|
+
export interface CxStepperAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
steps?: string;
|
|
8
|
+
current?: number;
|
|
9
|
+
variant?: 'outline' | 'filled' | 'ghost';
|
|
10
|
+
shape?: 'sharp' | 'rounded';
|
|
11
|
+
orientation?: 'horizontal' | 'vertical';
|
|
12
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
13
|
+
linear?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare global {
|
|
17
|
+
interface HTMLElementTagNameMap {
|
|
18
|
+
'cx-stepper': HTMLElement & CxStepperAttributes;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/stepper.rs
|
|
3
|
+
|
|
4
|
+
export function defineCxStepper(wasmFn, baseClass) {
|
|
5
|
+
class CxStepper extends baseClass {
|
|
6
|
+
static observedAttributes = ['id', 'label', 'steps', 'current', 'variant', 'shape', 'orientation', 'size', 'linear'];
|
|
7
|
+
static _booleanAttrs = new Set(['linear']);
|
|
8
|
+
static _numericAttrs = new Set(['current']);
|
|
9
|
+
static _focusable = false;
|
|
10
|
+
static _hostDisplay = 'block';
|
|
11
|
+
|
|
12
|
+
set current(v) { this._setProp('current', v); }
|
|
13
|
+
get current() { return this._props.current; }
|
|
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-stepper]', e);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
customElements.define('cx-stepper', CxStepper);
|
|
30
|
+
return CxStepper;
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/switch.rs
|
|
3
|
+
|
|
4
|
+
export interface CxSwitchAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
shape?: 'sharp' | 'rounded' | 'pill';
|
|
8
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
9
|
+
checked?: boolean;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
required?: boolean;
|
|
12
|
+
helperText?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
unlabeled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare global {
|
|
19
|
+
interface HTMLElementTagNameMap {
|
|
20
|
+
'cx-switch': HTMLElement & CxSwitchAttributes;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Custom behavior for <cx-switch> — toggle with CSS transition animation.
|
|
2
|
+
// Source: crates/wasm-api/src/switch.rs
|
|
3
|
+
|
|
4
|
+
// Size → thumb translate distance mapping (mirrors Rust switch/styles.rs).
|
|
5
|
+
// Values match Tailwind's spacing scale: translate-x-N = N * 0.25rem.
|
|
6
|
+
const THUMB_TRANSLATE = {
|
|
7
|
+
xs: '0.75rem', // translate-x-3
|
|
8
|
+
sm: '0.875rem', // translate-x-3.5
|
|
9
|
+
md: '1rem', // translate-x-4
|
|
10
|
+
lg: '1.25rem', // translate-x-5
|
|
11
|
+
xl: '1.25rem', // translate-x-5
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function defineCxSwitch(wasmFn, baseClass) {
|
|
15
|
+
class CxSwitch extends baseClass {
|
|
16
|
+
static observedAttributes = ['id', 'label', 'shape', 'size', 'checked', 'disabled', 'required', 'helper-text', 'error', 'description', 'unlabeled'];
|
|
17
|
+
static _booleanAttrs = new Set(['checked', 'disabled', 'required', 'unlabeled']);
|
|
18
|
+
static _hostDisplay = 'inline-flex';
|
|
19
|
+
|
|
20
|
+
// Thumb translate distance for current size.
|
|
21
|
+
#thumbDistance = THUMB_TRANSLATE.md;
|
|
22
|
+
|
|
23
|
+
connectedCallback() {
|
|
24
|
+
if (!this._isInitialized) {
|
|
25
|
+
this._markInitialized();
|
|
26
|
+
const shadow = this._shadow;
|
|
27
|
+
|
|
28
|
+
// Switch toggle: manipulate DOM directly (not re-render) so CSS
|
|
29
|
+
// transitions can animate the thumb slide and track color change.
|
|
30
|
+
shadow.addEventListener('click', (e) => {
|
|
31
|
+
const btn = e.target.closest('button[role="switch"]');
|
|
32
|
+
if (!btn || this._props.disabled) return;
|
|
33
|
+
|
|
34
|
+
const isChecked = btn.getAttribute('aria-checked') === 'true';
|
|
35
|
+
const newChecked = !isChecked;
|
|
36
|
+
|
|
37
|
+
// Toggle ARIA state
|
|
38
|
+
btn.setAttribute('aria-checked', String(newChecked));
|
|
39
|
+
|
|
40
|
+
// Toggle track background color
|
|
41
|
+
btn.style.backgroundColor = newChecked
|
|
42
|
+
? 'var(--color-primary)' : 'var(--color-secondary)';
|
|
43
|
+
|
|
44
|
+
// Toggle thumb position via inline translate.
|
|
45
|
+
// Using style.translate instead of class toggle ensures both states
|
|
46
|
+
// have explicit values, so the CSS transition always animates.
|
|
47
|
+
const thumb = btn.querySelector('span[aria-hidden="true"]');
|
|
48
|
+
if (thumb) {
|
|
49
|
+
thumb.style.translate = newChecked ? `${this.#thumbDistance} 0` : '0 0';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Toggle hidden input
|
|
53
|
+
const input = shadow.querySelector('input[type="checkbox"]');
|
|
54
|
+
if (input) input.checked = newChecked;
|
|
55
|
+
|
|
56
|
+
// Sync props (for next full re-render from attribute change)
|
|
57
|
+
this._props.checked = newChecked;
|
|
58
|
+
|
|
59
|
+
// Form participation
|
|
60
|
+
this._setFormValue(newChecked ? 'on' : '');
|
|
61
|
+
this._emit('cx-input', { checked: newChecked });
|
|
62
|
+
this._emit('cx-change', { checked: newChecked });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Forward focus events from inner interactive elements
|
|
66
|
+
shadow.addEventListener('focusin', (e) => {
|
|
67
|
+
this._emit('cx-focus', { relatedTarget: e.relatedTarget });
|
|
68
|
+
});
|
|
69
|
+
shadow.addEventListener('focusout', (e) => {
|
|
70
|
+
this._emit('cx-blur', { relatedTarget: e.relatedTarget });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Forward keyboard events from inner interactive elements
|
|
74
|
+
shadow.addEventListener('keydown', (e) => {
|
|
75
|
+
this._emit('cx-keydown', { key: e.key, code: e.code, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, altKey: e.altKey, metaKey: e.metaKey });
|
|
76
|
+
});
|
|
77
|
+
shadow.addEventListener('keyup', (e) => {
|
|
78
|
+
this._emit('cx-keyup', { key: e.key, code: e.code, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, altKey: e.altKey, metaKey: e.metaKey });
|
|
79
|
+
});
|
|
80
|
+
} // end _isInitialized guard
|
|
81
|
+
super.connectedCallback();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Override: update thumb distance when size changes.
|
|
85
|
+
attributeChangedCallback(name, old, value) {
|
|
86
|
+
if (name === 'size') {
|
|
87
|
+
this.#thumbDistance = THUMB_TRANSLATE[value] || THUMB_TRANSLATE.md;
|
|
88
|
+
}
|
|
89
|
+
super.attributeChangedCallback(name, old, value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── Public imperative API ──
|
|
93
|
+
focus() { const el = this._shadow.querySelector('button[role="switch"]'); if (el) el.focus(); else super.focus(); }
|
|
94
|
+
|
|
95
|
+
_doRender() {
|
|
96
|
+
try {
|
|
97
|
+
const result = wasmFn(this._props);
|
|
98
|
+
this._injectHtml(result);
|
|
99
|
+
|
|
100
|
+
// Set initial thumb translate so CSS transition has explicit
|
|
101
|
+
// start/end values. WASM renders checked state via Tailwind
|
|
102
|
+
// classes, but we need inline styles for the transition to work.
|
|
103
|
+
const thumb = this._shadow.querySelector(
|
|
104
|
+
'button[role="switch"] > span[aria-hidden="true"]'
|
|
105
|
+
);
|
|
106
|
+
if (thumb) {
|
|
107
|
+
const isChecked = this._props.checked;
|
|
108
|
+
// Remove any Tailwind translate class (it would conflict with inline style)
|
|
109
|
+
for (const cls of [...thumb.classList]) {
|
|
110
|
+
if (cls.startsWith('translate-x-')) {
|
|
111
|
+
thumb.classList.remove(cls);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Set explicit translate for transition animation
|
|
115
|
+
thumb.style.translate = isChecked ? `${this.#thumbDistance} 0` : '0 0';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Sync form value
|
|
119
|
+
const input = this._shadow.querySelector('input[type="checkbox"]');
|
|
120
|
+
if (input) this._setFormValue(input.checked ? 'on' : '');
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error('[cx-switch]', e);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
customElements.define('cx-switch', CxSwitch);
|
|
128
|
+
return CxSwitch;
|
|
129
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/table.rs
|
|
3
|
+
|
|
4
|
+
export interface CxTableAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
caption: string;
|
|
7
|
+
columns?: string;
|
|
8
|
+
rows?: string;
|
|
9
|
+
variant?: 'plain' | 'striped' | 'bordered';
|
|
10
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
11
|
+
selectable?: string;
|
|
12
|
+
selected?: string;
|
|
13
|
+
sorts?: string;
|
|
14
|
+
pagination?: string;
|
|
15
|
+
hoverable?: boolean;
|
|
16
|
+
stickyHeader?: boolean;
|
|
17
|
+
footer?: string;
|
|
18
|
+
emptyState?: string;
|
|
19
|
+
columnOrder?: string;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
loading?: number;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
declare global {
|
|
26
|
+
interface HTMLElementTagNameMap {
|
|
27
|
+
'cx-table': HTMLElement & CxTableAttributes;
|
|
28
|
+
}
|
|
29
|
+
}
|