@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,36 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/fab.rs
|
|
3
|
+
|
|
4
|
+
export function defineCxFab(wasmFn, baseClass) {
|
|
5
|
+
class CxFab extends baseClass {
|
|
6
|
+
static observedAttributes = ['icon', 'label', 'aria-label', 'variant', 'intent', 'shape', 'size', 'disabled', 'has-popup', 'expanded', 'controls'];
|
|
7
|
+
static _booleanAttrs = new Set(['disabled', 'has-popup', 'expanded']);
|
|
8
|
+
static _hostDisplay = 'inline-flex';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
connectedCallback() {
|
|
12
|
+
if (!this._isInitialized) {
|
|
13
|
+
this._markInitialized();
|
|
14
|
+
// Delegate click from inner element to Custom Event
|
|
15
|
+
this._shadow.addEventListener('click', (e) => {
|
|
16
|
+
if (!this.hasAttribute('disabled')) {
|
|
17
|
+
this._emit('cx-click', { originalEvent: e });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
super.connectedCallback();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_doRender() {
|
|
25
|
+
try {
|
|
26
|
+
const result = wasmFn(this._props);
|
|
27
|
+
this._injectHtml(result);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error('[cx-fab]', e);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
customElements.define('cx-fab', CxFab);
|
|
35
|
+
return CxFab;
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/file_upload.rs
|
|
3
|
+
|
|
4
|
+
export interface CxFileUploadAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
mode?: 'inline' | 'overlay';
|
|
8
|
+
variant?: 'outline' | 'filled' | 'subtle';
|
|
9
|
+
shape?: 'sharp' | 'rounded' | 'pill';
|
|
10
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
multiple?: boolean;
|
|
13
|
+
accept?: string;
|
|
14
|
+
maxSize?: number;
|
|
15
|
+
heading?: string;
|
|
16
|
+
browseText?: string;
|
|
17
|
+
hint?: string;
|
|
18
|
+
capture?: string;
|
|
19
|
+
preview?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare global {
|
|
23
|
+
interface HTMLElementTagNameMap {
|
|
24
|
+
'cx-file-upload': HTMLElement & CxFileUploadAttributes;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/file_upload.rs
|
|
3
|
+
|
|
4
|
+
export function defineCxFileUpload(wasmFn, baseClass) {
|
|
5
|
+
class CxFileUpload extends baseClass {
|
|
6
|
+
static observedAttributes = ['id', 'label', 'mode', 'variant', 'shape', 'size', 'disabled', 'multiple', 'accept', 'heading', 'browse-text', 'hint', 'capture', 'preview'];
|
|
7
|
+
static _booleanAttrs = new Set(['disabled', 'multiple', 'preview']);
|
|
8
|
+
static _hostDisplay = 'block';
|
|
9
|
+
|
|
10
|
+
set max_size(v) { this._setProp('max_size', v); }
|
|
11
|
+
get max_size() { return this._props.max_size; }
|
|
12
|
+
|
|
13
|
+
connectedCallback() {
|
|
14
|
+
if (!this._isInitialized) {
|
|
15
|
+
this._markInitialized();
|
|
16
|
+
const shadow = this._shadow;
|
|
17
|
+
|
|
18
|
+
// Delegate change events from the hidden file input → cx-change on the host
|
|
19
|
+
shadow.addEventListener('change', (e) => {
|
|
20
|
+
const input = e.target.closest('input[type="file"]');
|
|
21
|
+
if (input && input.files) {
|
|
22
|
+
const files = Array.from(input.files).map(f => ({
|
|
23
|
+
name: f.name,
|
|
24
|
+
size: f.size,
|
|
25
|
+
type: f.type,
|
|
26
|
+
}));
|
|
27
|
+
this._emit('cx-change', { files });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Delegate click on zone or browse button → open file picker
|
|
32
|
+
shadow.addEventListener('click', (e) => {
|
|
33
|
+
if (this.hasAttribute('disabled')) return;
|
|
34
|
+
const input = shadow.querySelector('input[type="file"]');
|
|
35
|
+
if (!input) return;
|
|
36
|
+
// Don't re-trigger if clicking the input itself
|
|
37
|
+
if (e.target === input) return;
|
|
38
|
+
// Don't trigger if clicking file list dismiss buttons
|
|
39
|
+
if (e.target.closest('[data-file-remove]')) return;
|
|
40
|
+
if (e.target.closest('[data-file-upload-list]')) return;
|
|
41
|
+
input.click();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
super.connectedCallback();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_doRender() {
|
|
48
|
+
try {
|
|
49
|
+
const result = wasmFn(this._props);
|
|
50
|
+
this._injectHtml(result);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error('[cx-file-upload]', e);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
customElements.define('cx-file-upload', CxFileUpload);
|
|
58
|
+
return CxFileUpload;
|
|
59
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/listbox.rs
|
|
3
|
+
|
|
4
|
+
export interface CxListboxAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
shape?: 'sharp' | 'rounded' | 'pill';
|
|
8
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
9
|
+
selectionMode?: string;
|
|
10
|
+
selected?: string;
|
|
11
|
+
items?: string;
|
|
12
|
+
groups?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare global {
|
|
16
|
+
interface HTMLElementTagNameMap {
|
|
17
|
+
'cx-listbox': HTMLElement & CxListboxAttributes;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// Custom behavior for <cx-listbox> — option selection + keyboard navigation.
|
|
2
|
+
//
|
|
3
|
+
// The Rust component renders:
|
|
4
|
+
// <div role="listbox"> — the container
|
|
5
|
+
// <div role="option" data-value="..." aria-selected="..."> — each option
|
|
6
|
+
// <span class="truncate">Label</span>
|
|
7
|
+
// <span data-check class="hidden|inline-flex">✓</span>
|
|
8
|
+
//
|
|
9
|
+
// This Custom Element wires up:
|
|
10
|
+
// - Click option to select/deselect
|
|
11
|
+
// - Keyboard navigation (ArrowUp/Down, Home/End, Enter/Space)
|
|
12
|
+
// - Single and multiple selection modes
|
|
13
|
+
// - cx-change event emission with selected value(s)
|
|
14
|
+
//
|
|
15
|
+
// Source: crates/wasm-api/src/listbox.rs
|
|
16
|
+
|
|
17
|
+
let _sheet;
|
|
18
|
+
function getSheet() {
|
|
19
|
+
if (!_sheet) {
|
|
20
|
+
_sheet = new CSSStyleSheet();
|
|
21
|
+
_sheet.replaceSync([
|
|
22
|
+
'[role="option"][data-focused] {',
|
|
23
|
+
' background-color: var(--color-secondary);',
|
|
24
|
+
'}',
|
|
25
|
+
'[role="option"]:not([aria-disabled="true"]):hover {',
|
|
26
|
+
' background-color: var(--color-secondary);',
|
|
27
|
+
' cursor: pointer;',
|
|
28
|
+
'}',
|
|
29
|
+
].join('\n'));
|
|
30
|
+
}
|
|
31
|
+
return _sheet;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function defineCxListbox(wasmFn, baseClass) {
|
|
35
|
+
class CxListbox extends baseClass {
|
|
36
|
+
static observedAttributes = ['id', 'label', 'shape', 'size', 'selection-mode', 'selected', 'items', 'groups'];
|
|
37
|
+
static _booleanAttrs = new Set([]);
|
|
38
|
+
|
|
39
|
+
connectedCallback() {
|
|
40
|
+
if (!this._isInitialized) {
|
|
41
|
+
this._markInitialized();
|
|
42
|
+
const shadow = this._shadow;
|
|
43
|
+
|
|
44
|
+
const sheet = getSheet();
|
|
45
|
+
if (!shadow.adoptedStyleSheets.includes(sheet)) {
|
|
46
|
+
shadow.adoptedStyleSheets = [...shadow.adoptedStyleSheets, sheet];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Click handler ──
|
|
50
|
+
shadow.addEventListener('click', (e) => {
|
|
51
|
+
const option = e.target.closest('[role="option"]');
|
|
52
|
+
if (option && !option.hasAttribute('aria-disabled')) {
|
|
53
|
+
this.#selectOption(option);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ── Keyboard handler ──
|
|
58
|
+
shadow.addEventListener('keydown', (e) => this.#handleKey(e));
|
|
59
|
+
|
|
60
|
+
// Forward focus events from inner interactive elements
|
|
61
|
+
shadow.addEventListener('focusin', (e) => {
|
|
62
|
+
this._emit('cx-focus', { relatedTarget: e.relatedTarget });
|
|
63
|
+
});
|
|
64
|
+
shadow.addEventListener('focusout', (e) => {
|
|
65
|
+
this._emit('cx-blur', { relatedTarget: e.relatedTarget });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Forward keyboard events from inner interactive elements
|
|
69
|
+
shadow.addEventListener('keydown', (e) => {
|
|
70
|
+
this._emit('cx-keydown', { key: e.key, code: e.code, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, altKey: e.altKey, metaKey: e.metaKey });
|
|
71
|
+
});
|
|
72
|
+
shadow.addEventListener('keyup', (e) => {
|
|
73
|
+
this._emit('cx-keyup', { key: e.key, code: e.code, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, altKey: e.altKey, metaKey: e.metaKey });
|
|
74
|
+
});
|
|
75
|
+
} // end _isInitialized guard
|
|
76
|
+
super.connectedCallback();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#getListbox() {
|
|
80
|
+
return this._shadow.querySelector('[role="listbox"]');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#getVisibleOptions() {
|
|
84
|
+
return Array.from(
|
|
85
|
+
this._shadow.querySelectorAll(
|
|
86
|
+
'[role="option"]:not([aria-disabled="true"]):not([hidden])'
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#getMode() {
|
|
92
|
+
return this._props.selection_mode || 'single';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Keyboard navigation ──
|
|
96
|
+
|
|
97
|
+
#handleKey(e) {
|
|
98
|
+
const listbox = this.#getListbox();
|
|
99
|
+
if (!listbox) return;
|
|
100
|
+
|
|
101
|
+
// Only handle if focus is inside the listbox
|
|
102
|
+
if (!listbox.contains(e.target) && e.target !== listbox) return;
|
|
103
|
+
|
|
104
|
+
switch (e.key) {
|
|
105
|
+
case 'ArrowDown':
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
this.#focusNext();
|
|
108
|
+
break;
|
|
109
|
+
case 'ArrowUp':
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
this.#focusPrev();
|
|
112
|
+
break;
|
|
113
|
+
case 'Home':
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
this.#focusFirst();
|
|
116
|
+
break;
|
|
117
|
+
case 'End':
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
this.#focusLast();
|
|
120
|
+
break;
|
|
121
|
+
case 'Enter':
|
|
122
|
+
case ' ':
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
const active = this.#getActiveOption();
|
|
125
|
+
if (active) this.#selectOption(active);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── Focus management (roving tabindex) ──
|
|
131
|
+
|
|
132
|
+
#clearFocused() {
|
|
133
|
+
this._shadow.querySelectorAll('[role="option"][data-focused]')
|
|
134
|
+
.forEach(o => {
|
|
135
|
+
o.removeAttribute('data-focused');
|
|
136
|
+
o.setAttribute('tabindex', '-1');
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#setActive(option) {
|
|
141
|
+
this.#clearFocused();
|
|
142
|
+
if (option) {
|
|
143
|
+
option.setAttribute('data-focused', '');
|
|
144
|
+
option.setAttribute('tabindex', '0');
|
|
145
|
+
option.focus();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#getActiveOption() {
|
|
150
|
+
return this._shadow.querySelector('[role="option"][data-focused]');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#focusFirst() {
|
|
154
|
+
const opts = this.#getVisibleOptions();
|
|
155
|
+
if (opts.length) this.#setActive(opts[0]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#focusLast() {
|
|
159
|
+
const opts = this.#getVisibleOptions();
|
|
160
|
+
if (opts.length) this.#setActive(opts[opts.length - 1]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#focusNext() {
|
|
164
|
+
const opts = this.#getVisibleOptions();
|
|
165
|
+
if (!opts.length) return;
|
|
166
|
+
const active = this.#getActiveOption();
|
|
167
|
+
const idx = active ? opts.indexOf(active) : -1;
|
|
168
|
+
this.#setActive(opts[(idx + 1) % opts.length]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#focusPrev() {
|
|
172
|
+
const opts = this.#getVisibleOptions();
|
|
173
|
+
if (!opts.length) return;
|
|
174
|
+
const active = this.#getActiveOption();
|
|
175
|
+
const idx = active ? opts.indexOf(active) : 0;
|
|
176
|
+
this.#setActive(opts[(idx - 1 + opts.length) % opts.length]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Option selection ──
|
|
180
|
+
|
|
181
|
+
#selectOption(option) {
|
|
182
|
+
const value = option.getAttribute('data-value') || '';
|
|
183
|
+
const mode = this.#getMode();
|
|
184
|
+
|
|
185
|
+
if (mode === 'single') {
|
|
186
|
+
// Single: replace selection
|
|
187
|
+
this._shadow.querySelectorAll('[role="option"]').forEach(o => {
|
|
188
|
+
const isThis = o === option;
|
|
189
|
+
o.setAttribute('aria-selected', String(isThis));
|
|
190
|
+
o.setAttribute('data-selected', String(isThis));
|
|
191
|
+
const check = o.querySelector('[data-check]');
|
|
192
|
+
if (check) {
|
|
193
|
+
if (isThis) check.classList.remove('hidden');
|
|
194
|
+
else check.classList.add('hidden');
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Sync host prop — single source of truth for WASM re-renders.
|
|
199
|
+
this._props.selected = value;
|
|
200
|
+
|
|
201
|
+
this._emit('cx-change', { value });
|
|
202
|
+
} else {
|
|
203
|
+
// Multiple: toggle
|
|
204
|
+
const wasSelected = option.getAttribute('aria-selected') === 'true';
|
|
205
|
+
const nowSelected = !wasSelected;
|
|
206
|
+
|
|
207
|
+
option.setAttribute('aria-selected', String(nowSelected));
|
|
208
|
+
option.setAttribute('data-selected', String(nowSelected));
|
|
209
|
+
const check = option.querySelector('[data-check]');
|
|
210
|
+
if (check) {
|
|
211
|
+
if (nowSelected) check.classList.remove('hidden');
|
|
212
|
+
else check.classList.add('hidden');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Collect all selected
|
|
216
|
+
const selected = [];
|
|
217
|
+
this._shadow.querySelectorAll('[role="option"][aria-selected="true"]')
|
|
218
|
+
.forEach(o => selected.push(o.getAttribute('data-value') || ''));
|
|
219
|
+
|
|
220
|
+
// Sync host prop — single source of truth for WASM re-renders.
|
|
221
|
+
this._props.selected = selected.join(',');
|
|
222
|
+
|
|
223
|
+
this._emit('cx-change', { value: selected });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_doRender() {
|
|
228
|
+
try {
|
|
229
|
+
const result = wasmFn(this._props);
|
|
230
|
+
this._injectHtml(result);
|
|
231
|
+
|
|
232
|
+
const sheet = getSheet();
|
|
233
|
+
if (!this._shadow.adoptedStyleSheets.includes(sheet)) {
|
|
234
|
+
this._shadow.adoptedStyleSheets = [...this._shadow.adoptedStyleSheets, sheet];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Make listbox focusable
|
|
238
|
+
const listbox = this.#getListbox();
|
|
239
|
+
if (listbox && !listbox.hasAttribute('tabindex')) {
|
|
240
|
+
listbox.setAttribute('tabindex', '0');
|
|
241
|
+
}
|
|
242
|
+
} catch (e) {
|
|
243
|
+
console.error('[cx-listbox]', e);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
customElements.define('cx-listbox', CxListbox);
|
|
249
|
+
return CxListbox;
|
|
250
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/menu.rs
|
|
3
|
+
|
|
4
|
+
export interface CxMenuAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
triggerLabel: string;
|
|
7
|
+
entries?: string;
|
|
8
|
+
variant?: 'filled' | 'outline' | 'ghost';
|
|
9
|
+
shape?: 'sharp' | 'rounded' | 'pill';
|
|
10
|
+
width?: 'auto' | 'sm' | 'md' | 'lg';
|
|
11
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
12
|
+
icon?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare global {
|
|
17
|
+
interface HTMLElementTagNameMap {
|
|
18
|
+
'cx-menu': HTMLElement & CxMenuAttributes;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Custom behavior for <cx-menu> — dropdown open/close, keyboard nav, item click.
|
|
2
|
+
// Mirrors static/_behaviors/menu.js but operates inside shadow DOM.
|
|
3
|
+
// Source: crates/wasm-api/src/menu.rs
|
|
4
|
+
|
|
5
|
+
export function defineCxMenu(wasmFn, baseClass) {
|
|
6
|
+
class CxMenu extends baseClass {
|
|
7
|
+
static observedAttributes = ['id', 'trigger-label', 'entries', 'variant', 'shape', 'width', 'size', 'icon', 'disabled'];
|
|
8
|
+
static _booleanAttrs = new Set(['disabled']);
|
|
9
|
+
static _hostDisplay = 'inline-flex';
|
|
10
|
+
|
|
11
|
+
#outsideClick = null;
|
|
12
|
+
|
|
13
|
+
connectedCallback() {
|
|
14
|
+
if (!this._isInitialized) {
|
|
15
|
+
this._markInitialized();
|
|
16
|
+
const shadow = this._shadow;
|
|
17
|
+
|
|
18
|
+
// Click delegation
|
|
19
|
+
shadow.addEventListener('click', (e) => {
|
|
20
|
+
// Trigger button: toggle menu
|
|
21
|
+
const trigger = e.target.closest('[data-floating-trigger]');
|
|
22
|
+
if (trigger) {
|
|
23
|
+
if (this.#isOpen()) {
|
|
24
|
+
this.#closeMenu();
|
|
25
|
+
} else {
|
|
26
|
+
this.#openMenu();
|
|
27
|
+
}
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Menu item click (regular menuitem): close and emit
|
|
32
|
+
const mi = e.target.closest('[role="menuitem"]');
|
|
33
|
+
if (mi && !mi.hasAttribute('aria-disabled')) {
|
|
34
|
+
const id = mi.getAttribute('data-item-id') || mi.textContent.trim();
|
|
35
|
+
this._emit('cx-action', { id });
|
|
36
|
+
this.#closeMenu();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Checkbox/radio items: toggle state but don't close
|
|
41
|
+
const mci = e.target.closest('[role="menuitemcheckbox"],[role="menuitemradio"]');
|
|
42
|
+
if (mci && !mci.hasAttribute('aria-disabled')) {
|
|
43
|
+
const checked = mci.getAttribute('aria-checked') === 'true';
|
|
44
|
+
const nowChecked = !checked;
|
|
45
|
+
mci.setAttribute('aria-checked', String(nowChecked));
|
|
46
|
+
|
|
47
|
+
// For radio items: deselect siblings in same group
|
|
48
|
+
if (mci.getAttribute('role') === 'menuitemradio' && nowChecked) {
|
|
49
|
+
const group = mci.closest('[role="group"]');
|
|
50
|
+
if (group) {
|
|
51
|
+
group.querySelectorAll('[role="menuitemradio"]').forEach(r => {
|
|
52
|
+
if (r !== mci) r.setAttribute('aria-checked', 'false');
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Sync checked item IDs to _props for re-render survival.
|
|
58
|
+
// Store as comma-separated list of checked data-item-ids.
|
|
59
|
+
const panel = shadow.querySelector('[data-menu]');
|
|
60
|
+
if (panel) {
|
|
61
|
+
const checkedIds = [];
|
|
62
|
+
panel.querySelectorAll('[role="menuitemcheckbox"][aria-checked="true"],[role="menuitemradio"][aria-checked="true"]')
|
|
63
|
+
.forEach(el => checkedIds.push(el.getAttribute('data-item-id') || ''));
|
|
64
|
+
this._props._checked_items = checkedIds.join(',');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this._emit('cx-change', {
|
|
68
|
+
id: mci.getAttribute('data-item-id') || mci.textContent.trim(),
|
|
69
|
+
checked: nowChecked,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Keyboard navigation
|
|
75
|
+
shadow.addEventListener('keydown', (e) => {
|
|
76
|
+
const menuitem = e.target.closest('[role="menuitem"],[role="menuitemcheckbox"],[role="menuitemradio"]');
|
|
77
|
+
|
|
78
|
+
// Escape: close menu
|
|
79
|
+
if (e.key === 'Escape' && this.#isOpen()) {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
this.#closeMenu();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Arrow keys on trigger
|
|
86
|
+
if (e.target.closest('[data-floating-trigger]') && (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')) {
|
|
87
|
+
if (!this.#isOpen()) {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
this.#openMenu();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!menuitem) return;
|
|
95
|
+
|
|
96
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Home' || e.key === 'End') {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
const panel = shadow.querySelector('[data-menu]');
|
|
99
|
+
if (!panel) return;
|
|
100
|
+
const all = Array.from(panel.querySelectorAll(
|
|
101
|
+
'[role="menuitem"]:not([aria-disabled]),[role="menuitemcheckbox"]:not([aria-disabled]),[role="menuitemradio"]:not([aria-disabled])'
|
|
102
|
+
));
|
|
103
|
+
const ci = all.indexOf(menuitem);
|
|
104
|
+
if (ci === -1) return;
|
|
105
|
+
let ni = ci;
|
|
106
|
+
if (e.key === 'ArrowDown') ni = (ci + 1) % all.length;
|
|
107
|
+
else if (e.key === 'ArrowUp') ni = (ci - 1 + all.length) % all.length;
|
|
108
|
+
else if (e.key === 'Home') ni = 0;
|
|
109
|
+
else if (e.key === 'End') ni = all.length - 1;
|
|
110
|
+
all[ni].focus();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
menuitem.click();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Close on outside click — use mousedown to fire before click
|
|
121
|
+
this.#outsideClick = (e) => {
|
|
122
|
+
if (this.#isOpen() && !this.contains(e.target) && !this._shadow.contains(e.target)) {
|
|
123
|
+
this.#closeMenu();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
document.addEventListener('mousedown', this.#outsideClick);
|
|
127
|
+
|
|
128
|
+
// Forward keyboard events from inner interactive elements
|
|
129
|
+
shadow.addEventListener('keydown', (e) => {
|
|
130
|
+
this._emit('cx-keydown', { key: e.key, code: e.code, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, altKey: e.altKey, metaKey: e.metaKey });
|
|
131
|
+
});
|
|
132
|
+
shadow.addEventListener('keyup', (e) => {
|
|
133
|
+
this._emit('cx-keyup', { key: e.key, code: e.code, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, altKey: e.altKey, metaKey: e.metaKey });
|
|
134
|
+
});
|
|
135
|
+
} // end _isInitialized guard
|
|
136
|
+
|
|
137
|
+
super.connectedCallback();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
disconnectedCallback() {
|
|
141
|
+
if (this.#outsideClick) {
|
|
142
|
+
document.removeEventListener('mousedown', this.#outsideClick);
|
|
143
|
+
}
|
|
144
|
+
super.disconnectedCallback();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── State via DOM (single source of truth) ──
|
|
148
|
+
|
|
149
|
+
#isOpen() {
|
|
150
|
+
const trigger = this._shadow.querySelector('[data-floating-trigger]');
|
|
151
|
+
return trigger ? trigger.getAttribute('aria-expanded') === 'true' : false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#openMenu() {
|
|
155
|
+
const panel = this._shadow.querySelector('[data-menu]');
|
|
156
|
+
const trigger = this._shadow.querySelector('[data-floating-trigger]');
|
|
157
|
+
if (!panel || !trigger) return;
|
|
158
|
+
|
|
159
|
+
// Position with fixed coordinates — escapes overflow:auto/scroll clipping.
|
|
160
|
+
this._positionFloatingFixed(trigger, panel, { matchWidth: true });
|
|
161
|
+
|
|
162
|
+
panel.setAttribute('data-open', '');
|
|
163
|
+
panel.classList.remove('hidden');
|
|
164
|
+
panel.style.display = 'block';
|
|
165
|
+
panel.style.pointerEvents = 'auto';
|
|
166
|
+
panel.style.opacity = '1';
|
|
167
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
168
|
+
|
|
169
|
+
// Focus first non-disabled item
|
|
170
|
+
requestAnimationFrame(() => {
|
|
171
|
+
const first = panel.querySelector(
|
|
172
|
+
'[role="menuitem"]:not([aria-disabled]),[role="menuitemcheckbox"]:not([aria-disabled]),[role="menuitemradio"]:not([aria-disabled])'
|
|
173
|
+
);
|
|
174
|
+
if (first) first.focus();
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
#closeMenu() {
|
|
179
|
+
const panel = this._shadow.querySelector('[data-menu]');
|
|
180
|
+
const trigger = this._shadow.querySelector('[data-floating-trigger]');
|
|
181
|
+
if (panel) {
|
|
182
|
+
panel.removeAttribute('data-open');
|
|
183
|
+
panel.classList.add('hidden');
|
|
184
|
+
panel.style.display = '';
|
|
185
|
+
panel.style.pointerEvents = '';
|
|
186
|
+
panel.style.opacity = '';
|
|
187
|
+
this._resetFloatingFixed(panel);
|
|
188
|
+
}
|
|
189
|
+
if (trigger) {
|
|
190
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
191
|
+
trigger.focus();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── Public imperative API ──
|
|
196
|
+
open() { this.#openMenu(); }
|
|
197
|
+
close() { this.#closeMenu(); }
|
|
198
|
+
|
|
199
|
+
_doRender() {
|
|
200
|
+
try {
|
|
201
|
+
const wasOpen = this.#isOpen();
|
|
202
|
+
// Save checked item IDs before re-render
|
|
203
|
+
const checkedIds = this._props._checked_items || '';
|
|
204
|
+
const result = wasmFn(this._props);
|
|
205
|
+
this._injectHtml(result);
|
|
206
|
+
if (wasOpen) this.#openMenu();
|
|
207
|
+
// Restore checkbox/radio checked states after re-render
|
|
208
|
+
if (checkedIds) {
|
|
209
|
+
const ids = checkedIds.split(',').filter(Boolean);
|
|
210
|
+
const shadow = this._shadow;
|
|
211
|
+
shadow.querySelectorAll('[role="menuitemcheckbox"],[role="menuitemradio"]').forEach(el => {
|
|
212
|
+
const id = el.getAttribute('data-item-id') || '';
|
|
213
|
+
if (ids.includes(id)) el.setAttribute('aria-checked', 'true');
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
} catch (e) {
|
|
217
|
+
console.error('[cx-menu]', e);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
customElements.define('cx-menu', CxMenu);
|
|
223
|
+
return CxMenu;
|
|
224
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/message_bubble.rs
|
|
3
|
+
|
|
4
|
+
export interface CxMessageBubbleAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
role?: 'user' | 'assistant';
|
|
7
|
+
variant?: 'filled' | 'ghost';
|
|
8
|
+
shape?: 'sharp' | 'rounded';
|
|
9
|
+
alignment?: 'auto' | 'start' | 'end';
|
|
10
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
11
|
+
content?: string;
|
|
12
|
+
htmlContent?: string;
|
|
13
|
+
senderName?: string;
|
|
14
|
+
timestamp?: string;
|
|
15
|
+
border?: string;
|
|
16
|
+
animated?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare global {
|
|
20
|
+
interface HTMLElementTagNameMap {
|
|
21
|
+
'cx-message-bubble': HTMLElement & CxMessageBubbleAttributes;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/message_bubble.rs
|
|
3
|
+
|
|
4
|
+
export function defineCxMessageBubble(wasmFn, baseClass) {
|
|
5
|
+
class CxMessageBubble extends baseClass {
|
|
6
|
+
static observedAttributes = ['id', 'role', 'variant', 'shape', 'alignment', 'size', 'html-content', 'sender-name', 'timestamp', 'border', 'animated'];
|
|
7
|
+
static _booleanAttrs = new Set(['animated']);
|
|
8
|
+
static _focusable = false;
|
|
9
|
+
static _hostDisplay = 'block';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
connectedCallback() {
|
|
13
|
+
super.connectedCallback();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
_doRender() {
|
|
17
|
+
try {
|
|
18
|
+
this._props.slotted = true;
|
|
19
|
+
const result = wasmFn(this._props);
|
|
20
|
+
this._injectHtml(result);
|
|
21
|
+
} catch (e) {
|
|
22
|
+
console.error('[cx-message-bubble]', e);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
customElements.define('cx-message-bubble', CxMessageBubble);
|
|
28
|
+
return CxMessageBubble;
|
|
29
|
+
}
|