@chromvoid/uikit 0.1.0
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/LICENSE +8 -0
- package/README.md +96 -0
- package/dist/components/cv-accordion-item.d.ts +69 -0
- package/dist/components/cv-accordion-item.js +176 -0
- package/dist/components/cv-accordion.d.ts +79 -0
- package/dist/components/cv-accordion.js +310 -0
- package/dist/components/cv-alert-dialog.d.ts +86 -0
- package/dist/components/cv-alert-dialog.js +393 -0
- package/dist/components/cv-alert.d.ts +48 -0
- package/dist/components/cv-alert.js +156 -0
- package/dist/components/cv-badge.d.ts +56 -0
- package/dist/components/cv-badge.js +280 -0
- package/dist/components/cv-breadcrumb-item.d.ts +35 -0
- package/dist/components/cv-breadcrumb-item.js +64 -0
- package/dist/components/cv-breadcrumb.d.ts +39 -0
- package/dist/components/cv-breadcrumb.js +160 -0
- package/dist/components/cv-button.d.ts +83 -0
- package/dist/components/cv-button.js +541 -0
- package/dist/components/cv-callout.d.ts +32 -0
- package/dist/components/cv-callout.js +221 -0
- package/dist/components/cv-card.d.ts +48 -0
- package/dist/components/cv-card.js +269 -0
- package/dist/components/cv-carousel-slide.d.ts +25 -0
- package/dist/components/cv-carousel-slide.js +51 -0
- package/dist/components/cv-carousel.d.ts +96 -0
- package/dist/components/cv-carousel.js +457 -0
- package/dist/components/cv-checkbox.d.ts +84 -0
- package/dist/components/cv-checkbox.js +274 -0
- package/dist/components/cv-combobox-group.d.ts +15 -0
- package/dist/components/cv-combobox-group.js +34 -0
- package/dist/components/cv-combobox-option.d.ts +30 -0
- package/dist/components/cv-combobox-option.js +66 -0
- package/dist/components/cv-combobox.d.ts +135 -0
- package/dist/components/cv-combobox.js +834 -0
- package/dist/components/cv-command-item.d.ts +30 -0
- package/dist/components/cv-command-item.js +68 -0
- package/dist/components/cv-command-palette.d.ts +105 -0
- package/dist/components/cv-command-palette.js +578 -0
- package/dist/components/cv-context-menu.d.ts +98 -0
- package/dist/components/cv-context-menu.js +515 -0
- package/dist/components/cv-copy-button.d.ts +61 -0
- package/dist/components/cv-copy-button.js +318 -0
- package/dist/components/cv-date-picker.d.ts +161 -0
- package/dist/components/cv-date-picker.js +803 -0
- package/dist/components/cv-dialog.d.ts +89 -0
- package/dist/components/cv-dialog.js +459 -0
- package/dist/components/cv-disclosure.d.ts +57 -0
- package/dist/components/cv-disclosure.js +241 -0
- package/dist/components/cv-drawer.d.ts +102 -0
- package/dist/components/cv-drawer.js +595 -0
- package/dist/components/cv-feed-article.d.ts +26 -0
- package/dist/components/cv-feed-article.js +52 -0
- package/dist/components/cv-feed.d.ts +62 -0
- package/dist/components/cv-feed.js +310 -0
- package/dist/components/cv-grid-cell.d.ts +30 -0
- package/dist/components/cv-grid-cell.js +57 -0
- package/dist/components/cv-grid-column.d.ts +30 -0
- package/dist/components/cv-grid-column.js +43 -0
- package/dist/components/cv-grid-row.d.ts +30 -0
- package/dist/components/cv-grid-row.js +42 -0
- package/dist/components/cv-grid.d.ts +119 -0
- package/dist/components/cv-grid.js +567 -0
- package/dist/components/cv-icon.d.ts +57 -0
- package/dist/components/cv-icon.js +352 -0
- package/dist/components/cv-input.d.ts +127 -0
- package/dist/components/cv-input.js +482 -0
- package/dist/components/cv-landmark.d.ts +32 -0
- package/dist/components/cv-landmark.js +62 -0
- package/dist/components/cv-link.d.ts +22 -0
- package/dist/components/cv-link.js +99 -0
- package/dist/components/cv-listbox-group.d.ts +15 -0
- package/dist/components/cv-listbox-group.js +42 -0
- package/dist/components/cv-listbox.d.ts +81 -0
- package/dist/components/cv-listbox.js +388 -0
- package/dist/components/cv-menu-button.d.ts +118 -0
- package/dist/components/cv-menu-button.js +822 -0
- package/dist/components/cv-menu-group.d.ts +20 -0
- package/dist/components/cv-menu-group.js +48 -0
- package/dist/components/cv-menu-item.d.ts +52 -0
- package/dist/components/cv-menu-item.js +105 -0
- package/dist/components/cv-menu.d.ts +62 -0
- package/dist/components/cv-menu.js +414 -0
- package/dist/components/cv-meter.d.ts +66 -0
- package/dist/components/cv-meter.js +154 -0
- package/dist/components/cv-number.d.ts +139 -0
- package/dist/components/cv-number.js +553 -0
- package/dist/components/cv-option.d.ts +30 -0
- package/dist/components/cv-option.js +84 -0
- package/dist/components/cv-popover.d.ts +87 -0
- package/dist/components/cv-popover.js +373 -0
- package/dist/components/cv-progress-ring.d.ts +45 -0
- package/dist/components/cv-progress-ring.js +169 -0
- package/dist/components/cv-progress.d.ts +45 -0
- package/dist/components/cv-progress.js +148 -0
- package/dist/components/cv-radio-group.d.ts +79 -0
- package/dist/components/cv-radio-group.js +398 -0
- package/dist/components/cv-radio.d.ts +36 -0
- package/dist/components/cv-radio.js +123 -0
- package/dist/components/cv-select-group.d.ts +15 -0
- package/dist/components/cv-select-group.js +44 -0
- package/dist/components/cv-select-option.d.ts +30 -0
- package/dist/components/cv-select-option.js +66 -0
- package/dist/components/cv-select.d.ts +128 -0
- package/dist/components/cv-select.js +666 -0
- package/dist/components/cv-sidebar-item.d.ts +26 -0
- package/dist/components/cv-sidebar-item.js +142 -0
- package/dist/components/cv-sidebar.d.ts +171 -0
- package/dist/components/cv-sidebar.js +767 -0
- package/dist/components/cv-slider-multi-thumb.d.ts +73 -0
- package/dist/components/cv-slider-multi-thumb.js +374 -0
- package/dist/components/cv-slider.d.ts +84 -0
- package/dist/components/cv-slider.js +328 -0
- package/dist/components/cv-spinbutton.d.ts +121 -0
- package/dist/components/cv-spinbutton.js +486 -0
- package/dist/components/cv-spinner.d.ts +18 -0
- package/dist/components/cv-spinner.js +95 -0
- package/dist/components/cv-switch.d.ts +81 -0
- package/dist/components/cv-switch.js +285 -0
- package/dist/components/cv-tab-panel.d.ts +20 -0
- package/dist/components/cv-tab-panel.js +37 -0
- package/dist/components/cv-tab.d.ts +40 -0
- package/dist/components/cv-tab.js +132 -0
- package/dist/components/cv-table-cell.d.ts +31 -0
- package/dist/components/cv-table-cell.js +49 -0
- package/dist/components/cv-table-column.d.ts +37 -0
- package/dist/components/cv-table-column.js +63 -0
- package/dist/components/cv-table-row.d.ts +30 -0
- package/dist/components/cv-table-row.js +45 -0
- package/dist/components/cv-table.d.ts +147 -0
- package/dist/components/cv-table.js +607 -0
- package/dist/components/cv-tabs.d.ts +70 -0
- package/dist/components/cv-tabs.js +524 -0
- package/dist/components/cv-textarea.d.ts +108 -0
- package/dist/components/cv-textarea.js +328 -0
- package/dist/components/cv-toast-region.d.ts +39 -0
- package/dist/components/cv-toast-region.js +162 -0
- package/dist/components/cv-toast.d.ts +67 -0
- package/dist/components/cv-toast.js +315 -0
- package/dist/components/cv-toolbar-item.d.ts +25 -0
- package/dist/components/cv-toolbar-item.js +72 -0
- package/dist/components/cv-toolbar-separator.d.ts +25 -0
- package/dist/components/cv-toolbar-separator.js +45 -0
- package/dist/components/cv-toolbar.d.ts +63 -0
- package/dist/components/cv-toolbar.js +295 -0
- package/dist/components/cv-tooltip.d.ts +83 -0
- package/dist/components/cv-tooltip.js +455 -0
- package/dist/components/cv-treegrid-cell.d.ts +30 -0
- package/dist/components/cv-treegrid-cell.js +57 -0
- package/dist/components/cv-treegrid-column.d.ts +37 -0
- package/dist/components/cv-treegrid-column.js +53 -0
- package/dist/components/cv-treegrid-row.d.ts +55 -0
- package/dist/components/cv-treegrid-row.js +90 -0
- package/dist/components/cv-treegrid.d.ts +96 -0
- package/dist/components/cv-treegrid.js +632 -0
- package/dist/components/cv-treeitem.d.ts +58 -0
- package/dist/components/cv-treeitem.js +144 -0
- package/dist/components/cv-treeview.d.ts +70 -0
- package/dist/components/cv-treeview.js +396 -0
- package/dist/components/cv-window-splitter.d.ts +79 -0
- package/dist/components/cv-window-splitter.js +316 -0
- package/dist/components/index.d.ts +94 -0
- package/dist/components/index.js +79 -0
- package/dist/dialog/create-dialog-controller.d.ts +31 -0
- package/dist/dialog/create-dialog-controller.js +320 -0
- package/dist/dialog/index.d.ts +2 -0
- package/dist/dialog/index.js +1 -0
- package/dist/form-associated/FormAssociatedReatomElement.d.ts +25 -0
- package/dist/form-associated/FormAssociatedReatomElement.js +70 -0
- package/dist/form-associated/withFormAssociated.d.ts +5 -0
- package/dist/form-associated/withFormAssociated.js +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/reatom-lit/ReatomLitElement.d.ts +27 -0
- package/dist/reatom-lit/ReatomLitElement.js +118 -0
- package/dist/reatom-lit/html.d.ts +4 -0
- package/dist/reatom-lit/html.js +10 -0
- package/dist/reatom-lit/index.d.ts +4 -0
- package/dist/reatom-lit/index.js +4 -0
- package/dist/reatom-lit/watch.d.ts +15 -0
- package/dist/reatom-lit/watch.js +40 -0
- package/dist/reatom-lit/withReatomElement.d.ts +4 -0
- package/dist/reatom-lit/withReatomElement.js +57 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +84 -0
- package/dist/styles/component-styles.d.ts +4 -0
- package/dist/styles/component-styles.js +78 -0
- package/dist/theme/cv-theme-provider.d.ts +32 -0
- package/dist/theme/cv-theme-provider.js +110 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.js +2 -0
- package/dist/theme/theme-engine.d.ts +4 -0
- package/dist/theme/theme-engine.js +67 -0
- package/dist/theme/tokens.css +265 -0
- package/dist/theme/types.d.ts +7 -0
- package/dist/theme/types.js +1 -0
- package/dist/toast/create-toast-controller.d.ts +12 -0
- package/dist/toast/create-toast-controller.js +12 -0
- package/dist/toast/index.d.ts +2 -0
- package/dist/toast/index.js +1 -0
- package/package.json +146 -0
- package/specs/_template.md +110 -0
- package/specs/components/accordion.md +207 -0
- package/specs/components/alert.md +83 -0
- package/specs/components/badge.md +183 -0
- package/specs/components/breadcrumb.md +152 -0
- package/specs/components/button.md +227 -0
- package/specs/components/callout.md +153 -0
- package/specs/components/card.md +192 -0
- package/specs/components/carousel.md +232 -0
- package/specs/components/checkbox.md +141 -0
- package/specs/components/combobox.md +427 -0
- package/specs/components/context-menu.md +375 -0
- package/specs/components/copy-button.md +236 -0
- package/specs/components/date-picker.md +290 -0
- package/specs/components/dialog.md +184 -0
- package/specs/components/disclosure.md +151 -0
- package/specs/components/drawer.md +216 -0
- package/specs/components/feed.md +266 -0
- package/specs/components/grid.md +423 -0
- package/specs/components/input.md +237 -0
- package/specs/components/landmark.md +92 -0
- package/specs/components/link.md +117 -0
- package/specs/components/listbox.md +327 -0
- package/specs/components/menu.md +508 -0
- package/specs/components/meter.md +148 -0
- package/specs/components/number.md +268 -0
- package/specs/components/option.md +167 -0
- package/specs/components/popover.md +207 -0
- package/specs/components/progress-ring.md +134 -0
- package/specs/components/progress.md +110 -0
- package/specs/components/radio.md +208 -0
- package/specs/components/select.md +305 -0
- package/specs/components/sidebar.md +204 -0
- package/specs/components/spinbutton.md +157 -0
- package/specs/components/spinner.md +83 -0
- package/specs/components/switch.md +145 -0
- package/specs/components/table.md +372 -0
- package/specs/components/tabs.md +242 -0
- package/specs/components/textarea.md +166 -0
- package/specs/components/theme.md +364 -0
- package/specs/components/toast.md +198 -0
- package/specs/components/toolbar.md +258 -0
- package/specs/components/tooltip.md +152 -0
- package/specs/components/treegrid.md +363 -0
- package/specs/components/treeview.md +263 -0
- package/specs/components/window-splitter.md +225 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
export declare class CVListboxGroup extends LitElement {
|
|
3
|
+
static elementName: string;
|
|
4
|
+
static get properties(): {
|
|
5
|
+
label: {
|
|
6
|
+
type: StringConstructor;
|
|
7
|
+
reflect: boolean;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
label: string;
|
|
11
|
+
constructor();
|
|
12
|
+
static styles: import("lit").CSSResult[];
|
|
13
|
+
static define(): void;
|
|
14
|
+
protected render(): import("lit").TemplateResult<1>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
export class CVListboxGroup extends LitElement {
|
|
3
|
+
static elementName = 'cv-listbox-group';
|
|
4
|
+
static get properties() {
|
|
5
|
+
return {
|
|
6
|
+
label: { type: String, reflect: true },
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
this.label = '';
|
|
12
|
+
}
|
|
13
|
+
static styles = [
|
|
14
|
+
css `
|
|
15
|
+
:host {
|
|
16
|
+
display: grid;
|
|
17
|
+
gap: var(--cv-listbox-group-gap, var(--cv-space-1, 4px));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
:host([hidden]) {
|
|
21
|
+
display: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
[part='label'] {
|
|
25
|
+
padding: 0 var(--cv-space-2, 8px);
|
|
26
|
+
font-size: var(--cv-listbox-group-label-font-size, 0.85em);
|
|
27
|
+
color: var(--cv-listbox-group-label-color, var(--cv-color-text-muted, #8892a6));
|
|
28
|
+
}
|
|
29
|
+
`,
|
|
30
|
+
];
|
|
31
|
+
static define() {
|
|
32
|
+
if (!customElements.get(this.elementName)) {
|
|
33
|
+
customElements.define(this.elementName, this);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
render() {
|
|
37
|
+
return html `
|
|
38
|
+
<div part="label">${this.label}</div>
|
|
39
|
+
<slot></slot>
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { PropertyValues } from 'lit';
|
|
2
|
+
import { ReatomLitElement } from '../reatom-lit/ReatomLitElement.js';
|
|
3
|
+
export interface CVListboxEventDetail {
|
|
4
|
+
selectedValues: string[];
|
|
5
|
+
activeValue: string | null;
|
|
6
|
+
}
|
|
7
|
+
type CVSelectionMode = 'single' | 'multiple';
|
|
8
|
+
type CVOrientation = 'vertical' | 'horizontal';
|
|
9
|
+
type CVFocusStrategy = 'roving-tabindex' | 'aria-activedescendant';
|
|
10
|
+
export declare class CVListbox extends ReatomLitElement {
|
|
11
|
+
static elementName: string;
|
|
12
|
+
static get properties(): {
|
|
13
|
+
selectionMode: {
|
|
14
|
+
type: StringConstructor;
|
|
15
|
+
attribute: string;
|
|
16
|
+
reflect: boolean;
|
|
17
|
+
};
|
|
18
|
+
orientation: {
|
|
19
|
+
type: StringConstructor;
|
|
20
|
+
reflect: boolean;
|
|
21
|
+
};
|
|
22
|
+
focusStrategy: {
|
|
23
|
+
type: StringConstructor;
|
|
24
|
+
attribute: string;
|
|
25
|
+
reflect: boolean;
|
|
26
|
+
};
|
|
27
|
+
selectionFollowsFocus: {
|
|
28
|
+
type: BooleanConstructor;
|
|
29
|
+
attribute: string;
|
|
30
|
+
};
|
|
31
|
+
rangeSelection: {
|
|
32
|
+
type: BooleanConstructor;
|
|
33
|
+
attribute: string;
|
|
34
|
+
};
|
|
35
|
+
typeahead: {
|
|
36
|
+
type: BooleanConstructor;
|
|
37
|
+
};
|
|
38
|
+
ariaLabel: {
|
|
39
|
+
type: StringConstructor;
|
|
40
|
+
attribute: string;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
selectionMode: CVSelectionMode;
|
|
44
|
+
orientation: CVOrientation;
|
|
45
|
+
focusStrategy: CVFocusStrategy;
|
|
46
|
+
selectionFollowsFocus: boolean;
|
|
47
|
+
rangeSelection: boolean;
|
|
48
|
+
typeahead: boolean;
|
|
49
|
+
ariaLabel: string;
|
|
50
|
+
private readonly idBase;
|
|
51
|
+
private optionRecords;
|
|
52
|
+
private optionListeners;
|
|
53
|
+
private model?;
|
|
54
|
+
constructor();
|
|
55
|
+
static styles: import("lit").CSSResult[];
|
|
56
|
+
static define(): void;
|
|
57
|
+
connectedCallback(): void;
|
|
58
|
+
disconnectedCallback(): void;
|
|
59
|
+
get value(): string | null;
|
|
60
|
+
set value(next: string | null);
|
|
61
|
+
get selectedValues(): string[];
|
|
62
|
+
willUpdate(changedProperties: PropertyValues): void;
|
|
63
|
+
updated(changedProperties: PropertyValues): void;
|
|
64
|
+
private getAllOptionElements;
|
|
65
|
+
private scanGroups;
|
|
66
|
+
private getInitialSelectedFromOptions;
|
|
67
|
+
private ensureOptionValue;
|
|
68
|
+
private rebuildModelFromSlot;
|
|
69
|
+
private detachOptionListeners;
|
|
70
|
+
private attachOptionListeners;
|
|
71
|
+
private syncOptionElements;
|
|
72
|
+
private focusActiveOption;
|
|
73
|
+
private dispatchInput;
|
|
74
|
+
private dispatchChange;
|
|
75
|
+
private applyInteractionResult;
|
|
76
|
+
private handleOptionPointerSelect;
|
|
77
|
+
private handleListboxKeyDown;
|
|
78
|
+
private handleSlotChange;
|
|
79
|
+
protected render(): import("lit").TemplateResult<1>;
|
|
80
|
+
}
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { createListbox } from '@chromvoid/headless-ui/listbox';
|
|
2
|
+
import { css, html, nothing } from 'lit';
|
|
3
|
+
import { ReatomLitElement } from '../reatom-lit/ReatomLitElement.js';
|
|
4
|
+
import { CVListboxGroup } from './cv-listbox-group.js';
|
|
5
|
+
import { CVOption } from './cv-option.js';
|
|
6
|
+
let cvListboxNonce = 0;
|
|
7
|
+
function arraysEqual(left, right) {
|
|
8
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
9
|
+
}
|
|
10
|
+
function shouldPreventDefaultForKey(event) {
|
|
11
|
+
if (event.key.toLowerCase() === 'a' && (event.ctrlKey || event.metaKey)) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return [
|
|
15
|
+
'ArrowUp',
|
|
16
|
+
'ArrowDown',
|
|
17
|
+
'ArrowLeft',
|
|
18
|
+
'ArrowRight',
|
|
19
|
+
'Home',
|
|
20
|
+
'End',
|
|
21
|
+
'Enter',
|
|
22
|
+
' ',
|
|
23
|
+
'Spacebar',
|
|
24
|
+
'Escape',
|
|
25
|
+
].includes(event.key);
|
|
26
|
+
}
|
|
27
|
+
export class CVListbox extends ReatomLitElement {
|
|
28
|
+
static elementName = 'cv-listbox';
|
|
29
|
+
static get properties() {
|
|
30
|
+
return {
|
|
31
|
+
selectionMode: { type: String, attribute: 'selection-mode', reflect: true },
|
|
32
|
+
orientation: { type: String, reflect: true },
|
|
33
|
+
focusStrategy: { type: String, attribute: 'focus-strategy', reflect: true },
|
|
34
|
+
selectionFollowsFocus: { type: Boolean, attribute: 'selection-follows-focus' },
|
|
35
|
+
rangeSelection: { type: Boolean, attribute: 'range-selection' },
|
|
36
|
+
typeahead: { type: Boolean },
|
|
37
|
+
ariaLabel: { type: String, attribute: 'aria-label' },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
idBase = `cv-listbox-${++cvListboxNonce}`;
|
|
41
|
+
optionRecords = [];
|
|
42
|
+
optionListeners = new WeakMap();
|
|
43
|
+
model;
|
|
44
|
+
constructor() {
|
|
45
|
+
super();
|
|
46
|
+
this.selectionMode = 'single';
|
|
47
|
+
this.orientation = 'vertical';
|
|
48
|
+
this.focusStrategy = 'aria-activedescendant';
|
|
49
|
+
this.selectionFollowsFocus = false;
|
|
50
|
+
this.rangeSelection = false;
|
|
51
|
+
this.typeahead = true;
|
|
52
|
+
this.ariaLabel = '';
|
|
53
|
+
}
|
|
54
|
+
static styles = [
|
|
55
|
+
css `
|
|
56
|
+
:host {
|
|
57
|
+
display: block;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
[part='base'] {
|
|
61
|
+
display: grid;
|
|
62
|
+
gap: var(--cv-listbox-gap, var(--cv-space-1, 4px));
|
|
63
|
+
padding: var(--cv-listbox-padding, var(--cv-space-1, 4px));
|
|
64
|
+
border-radius: var(--cv-listbox-border-radius, var(--cv-radius-md, 10px));
|
|
65
|
+
border: 1px solid var(--cv-listbox-border-color, var(--cv-color-border, #2a3245));
|
|
66
|
+
background: var(--cv-listbox-background, var(--cv-color-surface, #141923));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
[part='base']:focus-visible {
|
|
70
|
+
outline: 2px solid var(--cv-listbox-focus-outline-color, var(--cv-color-primary, #65d7ff));
|
|
71
|
+
outline-offset: 1px;
|
|
72
|
+
}
|
|
73
|
+
`,
|
|
74
|
+
];
|
|
75
|
+
static define() {
|
|
76
|
+
if (!customElements.get(this.elementName)) {
|
|
77
|
+
customElements.define(this.elementName, this);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
connectedCallback() {
|
|
81
|
+
super.connectedCallback();
|
|
82
|
+
if (!this.model) {
|
|
83
|
+
this.rebuildModelFromSlot(false, false);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
disconnectedCallback() {
|
|
87
|
+
super.disconnectedCallback();
|
|
88
|
+
this.detachOptionListeners();
|
|
89
|
+
}
|
|
90
|
+
get value() {
|
|
91
|
+
if (!this.model)
|
|
92
|
+
return null;
|
|
93
|
+
return this.model.state.selectedIds()[0] ?? null;
|
|
94
|
+
}
|
|
95
|
+
set value(next) {
|
|
96
|
+
if (!this.model)
|
|
97
|
+
return;
|
|
98
|
+
if (next == null) {
|
|
99
|
+
this.model.actions.clearSelected();
|
|
100
|
+
this.syncOptionElements();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.model.actions.selectOnly(next);
|
|
104
|
+
this.syncOptionElements();
|
|
105
|
+
}
|
|
106
|
+
get selectedValues() {
|
|
107
|
+
if (!this.model)
|
|
108
|
+
return [];
|
|
109
|
+
return [...this.model.state.selectedIds()];
|
|
110
|
+
}
|
|
111
|
+
willUpdate(changedProperties) {
|
|
112
|
+
super.willUpdate(changedProperties);
|
|
113
|
+
if (changedProperties.has('selectionMode') ||
|
|
114
|
+
changedProperties.has('orientation') ||
|
|
115
|
+
changedProperties.has('focusStrategy') ||
|
|
116
|
+
changedProperties.has('selectionFollowsFocus') ||
|
|
117
|
+
changedProperties.has('rangeSelection') ||
|
|
118
|
+
changedProperties.has('typeahead') ||
|
|
119
|
+
changedProperties.has('ariaLabel')) {
|
|
120
|
+
this.rebuildModelFromSlot(true, false);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
updated(changedProperties) {
|
|
124
|
+
super.updated(changedProperties);
|
|
125
|
+
this.syncOptionElements();
|
|
126
|
+
}
|
|
127
|
+
getAllOptionElements() {
|
|
128
|
+
const options = [];
|
|
129
|
+
for (const child of Array.from(this.children)) {
|
|
130
|
+
if (child.tagName.toLowerCase() === CVOption.elementName) {
|
|
131
|
+
options.push(child);
|
|
132
|
+
}
|
|
133
|
+
else if (child.tagName.toLowerCase() === CVListboxGroup.elementName) {
|
|
134
|
+
for (const groupChild of Array.from(child.children)) {
|
|
135
|
+
if (groupChild.tagName.toLowerCase() === CVOption.elementName) {
|
|
136
|
+
options.push(groupChild);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return options;
|
|
142
|
+
}
|
|
143
|
+
scanGroups() {
|
|
144
|
+
const groups = [];
|
|
145
|
+
const optionGroupMap = new Map();
|
|
146
|
+
let groupIndex = 0;
|
|
147
|
+
for (const child of Array.from(this.children)) {
|
|
148
|
+
if (child.tagName.toLowerCase() === CVListboxGroup.elementName) {
|
|
149
|
+
const groupElement = child;
|
|
150
|
+
const groupId = `group-${groupIndex++}`;
|
|
151
|
+
groups.push({ id: groupId, label: groupElement.label || '' });
|
|
152
|
+
for (const groupChild of Array.from(child.children)) {
|
|
153
|
+
if (groupChild.tagName.toLowerCase() === CVOption.elementName) {
|
|
154
|
+
optionGroupMap.set(groupChild, groupId);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { groups, optionGroupMap };
|
|
160
|
+
}
|
|
161
|
+
getInitialSelectedFromOptions(optionElements) {
|
|
162
|
+
return optionElements
|
|
163
|
+
.filter((option) => option.selected && !option.disabled)
|
|
164
|
+
.map((option, index) => this.ensureOptionValue(option, index));
|
|
165
|
+
}
|
|
166
|
+
ensureOptionValue(option, index) {
|
|
167
|
+
const normalized = option.value?.trim();
|
|
168
|
+
if (normalized)
|
|
169
|
+
return normalized;
|
|
170
|
+
const fallback = `option-${index + 1}`;
|
|
171
|
+
option.value = fallback;
|
|
172
|
+
return fallback;
|
|
173
|
+
}
|
|
174
|
+
rebuildModelFromSlot(preserveSelection, requestRender = true) {
|
|
175
|
+
const optionElements = this.getAllOptionElements();
|
|
176
|
+
const { groups, optionGroupMap } = this.scanGroups();
|
|
177
|
+
const previousSelected = preserveSelection
|
|
178
|
+
? (this.model?.state.selectedIds() ?? this.getInitialSelectedFromOptions(optionElements))
|
|
179
|
+
: this.getInitialSelectedFromOptions(optionElements);
|
|
180
|
+
const previousActive = preserveSelection ? (this.model?.state.activeId() ?? null) : null;
|
|
181
|
+
this.detachOptionListeners();
|
|
182
|
+
this.optionRecords = optionElements.map((element, index) => {
|
|
183
|
+
const id = this.ensureOptionValue(element, index);
|
|
184
|
+
const label = element.textContent?.trim() || id;
|
|
185
|
+
const disabled = element.disabled;
|
|
186
|
+
const groupId = optionGroupMap.get(element);
|
|
187
|
+
return {
|
|
188
|
+
id,
|
|
189
|
+
label,
|
|
190
|
+
disabled,
|
|
191
|
+
groupId,
|
|
192
|
+
element,
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
const selectableIds = new Set(this.optionRecords.filter((record) => !record.disabled).map((record) => record.id));
|
|
196
|
+
const initialSelectedIds = previousSelected.filter((id) => selectableIds.has(id));
|
|
197
|
+
const initialActiveId = previousActive && selectableIds.has(previousActive) ? previousActive : null;
|
|
198
|
+
this.model = createListbox({
|
|
199
|
+
idBase: this.idBase,
|
|
200
|
+
options: this.optionRecords.map((record) => ({
|
|
201
|
+
id: record.id,
|
|
202
|
+
label: record.label,
|
|
203
|
+
disabled: record.disabled,
|
|
204
|
+
groupId: record.groupId,
|
|
205
|
+
})),
|
|
206
|
+
groups,
|
|
207
|
+
selectionMode: this.selectionMode,
|
|
208
|
+
orientation: this.orientation,
|
|
209
|
+
focusStrategy: this.focusStrategy,
|
|
210
|
+
selectionFollowsFocus: this.selectionFollowsFocus,
|
|
211
|
+
rangeSelection: this.rangeSelection,
|
|
212
|
+
typeahead: this.typeahead,
|
|
213
|
+
ariaLabel: this.ariaLabel || undefined,
|
|
214
|
+
initialSelectedIds,
|
|
215
|
+
initialActiveId,
|
|
216
|
+
});
|
|
217
|
+
this.attachOptionListeners();
|
|
218
|
+
this.syncOptionElements();
|
|
219
|
+
if (requestRender) {
|
|
220
|
+
this.requestUpdate();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
detachOptionListeners() {
|
|
224
|
+
for (const record of this.optionRecords) {
|
|
225
|
+
const listeners = this.optionListeners.get(record.element);
|
|
226
|
+
if (!listeners)
|
|
227
|
+
continue;
|
|
228
|
+
record.element.removeEventListener('click', listeners.click);
|
|
229
|
+
record.element.removeEventListener('keydown', listeners.keydown);
|
|
230
|
+
this.optionListeners.delete(record.element);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
attachOptionListeners() {
|
|
234
|
+
if (!this.model)
|
|
235
|
+
return;
|
|
236
|
+
for (const record of this.optionRecords) {
|
|
237
|
+
const click = () => {
|
|
238
|
+
this.handleOptionPointerSelect(record.id);
|
|
239
|
+
};
|
|
240
|
+
const keydown = (event) => {
|
|
241
|
+
event.stopPropagation();
|
|
242
|
+
this.handleListboxKeyDown(event);
|
|
243
|
+
};
|
|
244
|
+
record.element.addEventListener('click', click);
|
|
245
|
+
record.element.addEventListener('keydown', keydown);
|
|
246
|
+
this.optionListeners.set(record.element, { click, keydown });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
syncOptionElements() {
|
|
250
|
+
if (!this.model)
|
|
251
|
+
return;
|
|
252
|
+
for (const record of this.optionRecords) {
|
|
253
|
+
const props = this.model.contracts.getOptionProps(record.id);
|
|
254
|
+
const ariaSelected = props['aria-selected'] ?? 'false';
|
|
255
|
+
record.element.id = props.id;
|
|
256
|
+
record.element.setAttribute('role', props.role);
|
|
257
|
+
record.element.setAttribute('tabindex', props.tabindex);
|
|
258
|
+
record.element.setAttribute('aria-selected', ariaSelected);
|
|
259
|
+
record.element.setAttribute('aria-setsize', props['aria-setsize']);
|
|
260
|
+
record.element.setAttribute('aria-posinset', props['aria-posinset']);
|
|
261
|
+
if (props['aria-disabled']) {
|
|
262
|
+
record.element.setAttribute('aria-disabled', props['aria-disabled']);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
record.element.removeAttribute('aria-disabled');
|
|
266
|
+
}
|
|
267
|
+
if (props['data-active'] === 'true') {
|
|
268
|
+
record.element.setAttribute('data-active', 'true');
|
|
269
|
+
record.element.active = true;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
record.element.removeAttribute('data-active');
|
|
273
|
+
record.element.active = false;
|
|
274
|
+
}
|
|
275
|
+
record.element.selected = ariaSelected === 'true';
|
|
276
|
+
record.element.disabled = record.disabled;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
focusActiveOption() {
|
|
280
|
+
if (!this.model || this.focusStrategy !== 'roving-tabindex')
|
|
281
|
+
return;
|
|
282
|
+
const activeId = this.model.state.activeId();
|
|
283
|
+
if (!activeId)
|
|
284
|
+
return;
|
|
285
|
+
const activeRecord = this.optionRecords.find((record) => record.id === activeId);
|
|
286
|
+
activeRecord?.element.focus();
|
|
287
|
+
}
|
|
288
|
+
dispatchInput(detail) {
|
|
289
|
+
this.dispatchEvent(new CustomEvent('cv-input', {
|
|
290
|
+
detail,
|
|
291
|
+
bubbles: true,
|
|
292
|
+
composed: true,
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
dispatchChange(detail) {
|
|
296
|
+
this.dispatchEvent(new CustomEvent('cv-change', {
|
|
297
|
+
detail,
|
|
298
|
+
bubbles: true,
|
|
299
|
+
composed: true,
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
applyInteractionResult(previousSelected, previousActive) {
|
|
303
|
+
if (!this.model)
|
|
304
|
+
return;
|
|
305
|
+
this.syncOptionElements();
|
|
306
|
+
const nextSelected = this.model.state.selectedIds();
|
|
307
|
+
const nextActive = this.model.state.activeId();
|
|
308
|
+
const selectedChanged = !arraysEqual(previousSelected, nextSelected);
|
|
309
|
+
const activeChanged = previousActive !== nextActive;
|
|
310
|
+
if (activeChanged || selectedChanged) {
|
|
311
|
+
this.dispatchInput({
|
|
312
|
+
selectedValues: [...nextSelected],
|
|
313
|
+
activeValue: nextActive,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
if (selectedChanged) {
|
|
317
|
+
this.dispatchChange({
|
|
318
|
+
selectedValues: [...nextSelected],
|
|
319
|
+
activeValue: nextActive,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
if (activeChanged) {
|
|
323
|
+
this.focusActiveOption();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
handleOptionPointerSelect(id) {
|
|
327
|
+
if (!this.model)
|
|
328
|
+
return;
|
|
329
|
+
const previousSelected = this.model.state.selectedIds();
|
|
330
|
+
const previousActive = this.model.state.activeId();
|
|
331
|
+
this.model.actions.setActive(id);
|
|
332
|
+
if (this.selectionMode === 'multiple') {
|
|
333
|
+
this.model.actions.toggleSelected(id);
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
this.model.actions.selectOnly(id);
|
|
337
|
+
}
|
|
338
|
+
this.applyInteractionResult(previousSelected, previousActive);
|
|
339
|
+
}
|
|
340
|
+
handleListboxKeyDown(event) {
|
|
341
|
+
if (!this.model)
|
|
342
|
+
return;
|
|
343
|
+
const rootElement = this.shadowRoot?.querySelector('[part="base"]');
|
|
344
|
+
if (this.focusStrategy === 'roving-tabindex' && event.currentTarget === rootElement) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (shouldPreventDefaultForKey(event)) {
|
|
348
|
+
event.preventDefault();
|
|
349
|
+
}
|
|
350
|
+
const previousSelected = this.model.state.selectedIds();
|
|
351
|
+
const previousActive = this.model.state.activeId();
|
|
352
|
+
this.model.actions.handleKeyDown({
|
|
353
|
+
key: event.key,
|
|
354
|
+
shiftKey: event.shiftKey,
|
|
355
|
+
ctrlKey: event.ctrlKey,
|
|
356
|
+
metaKey: event.metaKey,
|
|
357
|
+
altKey: event.altKey,
|
|
358
|
+
});
|
|
359
|
+
this.applyInteractionResult(previousSelected, previousActive);
|
|
360
|
+
}
|
|
361
|
+
handleSlotChange() {
|
|
362
|
+
this.rebuildModelFromSlot(true, true);
|
|
363
|
+
}
|
|
364
|
+
render() {
|
|
365
|
+
const rootProps = this.model?.contracts.getRootProps() ?? {
|
|
366
|
+
role: 'listbox',
|
|
367
|
+
tabindex: this.focusStrategy === 'aria-activedescendant' ? '0' : '-1',
|
|
368
|
+
'aria-orientation': this.orientation,
|
|
369
|
+
'aria-label': this.ariaLabel || undefined,
|
|
370
|
+
'aria-multiselectable': this.selectionMode === 'multiple' ? 'true' : undefined,
|
|
371
|
+
'aria-activedescendant': undefined,
|
|
372
|
+
};
|
|
373
|
+
return html `
|
|
374
|
+
<div
|
|
375
|
+
role=${rootProps.role}
|
|
376
|
+
tabindex=${rootProps.tabindex}
|
|
377
|
+
aria-orientation=${rootProps['aria-orientation']}
|
|
378
|
+
aria-label=${rootProps['aria-label'] ?? nothing}
|
|
379
|
+
aria-multiselectable=${rootProps['aria-multiselectable'] ?? nothing}
|
|
380
|
+
aria-activedescendant=${rootProps['aria-activedescendant'] ?? nothing}
|
|
381
|
+
part="base"
|
|
382
|
+
@keydown=${this.handleListboxKeyDown}
|
|
383
|
+
>
|
|
384
|
+
<slot @slotchange=${this.handleSlotChange}></slot>
|
|
385
|
+
</div>
|
|
386
|
+
`;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { PropertyValues } from 'lit';
|
|
2
|
+
import { ReatomLitElement } from '../reatom-lit/ReatomLitElement.js';
|
|
3
|
+
export interface CVMenuButtonEventDetail {
|
|
4
|
+
value: string | null;
|
|
5
|
+
activeId: string | null;
|
|
6
|
+
open: boolean;
|
|
7
|
+
}
|
|
8
|
+
export type CVMenuButtonInputEvent = CustomEvent<CVMenuButtonEventDetail>;
|
|
9
|
+
export type CVMenuButtonChangeEvent = CustomEvent<CVMenuButtonEventDetail>;
|
|
10
|
+
export type CVMenuButtonActionEvent = CustomEvent<Record<string, never>>;
|
|
11
|
+
export interface CVMenuButtonEventMap {
|
|
12
|
+
'cv-input': CVMenuButtonInputEvent;
|
|
13
|
+
'cv-change': CVMenuButtonChangeEvent;
|
|
14
|
+
'cv-action': CVMenuButtonActionEvent;
|
|
15
|
+
}
|
|
16
|
+
export declare class CVMenuButton extends ReatomLitElement {
|
|
17
|
+
static elementName: string;
|
|
18
|
+
static hostDisplay: "inline-block";
|
|
19
|
+
static get properties(): {
|
|
20
|
+
value: {
|
|
21
|
+
type: StringConstructor;
|
|
22
|
+
reflect: boolean;
|
|
23
|
+
};
|
|
24
|
+
open: {
|
|
25
|
+
type: BooleanConstructor;
|
|
26
|
+
reflect: boolean;
|
|
27
|
+
};
|
|
28
|
+
disabled: {
|
|
29
|
+
type: BooleanConstructor;
|
|
30
|
+
reflect: boolean;
|
|
31
|
+
};
|
|
32
|
+
split: {
|
|
33
|
+
type: BooleanConstructor;
|
|
34
|
+
reflect: boolean;
|
|
35
|
+
};
|
|
36
|
+
size: {
|
|
37
|
+
type: StringConstructor;
|
|
38
|
+
reflect: boolean;
|
|
39
|
+
};
|
|
40
|
+
variant: {
|
|
41
|
+
type: StringConstructor;
|
|
42
|
+
reflect: boolean;
|
|
43
|
+
};
|
|
44
|
+
closeOnSelect: {
|
|
45
|
+
type: BooleanConstructor;
|
|
46
|
+
attribute: string;
|
|
47
|
+
reflect: boolean;
|
|
48
|
+
};
|
|
49
|
+
ariaLabel: {
|
|
50
|
+
type: StringConstructor;
|
|
51
|
+
attribute: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
value: string;
|
|
55
|
+
open: boolean;
|
|
56
|
+
disabled: boolean;
|
|
57
|
+
split: boolean;
|
|
58
|
+
size: 'small' | 'medium' | 'large';
|
|
59
|
+
variant: 'default' | 'primary' | 'danger' | 'ghost';
|
|
60
|
+
closeOnSelect: boolean;
|
|
61
|
+
ariaLabel: string;
|
|
62
|
+
private readonly idBase;
|
|
63
|
+
private itemRecords;
|
|
64
|
+
private itemListeners;
|
|
65
|
+
private hasPrefixContent;
|
|
66
|
+
private hasLabelContent;
|
|
67
|
+
private hasSuffixContent;
|
|
68
|
+
private model?;
|
|
69
|
+
private hasLayoutListeners;
|
|
70
|
+
private layoutFrame;
|
|
71
|
+
constructor();
|
|
72
|
+
static styles: import("lit").CSSResult[];
|
|
73
|
+
static define(): void;
|
|
74
|
+
connectedCallback(): void;
|
|
75
|
+
disconnectedCallback(): void;
|
|
76
|
+
willUpdate(changedProperties: PropertyValues): void;
|
|
77
|
+
updated(changedProperties: PropertyValues): void;
|
|
78
|
+
private getMenuElement;
|
|
79
|
+
private getBaseElement;
|
|
80
|
+
private clearInlineLayout;
|
|
81
|
+
private getMenuOffset;
|
|
82
|
+
private getMenuMinInlineSize;
|
|
83
|
+
private applyMenuLayout;
|
|
84
|
+
private syncMenuLayout;
|
|
85
|
+
private cancelLayoutFrame;
|
|
86
|
+
private scheduleLayout;
|
|
87
|
+
private toggleLayoutListeners;
|
|
88
|
+
private handleViewportChange;
|
|
89
|
+
private getItemElements;
|
|
90
|
+
private syncContentParts;
|
|
91
|
+
private hasNamedSlotContent;
|
|
92
|
+
private hasDefaultSlotContent;
|
|
93
|
+
private ensureItemValue;
|
|
94
|
+
private rebuildModelFromSlot;
|
|
95
|
+
private prefetchMenuIcons;
|
|
96
|
+
private detachItemListeners;
|
|
97
|
+
private attachItemListeners;
|
|
98
|
+
private syncItemElements;
|
|
99
|
+
private captureState;
|
|
100
|
+
private dispatchInput;
|
|
101
|
+
private dispatchChange;
|
|
102
|
+
private dispatchAction;
|
|
103
|
+
private focusActiveItem;
|
|
104
|
+
private applyInteractionResult;
|
|
105
|
+
private syncOutsidePointerListener;
|
|
106
|
+
private handleDocumentPointerDown;
|
|
107
|
+
private handleItemClick;
|
|
108
|
+
private handleTriggerClick;
|
|
109
|
+
private handleActionClick;
|
|
110
|
+
private handleDropdownClick;
|
|
111
|
+
private handleKeyDown;
|
|
112
|
+
private handleMenuSlotChange;
|
|
113
|
+
private handleContentSlotChange;
|
|
114
|
+
private renderDropdownIcon;
|
|
115
|
+
private renderSplitMode;
|
|
116
|
+
private renderStandardMode;
|
|
117
|
+
protected render(): import("lit").TemplateResult<1>;
|
|
118
|
+
}
|