@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,528 @@
|
|
|
1
|
+
// Custom behavior for <cx-date-picker> — calendar panel, date selection, keyboard nav.
|
|
2
|
+
//
|
|
3
|
+
// The Rust component renders:
|
|
4
|
+
// <div part="base" data-date-picker="{id}">
|
|
5
|
+
// <input data-date-picker-input role="combobox" aria-haspopup="dialog">
|
|
6
|
+
// <button data-date-picker-trigger aria-label="Choose date">
|
|
7
|
+
// <button data-date-picker-step-up> / <button data-date-picker-step-down>
|
|
8
|
+
// <div data-floating data-date-picker-panel role="dialog">
|
|
9
|
+
// <button data-date-picker-prev> / <button data-date-picker-next>
|
|
10
|
+
// <div data-date-picker-heading aria-live="polite">
|
|
11
|
+
// <div role="grid" data-date-picker-grid>
|
|
12
|
+
// <button role="gridcell" data-date="YYYY-MM-DD">
|
|
13
|
+
// <button data-date-picker-today>
|
|
14
|
+
//
|
|
15
|
+
// This Custom Element wires up:
|
|
16
|
+
// - Toggle calendar panel via trigger button or input click
|
|
17
|
+
// - Day selection via click on gridcell
|
|
18
|
+
// - Month navigation (prev/next buttons)
|
|
19
|
+
// - Today button shortcut
|
|
20
|
+
// - Day stepping via up/down buttons
|
|
21
|
+
// - Full keyboard nav (arrows ±1/7 days, Page ±month, Home/End, Escape)
|
|
22
|
+
// - Click outside / focus exit to close
|
|
23
|
+
//
|
|
24
|
+
// Source: crates/wasm-api/src/date_picker.rs
|
|
25
|
+
|
|
26
|
+
let _sheet;
|
|
27
|
+
function getSheet() {
|
|
28
|
+
if (!_sheet) {
|
|
29
|
+
_sheet = new CSSStyleSheet();
|
|
30
|
+
_sheet.replaceSync([
|
|
31
|
+
// Calendar panel uses position:fixed (set by JS) to escape scroll container clipping.
|
|
32
|
+
'[data-floating] {',
|
|
33
|
+
' z-index: 50;',
|
|
34
|
+
'}',
|
|
35
|
+
// Hover for day cells
|
|
36
|
+
'button[role="gridcell"]:not([aria-disabled="true"]):hover {',
|
|
37
|
+
' background-color: var(--color-secondary);',
|
|
38
|
+
' cursor: pointer;',
|
|
39
|
+
'}',
|
|
40
|
+
// Focus for day cells
|
|
41
|
+
'button[role="gridcell"]:focus-visible {',
|
|
42
|
+
' outline: 2px solid var(--color-ring);',
|
|
43
|
+
' outline-offset: -2px;',
|
|
44
|
+
' border-radius: 0.25rem;',
|
|
45
|
+
'}',
|
|
46
|
+
].join('\n'));
|
|
47
|
+
}
|
|
48
|
+
return _sheet;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Date helpers ──
|
|
52
|
+
|
|
53
|
+
function todayStr() {
|
|
54
|
+
const d = new Date();
|
|
55
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function addDays(isoDate, n) {
|
|
59
|
+
const d = new Date(isoDate + 'T12:00:00');
|
|
60
|
+
d.setDate(d.getDate() + n);
|
|
61
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function addMonths(isoDate, n) {
|
|
65
|
+
const d = new Date(isoDate + 'T12:00:00');
|
|
66
|
+
d.setMonth(d.getMonth() + n);
|
|
67
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getMonthYear(isoDate) {
|
|
71
|
+
return isoDate.substring(0, 7); // "YYYY-MM"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function defineCxDatePicker(wasmFn, baseClass) {
|
|
75
|
+
class CxDatePicker extends baseClass {
|
|
76
|
+
static observedAttributes = ['id', 'label', 'variant', 'shape', 'size', 'selected', 'min', 'max', 'disabled-dates', 'format', 'first-day', 'placeholder', 'helper-text', 'error', 'disabled', 'required', 'readonly', 'name', 'on-change'];
|
|
77
|
+
static _booleanAttrs = new Set(['disabled', 'required', 'readonly']);
|
|
78
|
+
|
|
79
|
+
#outsideClick = null;
|
|
80
|
+
|
|
81
|
+
connectedCallback() {
|
|
82
|
+
if (!this._isInitialized) {
|
|
83
|
+
this._markInitialized();
|
|
84
|
+
const shadow = this._shadow;
|
|
85
|
+
|
|
86
|
+
const sheet = getSheet();
|
|
87
|
+
if (!shadow.adoptedStyleSheets.includes(sheet)) {
|
|
88
|
+
shadow.adoptedStyleSheets = [...shadow.adoptedStyleSheets, sheet];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── Click handler ──
|
|
92
|
+
shadow.addEventListener('click', (e) => {
|
|
93
|
+
// Calendar trigger button
|
|
94
|
+
if (e.target.closest('[data-date-picker-trigger]')) {
|
|
95
|
+
if (!this._props.disabled && !this._props.readonly) {
|
|
96
|
+
this.#toggle();
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Day cell click
|
|
102
|
+
const cell = e.target.closest('button[role="gridcell"]');
|
|
103
|
+
if (cell && !cell.hasAttribute('aria-disabled')) {
|
|
104
|
+
const date = cell.getAttribute('data-date');
|
|
105
|
+
if (date) this.#selectDate(date);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Previous month
|
|
110
|
+
if (e.target.closest('[data-date-picker-prev]')) {
|
|
111
|
+
this.#navigateMonth(-1);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Next month
|
|
116
|
+
if (e.target.closest('[data-date-picker-next]')) {
|
|
117
|
+
this.#navigateMonth(1);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Today button
|
|
122
|
+
if (e.target.closest('[data-date-picker-today]')) {
|
|
123
|
+
this.#selectDate(todayStr());
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Step up (+1 day)
|
|
128
|
+
if (e.target.closest('[data-date-picker-step-up]')) {
|
|
129
|
+
this.#stepDay(1);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Step down (-1 day)
|
|
134
|
+
if (e.target.closest('[data-date-picker-step-down]')) {
|
|
135
|
+
this.#stepDay(-1);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Input click → open panel
|
|
140
|
+
const input = e.target.closest('[data-date-picker-input]');
|
|
141
|
+
if (input && !this.#isOpen() && !this._props.disabled && !this._props.readonly) {
|
|
142
|
+
this.#open();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// ── Keyboard handler ──
|
|
147
|
+
shadow.addEventListener('keydown', (e) => this.#handleKey(e));
|
|
148
|
+
|
|
149
|
+
// ── Input typing ──
|
|
150
|
+
shadow.addEventListener('input', (e) => {
|
|
151
|
+
if (e.target.matches('[data-date-picker-input]')) {
|
|
152
|
+
this._setFormValue(e.target.value);
|
|
153
|
+
this._emit('cx-input', { value: e.target.value });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ── Click outside → close ──
|
|
158
|
+
this.#outsideClick = (e) => {
|
|
159
|
+
if (this.#isOpen() && !this.contains(e.target) && !this._shadow.contains(e.target)) {
|
|
160
|
+
this.#close();
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
document.addEventListener('mousedown', this.#outsideClick);
|
|
164
|
+
|
|
165
|
+
// ── Focus exit → close ──
|
|
166
|
+
// Must check both light DOM and shadow DOM — Node.contains() doesn't cross shadow boundaries.
|
|
167
|
+
shadow.addEventListener('focusout', () => {
|
|
168
|
+
setTimeout(() => {
|
|
169
|
+
if (this.#isOpen()) {
|
|
170
|
+
const active = shadow.activeElement || document.activeElement;
|
|
171
|
+
if (!this.contains(active) && !this._shadow.contains(active) && active !== this) {
|
|
172
|
+
this.#close();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}, 0);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Forward focus events from inner interactive elements
|
|
179
|
+
shadow.addEventListener('focusin', (e) => {
|
|
180
|
+
this._emit('cx-focus', { relatedTarget: e.relatedTarget });
|
|
181
|
+
});
|
|
182
|
+
shadow.addEventListener('focusout', (e) => {
|
|
183
|
+
this._emit('cx-blur', { relatedTarget: e.relatedTarget });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Forward keyboard events from inner interactive elements
|
|
187
|
+
shadow.addEventListener('keydown', (e) => {
|
|
188
|
+
this._emit('cx-keydown', { key: e.key, code: e.code, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, altKey: e.altKey, metaKey: e.metaKey });
|
|
189
|
+
});
|
|
190
|
+
shadow.addEventListener('keyup', (e) => {
|
|
191
|
+
this._emit('cx-keyup', { key: e.key, code: e.code, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, altKey: e.altKey, metaKey: e.metaKey });
|
|
192
|
+
});
|
|
193
|
+
} // end _isInitialized guard
|
|
194
|
+
super.connectedCallback();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
disconnectedCallback() {
|
|
198
|
+
if (this.#outsideClick) {
|
|
199
|
+
document.removeEventListener('mousedown', this.#outsideClick);
|
|
200
|
+
this.#outsideClick = null;
|
|
201
|
+
}
|
|
202
|
+
super.disconnectedCallback();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ── DOM accessors ──
|
|
206
|
+
|
|
207
|
+
#getInput() {
|
|
208
|
+
return this._shadow.querySelector('[data-date-picker-input]');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
#getPanel() {
|
|
212
|
+
return this._shadow.querySelector('[data-date-picker-panel]');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#getTrigger() {
|
|
216
|
+
return this._shadow.querySelector('[data-date-picker-trigger]');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── Panel state ──
|
|
220
|
+
|
|
221
|
+
#isOpen() {
|
|
222
|
+
const panel = this.#getPanel();
|
|
223
|
+
return panel ? panel.hasAttribute('data-open') : false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#open() {
|
|
227
|
+
const panel = this.#getPanel();
|
|
228
|
+
const input = this.#getInput();
|
|
229
|
+
const trigger = this._shadow.querySelector('button[data-floating-trigger]') || input;
|
|
230
|
+
if (!panel || this.#isOpen()) return;
|
|
231
|
+
|
|
232
|
+
// Position with fixed coordinates — escapes overflow:auto/scroll clipping.
|
|
233
|
+
if (trigger) this._positionFloatingFixed(trigger, panel);
|
|
234
|
+
|
|
235
|
+
panel.setAttribute('data-open', '');
|
|
236
|
+
panel.classList.remove('hidden');
|
|
237
|
+
panel.style.display = 'block';
|
|
238
|
+
panel.style.pointerEvents = 'auto';
|
|
239
|
+
panel.style.opacity = '1';
|
|
240
|
+
|
|
241
|
+
if (input) input.setAttribute('aria-expanded', 'true');
|
|
242
|
+
|
|
243
|
+
// Focus the active gridcell
|
|
244
|
+
requestAnimationFrame(() => {
|
|
245
|
+
const active = panel.querySelector('button[role="gridcell"][tabindex="0"]');
|
|
246
|
+
if (active) active.focus();
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#close() {
|
|
251
|
+
const panel = this.#getPanel();
|
|
252
|
+
const input = this.#getInput();
|
|
253
|
+
if (!panel) return;
|
|
254
|
+
|
|
255
|
+
panel.removeAttribute('data-open');
|
|
256
|
+
panel.classList.add('hidden');
|
|
257
|
+
panel.style.display = '';
|
|
258
|
+
panel.style.pointerEvents = '';
|
|
259
|
+
panel.style.opacity = '';
|
|
260
|
+
this._resetFloatingFixed(panel);
|
|
261
|
+
|
|
262
|
+
if (input) {
|
|
263
|
+
input.setAttribute('aria-expanded', 'false');
|
|
264
|
+
input.focus();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#toggle() {
|
|
269
|
+
if (this.#isOpen()) this.#close();
|
|
270
|
+
else this.#open();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ── Date selection ──
|
|
274
|
+
|
|
275
|
+
#selectDate(isoDate) {
|
|
276
|
+
this._props.selected = isoDate;
|
|
277
|
+
|
|
278
|
+
// Update input value
|
|
279
|
+
const input = this.#getInput();
|
|
280
|
+
if (input) {
|
|
281
|
+
input.value = isoDate;
|
|
282
|
+
this._setFormValue(isoDate);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this._emit('cx-change', { value: isoDate });
|
|
286
|
+
this.#close();
|
|
287
|
+
|
|
288
|
+
// Re-render to show updated calendar state
|
|
289
|
+
this._scheduleRender();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ── Day stepping ──
|
|
293
|
+
|
|
294
|
+
#stepDay(delta) {
|
|
295
|
+
const current = this._props.selected;
|
|
296
|
+
if (!current) return;
|
|
297
|
+
const newDate = addDays(current, delta);
|
|
298
|
+
this._props.selected = newDate;
|
|
299
|
+
|
|
300
|
+
const input = this.#getInput();
|
|
301
|
+
if (input) {
|
|
302
|
+
input.value = newDate;
|
|
303
|
+
this._setFormValue(newDate);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
this._emit('cx-change', { value: newDate });
|
|
307
|
+
this._scheduleRender();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ── Month navigation ──
|
|
311
|
+
|
|
312
|
+
#navigateMonth(delta) {
|
|
313
|
+
// Find current viewing month from heading or selected date
|
|
314
|
+
const heading = this._shadow.querySelector('[data-date-picker-heading]');
|
|
315
|
+
const grid = this._shadow.querySelector('[data-date-picker-grid]');
|
|
316
|
+
if (!grid) return;
|
|
317
|
+
|
|
318
|
+
// Find a date in the current month from the grid
|
|
319
|
+
const firstCell = grid.querySelector('button[role="gridcell"]:not([aria-disabled="true"])');
|
|
320
|
+
if (!firstCell) return;
|
|
321
|
+
|
|
322
|
+
const currentDate = firstCell.getAttribute('data-date');
|
|
323
|
+
if (!currentDate) return;
|
|
324
|
+
|
|
325
|
+
const newMonthDate = addMonths(currentDate, delta);
|
|
326
|
+
const newMonth = getMonthYear(newMonthDate);
|
|
327
|
+
|
|
328
|
+
// Re-render with the new viewing month by updating a prop
|
|
329
|
+
// The WASM will render the correct month when we set selected or use viewing
|
|
330
|
+
this._props._viewing = newMonth;
|
|
331
|
+
this._scheduleRender();
|
|
332
|
+
|
|
333
|
+
// After re-render, focus first active cell
|
|
334
|
+
requestAnimationFrame(() => {
|
|
335
|
+
requestAnimationFrame(() => {
|
|
336
|
+
const panel = this.#getPanel();
|
|
337
|
+
if (panel && this.#isOpen()) {
|
|
338
|
+
// Restore open state after re-render
|
|
339
|
+
panel.setAttribute('data-open', '');
|
|
340
|
+
panel.classList.remove('hidden');
|
|
341
|
+
panel.style.display = 'block';
|
|
342
|
+
panel.style.pointerEvents = 'auto';
|
|
343
|
+
panel.style.opacity = '1';
|
|
344
|
+
|
|
345
|
+
const active = panel.querySelector('button[role="gridcell"][tabindex="0"]');
|
|
346
|
+
if (active) active.focus();
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ── Keyboard navigation ──
|
|
353
|
+
|
|
354
|
+
#handleKey(e) {
|
|
355
|
+
const input = this.#getInput();
|
|
356
|
+
|
|
357
|
+
// Input keyboard
|
|
358
|
+
if (e.target === input || e.target?.matches?.('[data-date-picker-input]')) {
|
|
359
|
+
if (e.key === 'ArrowDown' && !this.#isOpen()) {
|
|
360
|
+
e.preventDefault();
|
|
361
|
+
this.#open();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (e.key === 'Escape' && this.#isOpen()) {
|
|
365
|
+
e.preventDefault();
|
|
366
|
+
this.#close();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (e.key === 'Enter') {
|
|
370
|
+
// Try to parse typed date
|
|
371
|
+
const val = input?.value?.trim();
|
|
372
|
+
if (val && /^\d{4}-\d{2}-\d{2}$/.test(val)) {
|
|
373
|
+
e.preventDefault();
|
|
374
|
+
this.#selectDate(val);
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Grid cell keyboard
|
|
382
|
+
const cell = e.target?.closest?.('button[role="gridcell"]');
|
|
383
|
+
if (!cell) {
|
|
384
|
+
// Escape from anywhere in panel
|
|
385
|
+
if (e.key === 'Escape' && this.#isOpen()) {
|
|
386
|
+
e.preventDefault();
|
|
387
|
+
this.#close();
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const date = cell.getAttribute('data-date');
|
|
393
|
+
if (!date) return;
|
|
394
|
+
|
|
395
|
+
let targetDate = null;
|
|
396
|
+
|
|
397
|
+
switch (e.key) {
|
|
398
|
+
case 'ArrowRight':
|
|
399
|
+
e.preventDefault();
|
|
400
|
+
targetDate = addDays(date, 1);
|
|
401
|
+
break;
|
|
402
|
+
case 'ArrowLeft':
|
|
403
|
+
e.preventDefault();
|
|
404
|
+
targetDate = addDays(date, -1);
|
|
405
|
+
break;
|
|
406
|
+
case 'ArrowDown':
|
|
407
|
+
e.preventDefault();
|
|
408
|
+
targetDate = addDays(date, 7);
|
|
409
|
+
break;
|
|
410
|
+
case 'ArrowUp':
|
|
411
|
+
e.preventDefault();
|
|
412
|
+
targetDate = addDays(date, -7);
|
|
413
|
+
break;
|
|
414
|
+
case 'PageDown':
|
|
415
|
+
e.preventDefault();
|
|
416
|
+
targetDate = addMonths(date, e.shiftKey ? 12 : 1);
|
|
417
|
+
break;
|
|
418
|
+
case 'PageUp':
|
|
419
|
+
e.preventDefault();
|
|
420
|
+
targetDate = addMonths(date, e.shiftKey ? -12 : -1);
|
|
421
|
+
break;
|
|
422
|
+
case 'Home':
|
|
423
|
+
e.preventDefault();
|
|
424
|
+
targetDate = date.substring(0, 8) + '01';
|
|
425
|
+
break;
|
|
426
|
+
case 'End': {
|
|
427
|
+
e.preventDefault();
|
|
428
|
+
const d = new Date(date + 'T12:00:00');
|
|
429
|
+
d.setMonth(d.getMonth() + 1, 0);
|
|
430
|
+
targetDate = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
case 'Enter':
|
|
434
|
+
case ' ':
|
|
435
|
+
e.preventDefault();
|
|
436
|
+
if (!cell.hasAttribute('aria-disabled')) {
|
|
437
|
+
this.#selectDate(date);
|
|
438
|
+
}
|
|
439
|
+
return;
|
|
440
|
+
case 'Escape':
|
|
441
|
+
e.preventDefault();
|
|
442
|
+
this.#close();
|
|
443
|
+
return;
|
|
444
|
+
case 'Tab':
|
|
445
|
+
this.#close();
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (targetDate) {
|
|
450
|
+
// Try to find the target cell in current grid
|
|
451
|
+
const target = this._shadow.querySelector(`button[data-date="${targetDate}"]`);
|
|
452
|
+
if (target && !target.hasAttribute('aria-disabled')) {
|
|
453
|
+
// Same month — just move focus
|
|
454
|
+
this.#setGridFocus(target);
|
|
455
|
+
} else {
|
|
456
|
+
// Different month — navigate and focus after re-render
|
|
457
|
+
const targetMonth = getMonthYear(targetDate);
|
|
458
|
+
this._props._viewing = targetMonth;
|
|
459
|
+
this._props._focus_date = targetDate;
|
|
460
|
+
this._scheduleRender();
|
|
461
|
+
|
|
462
|
+
requestAnimationFrame(() => {
|
|
463
|
+
requestAnimationFrame(() => {
|
|
464
|
+
const panel = this.#getPanel();
|
|
465
|
+
if (panel) {
|
|
466
|
+
panel.setAttribute('data-open', '');
|
|
467
|
+
panel.classList.remove('hidden');
|
|
468
|
+
panel.style.display = 'block';
|
|
469
|
+
panel.style.pointerEvents = 'auto';
|
|
470
|
+
panel.style.opacity = '1';
|
|
471
|
+
|
|
472
|
+
const tc = this._shadow.querySelector(`button[data-date="${targetDate}"]`);
|
|
473
|
+
if (tc) this.#setGridFocus(tc);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
#setGridFocus(cell) {
|
|
482
|
+
// Reset all cells to tabindex -1
|
|
483
|
+
this._shadow.querySelectorAll('button[role="gridcell"]').forEach(c => {
|
|
484
|
+
c.setAttribute('tabindex', '-1');
|
|
485
|
+
});
|
|
486
|
+
cell.setAttribute('tabindex', '0');
|
|
487
|
+
cell.focus();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// ── Public imperative API ──
|
|
491
|
+
open() { this.#open(); }
|
|
492
|
+
close() { this.#close(); }
|
|
493
|
+
focus() { const el = this._shadow.querySelector('button[data-floating-trigger]'); if (el) el.focus(); else super.focus(); }
|
|
494
|
+
|
|
495
|
+
// ── Render ──
|
|
496
|
+
|
|
497
|
+
_doRender() {
|
|
498
|
+
try {
|
|
499
|
+
const wasOpen = this.#isOpen();
|
|
500
|
+
|
|
501
|
+
const result = wasmFn(this._props);
|
|
502
|
+
this._injectHtml(result);
|
|
503
|
+
|
|
504
|
+
const sheet = getSheet();
|
|
505
|
+
if (!this._shadow.adoptedStyleSheets.includes(sheet)) {
|
|
506
|
+
this._shadow.adoptedStyleSheets = [...this._shadow.adoptedStyleSheets, sheet];
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Sync form value
|
|
510
|
+
const input = this.#getInput();
|
|
511
|
+
if (input) {
|
|
512
|
+
if (this._props.selected) input.value = this._props.selected;
|
|
513
|
+
this._setFormValue(input.value || '');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Restore open state
|
|
517
|
+
if (wasOpen) {
|
|
518
|
+
this.#open();
|
|
519
|
+
}
|
|
520
|
+
} catch (e) {
|
|
521
|
+
console.error('[cx-date-picker]', e);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
customElements.define('cx-date-picker', CxDatePicker);
|
|
527
|
+
return CxDatePicker;
|
|
528
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-elements.mjs — DO NOT EDIT
|
|
2
|
+
// Source: crates/wasm-api/src/dialog.rs
|
|
3
|
+
|
|
4
|
+
export interface CxDialogAttributes {
|
|
5
|
+
id?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
variant?: 'standard' | 'alert';
|
|
8
|
+
description?: string;
|
|
9
|
+
body?: string;
|
|
10
|
+
footer?: string;
|
|
11
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
12
|
+
closeButton?: string;
|
|
13
|
+
drawer?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare global {
|
|
17
|
+
interface HTMLElementTagNameMap {
|
|
18
|
+
'cx-dialog': HTMLElement & CxDialogAttributes;
|
|
19
|
+
}
|
|
20
|
+
}
|