@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,18 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/message_group.rs
|
|
3
|
+
|
|
4
|
+
export interface CxMessageGroupAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
role?: 'user' | 'assistant';
|
|
7
|
+
alignment?: 'auto' | 'start' | 'end';
|
|
8
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
9
|
+
senderName?: string;
|
|
10
|
+
animated?: boolean;
|
|
11
|
+
parts?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
interface HTMLElementTagNameMap {
|
|
16
|
+
'cx-message-group': HTMLElement & CxMessageGroupAttributes;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/message_group.rs
|
|
3
|
+
|
|
4
|
+
export function defineCxMessageGroup(wasmFn, baseClass) {
|
|
5
|
+
class CxMessageGroup extends baseClass {
|
|
6
|
+
static observedAttributes = ['id', 'role', 'alignment', 'size', 'sender-name', 'animated', 'parts'];
|
|
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
|
+
const result = wasmFn(this._props);
|
|
19
|
+
this._injectHtml(result);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error('[cx-message-group]', e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
customElements.define('cx-message-group', CxMessageGroup);
|
|
27
|
+
return CxMessageGroup;
|
|
28
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// CxMessagePart — typed content blocks with streaming markdown support.
|
|
2
|
+
// Source: crates/wasm-api/src/message_part.rs + custom streaming behavior.
|
|
3
|
+
|
|
4
|
+
export interface CxMessagePartAttributes {
|
|
5
|
+
/** Enable streaming mode. When present, WASM rendering is paused. */
|
|
6
|
+
stream?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CxMessagePartElement extends HTMLElement, CxMessagePartAttributes {
|
|
10
|
+
/**
|
|
11
|
+
* Initialize the streaming markdown parser.
|
|
12
|
+
* Clears content and shows a blinking cursor.
|
|
13
|
+
*/
|
|
14
|
+
startStream(): Promise<void>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Feed markdown tokens into the streaming parser.
|
|
18
|
+
* Tokens are rAF-batched for performance.
|
|
19
|
+
*
|
|
20
|
+
* @param text - Markdown token(s) to append
|
|
21
|
+
*/
|
|
22
|
+
appendTokens(text: string): void;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* End the stream. Flushes remaining tokens, removes cursor,
|
|
26
|
+
* then runs a final WASM sanitization pass for XSS safety.
|
|
27
|
+
*/
|
|
28
|
+
endStream(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare global {
|
|
32
|
+
interface HTMLElementTagNameMap {
|
|
33
|
+
'cx-message-part': CxMessagePartElement;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// CxMessagePart — Custom Element for typed message content blocks.
|
|
2
|
+
//
|
|
3
|
+
// Extends the auto-generated skeleton with streaming markdown support.
|
|
4
|
+
// When the `stream` attribute is present, the element exposes imperative
|
|
5
|
+
// methods for feeding tokens from an SSE/WebSocket source.
|
|
6
|
+
//
|
|
7
|
+
// Static rendering: set attributes as usual (WASM renders full content).
|
|
8
|
+
// Streaming: call startStream() → appendTokens(text) → endStream().
|
|
9
|
+
|
|
10
|
+
export function defineCxMessagePart(wasmFn, baseClass) {
|
|
11
|
+
class CxMessagePart extends baseClass {
|
|
12
|
+
static observedAttributes = ['stream'];
|
|
13
|
+
static _booleanAttrs = new Set(['stream']);
|
|
14
|
+
static _focusable = false;
|
|
15
|
+
|
|
16
|
+
#streamState = null;
|
|
17
|
+
|
|
18
|
+
connectedCallback() {
|
|
19
|
+
super.connectedCallback();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_doRender() {
|
|
23
|
+
// Skip WASM render while streaming — the streaming parser owns the DOM.
|
|
24
|
+
if (this.#streamState) return;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const result = wasmFn(this._props);
|
|
28
|
+
this._injectHtml(result);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error('[cx-message-part]', e);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Streaming API ───
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Initialize the streaming markdown parser.
|
|
38
|
+
* Clears existing content and shows a blinking cursor.
|
|
39
|
+
*/
|
|
40
|
+
async startStream() {
|
|
41
|
+
const smd = await import('../streaming-markdown.js');
|
|
42
|
+
const shadow = this._shadow;
|
|
43
|
+
|
|
44
|
+
// Create a prose container inside shadow DOM.
|
|
45
|
+
const container = document.createElement('div');
|
|
46
|
+
container.className = 'cx-prose';
|
|
47
|
+
shadow.innerHTML = '';
|
|
48
|
+
shadow.appendChild(container);
|
|
49
|
+
|
|
50
|
+
// Create blinking cursor.
|
|
51
|
+
const cursor = document.createElement('span');
|
|
52
|
+
cursor.className = 'cx-stream-cursor';
|
|
53
|
+
cursor.setAttribute('aria-hidden', 'true');
|
|
54
|
+
shadow.appendChild(cursor);
|
|
55
|
+
|
|
56
|
+
// Initialize streaming parser.
|
|
57
|
+
const renderer = smd.default_renderer(container);
|
|
58
|
+
const parser = smd.parser(renderer);
|
|
59
|
+
|
|
60
|
+
this.#streamState = {
|
|
61
|
+
parser,
|
|
62
|
+
smd,
|
|
63
|
+
buffer: [],
|
|
64
|
+
accumulated: '',
|
|
65
|
+
rafId: null,
|
|
66
|
+
container,
|
|
67
|
+
cursor,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
this.setAttribute('aria-busy', 'true');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Feed markdown tokens into the streaming parser.
|
|
75
|
+
* Tokens are rAF-batched for optimal rendering performance.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} text - Markdown token(s) to append
|
|
78
|
+
*/
|
|
79
|
+
appendTokens(text) {
|
|
80
|
+
const s = this.#streamState;
|
|
81
|
+
if (!s) return;
|
|
82
|
+
|
|
83
|
+
s.buffer.push(text);
|
|
84
|
+
|
|
85
|
+
if (!s.rafId) {
|
|
86
|
+
s.rafId = requestAnimationFrame(() => {
|
|
87
|
+
if (!this.#streamState) return;
|
|
88
|
+
const chunk = s.buffer.join('');
|
|
89
|
+
s.buffer.length = 0;
|
|
90
|
+
s.accumulated += chunk;
|
|
91
|
+
s.smd.parser_write(s.parser, chunk);
|
|
92
|
+
s.rafId = null;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* End the stream. Flushes remaining tokens, removes cursor,
|
|
99
|
+
* then runs a final WASM sanitization pass for XSS safety.
|
|
100
|
+
*/
|
|
101
|
+
async endStream() {
|
|
102
|
+
const s = this.#streamState;
|
|
103
|
+
if (!s) return;
|
|
104
|
+
|
|
105
|
+
// Flush remaining tokens.
|
|
106
|
+
if (s.rafId) {
|
|
107
|
+
cancelAnimationFrame(s.rafId);
|
|
108
|
+
s.rafId = null;
|
|
109
|
+
}
|
|
110
|
+
if (s.buffer.length > 0) {
|
|
111
|
+
const chunk = s.buffer.join('');
|
|
112
|
+
s.buffer.length = 0;
|
|
113
|
+
s.accumulated += chunk;
|
|
114
|
+
s.smd.parser_write(s.parser, chunk);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// End streaming parser.
|
|
118
|
+
s.smd.parser_end(s.parser);
|
|
119
|
+
|
|
120
|
+
// Remove cursor.
|
|
121
|
+
if (s.cursor && s.cursor.parentNode) {
|
|
122
|
+
s.cursor.remove();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Final sanitization pass through WASM.
|
|
126
|
+
// The streaming parser renders to DOM directly — this re-renders
|
|
127
|
+
// through our compile-safe XSS filtering for defense-in-depth.
|
|
128
|
+
try {
|
|
129
|
+
const wasm = (await import('../../wasm/wasm_api.js'));
|
|
130
|
+
if (wasm.cx_render_markdown && s.accumulated) {
|
|
131
|
+
const safeHtml = wasm.cx_render_markdown(s.accumulated);
|
|
132
|
+
s.container.innerHTML = '';
|
|
133
|
+
s.container.innerHTML = safeHtml.replace(/^<div class="cx-prose">/, '').replace(/<\/div>$/, '');
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
// WASM sanitization not available — keep streaming output.
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.removeAttribute('aria-busy');
|
|
140
|
+
|
|
141
|
+
// Dispatch completion event.
|
|
142
|
+
this.dispatchEvent(new CustomEvent('cx-stream-end', {
|
|
143
|
+
bubbles: true,
|
|
144
|
+
composed: true,
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
this.#streamState = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
customElements.define('cx-message-part', CxMessagePart);
|
|
152
|
+
return CxMessagePart;
|
|
153
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/pagination.rs
|
|
3
|
+
|
|
4
|
+
export interface CxPaginationAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
currentPage: number;
|
|
7
|
+
pageSize: number;
|
|
8
|
+
totalItems: number;
|
|
9
|
+
label?: string;
|
|
10
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
11
|
+
showInfo?: boolean;
|
|
12
|
+
showPrevNext?: boolean;
|
|
13
|
+
prevLabel?: string;
|
|
14
|
+
nextLabel?: string;
|
|
15
|
+
pageSizeOptions?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare global {
|
|
19
|
+
interface HTMLElementTagNameMap {
|
|
20
|
+
'cx-pagination': HTMLElement & CxPaginationAttributes;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/pagination.rs
|
|
3
|
+
|
|
4
|
+
export function defineCxPagination(wasmFn, baseClass) {
|
|
5
|
+
class CxPagination extends baseClass {
|
|
6
|
+
static observedAttributes = ['id', 'current-page', 'page-size', 'total-items', 'label', 'size', 'show-info', 'show-prev-next', 'prev-label', 'next-label', 'page-size-options'];
|
|
7
|
+
static _booleanAttrs = new Set(['show-info', 'show-prev-next']);
|
|
8
|
+
static _numericAttrs = new Set(['current-page', 'page-size', 'total-items']);
|
|
9
|
+
static _hostDisplay = 'block';
|
|
10
|
+
|
|
11
|
+
set current_page(v) { this._setProp('current_page', v); }
|
|
12
|
+
get current_page() { return this._props.current_page; }
|
|
13
|
+
|
|
14
|
+
set page_size(v) { this._setProp('page_size', v); }
|
|
15
|
+
get page_size() { return this._props.page_size; }
|
|
16
|
+
|
|
17
|
+
set total_items(v) { this._setProp('total_items', v); }
|
|
18
|
+
get total_items() { return this._props.total_items; }
|
|
19
|
+
|
|
20
|
+
connectedCallback() {
|
|
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-pagination]', e);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
customElements.define('cx-pagination', CxPagination);
|
|
35
|
+
return CxPagination;
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/popover.rs
|
|
3
|
+
|
|
4
|
+
export interface CxPopoverAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
triggerLabel: string;
|
|
7
|
+
content?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
width?: 'sm' | 'md' | 'lg' | 'xl' | 'auto';
|
|
11
|
+
placement?: 'top' | 'bottom';
|
|
12
|
+
align?: 'start' | 'center' | 'end';
|
|
13
|
+
arrow?: boolean;
|
|
14
|
+
closeButton?: boolean;
|
|
15
|
+
variant?: 'outline' | 'filled' | 'ghost';
|
|
16
|
+
shape?: 'sharp' | 'rounded' | 'pill';
|
|
17
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
18
|
+
icon?: string;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare global {
|
|
23
|
+
interface HTMLElementTagNameMap {
|
|
24
|
+
'cx-popover': HTMLElement & CxPopoverAttributes;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Custom behavior for <cx-popover> — trigger toggle, close on outside/escape.
|
|
2
|
+
//
|
|
3
|
+
// The Rust component renders:
|
|
4
|
+
// <button data-floating-trigger> — the trigger button
|
|
5
|
+
// <div data-floating data-popover role="dialog"> — the floating panel
|
|
6
|
+
//
|
|
7
|
+
// This Custom Element wires up:
|
|
8
|
+
// - Click trigger to toggle panel visibility
|
|
9
|
+
// - Click close button to dismiss
|
|
10
|
+
// - Click outside to close
|
|
11
|
+
// - Escape key to close
|
|
12
|
+
// - Focus management on open/close
|
|
13
|
+
//
|
|
14
|
+
// Floating panel uses position:fixed (viewport coordinates) to escape
|
|
15
|
+
// scroll container clipping — e.g. when inside <cx-scrollbar>.
|
|
16
|
+
//
|
|
17
|
+
// Source: crates/wasm-api/src/popover.rs
|
|
18
|
+
|
|
19
|
+
let _sheet;
|
|
20
|
+
function getSheet() {
|
|
21
|
+
if (!_sheet) {
|
|
22
|
+
_sheet = new CSSStyleSheet();
|
|
23
|
+
_sheet.replaceSync([
|
|
24
|
+
':host { display: inline-flex; }',
|
|
25
|
+
// Panel hidden by default — #open() sets position:fixed + coordinates.
|
|
26
|
+
// The sheet provides base z-index and hidden state only.
|
|
27
|
+
'[data-floating] {',
|
|
28
|
+
' z-index: 50;',
|
|
29
|
+
'}',
|
|
30
|
+
].join('\n'));
|
|
31
|
+
}
|
|
32
|
+
return _sheet;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function defineCxPopover(wasmFn, baseClass) {
|
|
36
|
+
class CxPopover extends baseClass {
|
|
37
|
+
static observedAttributes = ['id', 'trigger-label', 'title', 'description', 'width', 'placement', 'align', 'arrow', 'close-button', 'variant', 'shape', 'size', 'icon', 'disabled'];
|
|
38
|
+
static _booleanAttrs = new Set(['arrow', 'close-button', 'disabled']);
|
|
39
|
+
|
|
40
|
+
#outsideClick = null;
|
|
41
|
+
|
|
42
|
+
connectedCallback() {
|
|
43
|
+
if (!this._isInitialized) {
|
|
44
|
+
this._markInitialized();
|
|
45
|
+
const shadow = this._shadow;
|
|
46
|
+
|
|
47
|
+
const sheet = getSheet();
|
|
48
|
+
if (!shadow.adoptedStyleSheets.includes(sheet)) {
|
|
49
|
+
shadow.adoptedStyleSheets = [...shadow.adoptedStyleSheets, sheet];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── Click handler ──
|
|
53
|
+
shadow.addEventListener('click', (e) => {
|
|
54
|
+
// Close button
|
|
55
|
+
if (e.target.closest('[data-popover-close]')) {
|
|
56
|
+
this.#close();
|
|
57
|
+
this.#getTrigger()?.focus();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Trigger click → toggle
|
|
62
|
+
const trigger = this.#getTrigger();
|
|
63
|
+
if (trigger && trigger.contains(e.target) && !this._props.disabled) {
|
|
64
|
+
this.#toggle();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ── Keyboard handler ──
|
|
69
|
+
shadow.addEventListener('keydown', (e) => {
|
|
70
|
+
if (e.key === 'Escape' && this.#isOpen()) {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
this.#close();
|
|
73
|
+
this.#getTrigger()?.focus();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ── Click outside → close ──
|
|
78
|
+
this.#outsideClick = (e) => {
|
|
79
|
+
if (this.#isOpen() && !this.contains(e.target) && !this._shadow.contains(e.target)) {
|
|
80
|
+
this.#close();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
document.addEventListener('mousedown', this.#outsideClick);
|
|
84
|
+
|
|
85
|
+
// ── Focus exit → close ──
|
|
86
|
+
// Must check both light DOM and shadow DOM — Node.contains() doesn't cross shadow boundaries.
|
|
87
|
+
shadow.addEventListener('focusout', () => {
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
if (this.#isOpen()) {
|
|
90
|
+
const active = this._shadow.activeElement || document.activeElement;
|
|
91
|
+
if (!this.contains(active) && !this._shadow.contains(active) && active !== this) {
|
|
92
|
+
this.#close();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}, 0);
|
|
96
|
+
});
|
|
97
|
+
} // end _isInitialized guard
|
|
98
|
+
super.connectedCallback();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
disconnectedCallback() {
|
|
102
|
+
if (this.#outsideClick) {
|
|
103
|
+
document.removeEventListener('mousedown', this.#outsideClick);
|
|
104
|
+
this.#outsideClick = null;
|
|
105
|
+
}
|
|
106
|
+
super.disconnectedCallback();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#getTrigger() {
|
|
110
|
+
return this._shadow.querySelector('button[data-floating-trigger]');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#getPanel() {
|
|
114
|
+
return this._shadow.querySelector('[data-popover]');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#isOpen() {
|
|
118
|
+
const panel = this.#getPanel();
|
|
119
|
+
return panel ? panel.hasAttribute('data-open') : false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#open() {
|
|
123
|
+
const trigger = this.#getTrigger();
|
|
124
|
+
const panel = this.#getPanel();
|
|
125
|
+
if (!trigger || !panel || this.#isOpen()) return;
|
|
126
|
+
|
|
127
|
+
// Position with fixed coordinates — escapes overflow:auto/scroll clipping.
|
|
128
|
+
this._positionFloatingFixed(trigger, panel);
|
|
129
|
+
|
|
130
|
+
panel.setAttribute('data-open', '');
|
|
131
|
+
panel.classList.remove('hidden');
|
|
132
|
+
panel.style.display = 'block';
|
|
133
|
+
panel.style.pointerEvents = 'auto';
|
|
134
|
+
panel.style.opacity = '1';
|
|
135
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
136
|
+
|
|
137
|
+
// Move focus to first focusable element in panel, or panel itself
|
|
138
|
+
requestAnimationFrame(() => {
|
|
139
|
+
const focusable = panel.querySelector(
|
|
140
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
141
|
+
);
|
|
142
|
+
if (focusable) focusable.focus();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#close() {
|
|
147
|
+
const trigger = this.#getTrigger();
|
|
148
|
+
const panel = this.#getPanel();
|
|
149
|
+
if (!trigger || !panel) return;
|
|
150
|
+
|
|
151
|
+
panel.removeAttribute('data-open');
|
|
152
|
+
panel.classList.add('hidden');
|
|
153
|
+
panel.style.display = '';
|
|
154
|
+
panel.style.pointerEvents = '';
|
|
155
|
+
panel.style.opacity = '';
|
|
156
|
+
this._resetFloatingFixed(panel);
|
|
157
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#toggle() {
|
|
161
|
+
if (this.#isOpen()) this.#close();
|
|
162
|
+
else this.#open();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Public imperative API ──
|
|
166
|
+
open() { this.#open(); }
|
|
167
|
+
close() { this.#close(); }
|
|
168
|
+
|
|
169
|
+
_doRender() {
|
|
170
|
+
try {
|
|
171
|
+
const wasOpen = this.#isOpen();
|
|
172
|
+
|
|
173
|
+
this._props.slotted = true;
|
|
174
|
+
const result = wasmFn(this._props);
|
|
175
|
+
this._injectHtml(result);
|
|
176
|
+
|
|
177
|
+
const sheet = getSheet();
|
|
178
|
+
if (!this._shadow.adoptedStyleSheets.includes(sheet)) {
|
|
179
|
+
this._shadow.adoptedStyleSheets = [...this._shadow.adoptedStyleSheets, sheet];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (wasOpen) this.#open();
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.error('[cx-popover]', e);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
customElements.define('cx-popover', CxPopover);
|
|
190
|
+
return CxPopover;
|
|
191
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/profile_menu.rs
|
|
3
|
+
|
|
4
|
+
export interface CxProfileMenuAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
label: string;
|
|
7
|
+
image?: string;
|
|
8
|
+
initials?: string;
|
|
9
|
+
shape?: 'circle' | 'rounded';
|
|
10
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
11
|
+
width?: 'auto' | 'sm' | 'md' | 'lg';
|
|
12
|
+
entries?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare global {
|
|
17
|
+
interface HTMLElementTagNameMap {
|
|
18
|
+
'cx-profile-menu': HTMLElement & CxProfileMenuAttributes;
|
|
19
|
+
}
|
|
20
|
+
}
|