@c2n/select 0.0.6 → 0.0.7
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/dist/select.js +133 -8
- package/package.json +21 -9
- package/react.d.ts +24 -0
- package/react.js +3 -0
- package/types/src/select.d.ts +64 -2
- package/types/src/select.d.ts.map +1 -1
- package/vue.d.ts +38 -0
- package/vue.js +3 -0
package/dist/select.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { LitElement, html, svg, unsafeCSS } from "lit";
|
|
2
|
-
import {
|
|
1
|
+
import { LitElement, html, nothing, svg, unsafeCSS } from "lit";
|
|
2
|
+
import { property, query, state } from "lit/decorators.js";
|
|
3
|
+
import { customElement } from "@c2n/core/element-helper.js";
|
|
3
4
|
import { classMap } from "lit/directives/class-map.js";
|
|
4
5
|
import { ListItem } from "@c2n/list-item";
|
|
5
6
|
import { arrayPropertyConverter } from "@c2n/core/lit-helper.js";
|
|
@@ -7,7 +8,7 @@ import { redispatchEvent } from "@c2n/core/dom-helper.js";
|
|
|
7
8
|
import "@c2n/overlay";
|
|
8
9
|
import "@c2n/list";
|
|
9
10
|
//#region src/select.scss?inline
|
|
10
|
-
var select_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: inline-flex;\n vertical-align: middle;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.c2-select {\n display: contents;\n}\n\n.button {\n display: flex;\n flex: 1;\n position: relative;\n box-sizing: border-box;\n width: 100%;\n min-width: 100%;\n max-width: 100%;\n min-height: var(--c2-select__button--min-height,36px);\n margin: 0;\n align-items: center;\n justify-content: space-between;\n appearance: none;\n user-select: none;\n cursor: pointer;\n text-transform: none;\n vertical-align: middle;\n outline: none;\n font-size: var(--c2-select__button--font-size,14px);\n font-weight: var(--c2-select__button--font-weight);\n font-style: var(--c2-select__button--font-style,normal);\n font-family: var(--c2-select__button--font-family);\n line-height: 1.4;\n background: var(--c2-select__button--background,#ffffff);\n gap: var(--c2-select__button--gap,8px);\n border-top: var(--c2-select__button--border-top,1px solid #bcbcc6);\n border-bottom: var(--c2-select__button--border-bottom,1px solid #bcbcc6);\n border-right: var(--c2-select__button--border-right,1px solid #bcbcc6);\n border-left: var(--c2-select__button--border-left,1px solid #bcbcc6);\n color: var(--c2-select__button--color,#18181b);\n padding: var(--c2-select__button--padding,6px 10px 6px 12px);\n border-top-left-radius: var(--c2-select__button--border-top-left-radius,6px);\n border-top-right-radius: var(--c2-select__button--border-top-right-radius,6px);\n border-bottom-left-radius: var(--c2-select__button--border-bottom-left-radius,6px);\n border-bottom-right-radius: var(--c2-select__button--border-bottom-right-radius,6px);\n will-change: background, color, border;\n transition: background 250ms cubic-bezier(0.2, 0, 0, 1), color 250ms cubic-bezier(0.2, 0, 0, 1), border 250ms cubic-bezier(0.2, 0, 0, 1);\n}\n.button:hover {\n border-top: var(--c2-select__button__hover--border-top,1px solid #a1a1aa);\n border-bottom: var(--c2-select__button__hover--border-bottom,1px solid #a1a1aa);\n border-right: var(--c2-select__button__hover--border-right,1px solid #a1a1aa);\n border-left: var(--c2-select__button__hover--border-left,1px solid #a1a1aa);\n background: var(--c2-select__button__hover--background,#fafafa);\n color: var(--c2-select__button__hover--color,#18181b);\n}\n.button:focus-visible {\n outline: var(--c2-select__button__focus--outline,2px solid rgba(71, 110, 249, 0.4));\n outline-offset: var(--c2-select__button__focus--outline-offset,
|
|
11
|
+
var select_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: inline-flex;\n vertical-align: middle;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.c2-select {\n display: contents;\n}\n\n.button {\n display: flex;\n flex: 1;\n position: relative;\n box-sizing: border-box;\n width: 100%;\n min-width: 100%;\n max-width: 100%;\n min-height: var(--c2-select__button--min-height,36px);\n margin: 0;\n align-items: center;\n justify-content: space-between;\n appearance: none;\n user-select: none;\n cursor: pointer;\n text-transform: none;\n vertical-align: middle;\n outline: none;\n font-size: var(--c2-select__button--font-size,14px);\n font-weight: var(--c2-select__button--font-weight);\n font-style: var(--c2-select__button--font-style,normal);\n font-family: var(--c2-select__button--font-family);\n line-height: 1.4;\n background: var(--c2-select__button--background,#ffffff);\n gap: var(--c2-select__button--gap,8px);\n border-top: var(--c2-select__button--border-top,1px solid #bcbcc6);\n border-bottom: var(--c2-select__button--border-bottom,1px solid #bcbcc6);\n border-right: var(--c2-select__button--border-right,1px solid #bcbcc6);\n border-left: var(--c2-select__button--border-left,1px solid #bcbcc6);\n color: var(--c2-select__button--color,#18181b);\n padding: var(--c2-select__button--padding,6px 10px 6px 12px);\n border-top-left-radius: var(--c2-select__button--border-top-left-radius,6px);\n border-top-right-radius: var(--c2-select__button--border-top-right-radius,6px);\n border-bottom-left-radius: var(--c2-select__button--border-bottom-left-radius,6px);\n border-bottom-right-radius: var(--c2-select__button--border-bottom-right-radius,6px);\n will-change: background, color, border;\n transition: background 250ms cubic-bezier(0.2, 0, 0, 1), color 250ms cubic-bezier(0.2, 0, 0, 1), border 250ms cubic-bezier(0.2, 0, 0, 1);\n}\n.button:hover {\n border-top: var(--c2-select__button__hover--border-top,1px solid #a1a1aa);\n border-bottom: var(--c2-select__button__hover--border-bottom,1px solid #a1a1aa);\n border-right: var(--c2-select__button__hover--border-right,1px solid #a1a1aa);\n border-left: var(--c2-select__button__hover--border-left,1px solid #a1a1aa);\n background: var(--c2-select__button__hover--background,#fafafa);\n color: var(--c2-select__button__hover--color,#18181b);\n}\n.button:focus-visible {\n outline: var(--c2-select__button__focus--outline,2px solid rgba(71, 110, 249, 0.4));\n outline-offset: var(--c2-select__button__focus--outline-offset,0px);\n}\n.button:disabled {\n cursor: default;\n pointer-events: none;\n opacity: var(--c2-select__button__disabled--opacity,0.38);\n}\n.button ::slotted([slot=button-prefix-icon]) {\n flex: none;\n width: var(--c2-select__button__prefix-icon--width,16px);\n height: var(--c2-select__button__prefix-icon--height,16px);\n color: var(--c2-select__button__prefix-icon--color);\n --c2-feather-icon--size: var(--c2-select__button__prefix-icon--width,16px);\n --c2-mat-icon--font-size: var(--c2-select__button__prefix-icon--width,16px);\n}\n.button ::slotted([slot=button-suffix-icon]),\n.button .default-icon {\n flex: none;\n width: var(--c2-select__button__suffix-icon--width,16px);\n height: var(--c2-select__button__suffix-icon--height,16px);\n color: var(--c2-select__button__suffix-icon--color,#71717a);\n --c2-feather-icon--size: var(--c2-select__button__suffix-icon--width,16px);\n --c2-mat-icon--font-size: var(--c2-select__button__suffix-icon--width,16px);\n}\n.button .default-icon {\n transition: transform 250ms cubic-bezier(0.2, 0, 0, 1);\n}\n.button .placeholder {\n font-weight: var(--c2-select__placeholder--font-weight);\n font-style: var(--c2-select__placeholder--font-style);\n color: var(--c2-select__placeholder--color,#71717a);\n opacity: var(--c2-select__placeholder--opacity,1);\n}\n.button .button-content {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n text-align: start;\n flex: 1;\n min-width: 0;\n}\n\n:host([open]) .button {\n border-top: var(--c2-select__button__open--border-top,1px solid #476ef9);\n border-bottom: var(--c2-select__button__open--border-bottom,1px solid #476ef9);\n border-right: var(--c2-select__button__open--border-right,1px solid #476ef9);\n border-left: var(--c2-select__button__open--border-left,1px solid #476ef9);\n}\n:host([open]) .button .default-icon {\n transform: rotate(180deg);\n}\n\n:host([readonly]) .button {\n cursor: default;\n}\n\n.list {\n min-width: var(--c2-select__list--min-width,auto);\n --c2-list--background: var(--c2-select__list--background,#ffffff);\n --c2-list--border-top: var(--c2-select__list--border-top,1px solid #e4e4e7);\n --c2-list--border-right: var(--c2-select__list--border-right,1px solid #e4e4e7);\n --c2-list--border-bottom: var(--c2-select__list--border-bottom,1px solid #e4e4e7);\n --c2-list--border-left: var(--c2-select__list--border-left,1px solid #e4e4e7);\n --c2-list--border-top-left-radius: var(--c2-select__list--border-top-left-radius,8px);\n --c2-list--border-top-right-radius: var(--c2-select__list--border-top-right-radius,8px);\n --c2-list--border-bottom-left-radius: var(--c2-select__list--border-bottom-left-radius,8px);\n --c2-list--border-bottom-right-radius: var(--c2-select__list--border-bottom-right-radius,8px);\n --c2-list--box-shadow: var(--c2-select__list--box-shadow,0 8px 24px rgba(24, 24, 27, 0.08));\n --c2-list--padding-top: var(--c2-select__list--padding-top,4px);\n --c2-list--padding-right: var(--c2-select__list--padding-right,4px);\n --c2-list--padding-bottom: var(--c2-select__list--padding-bottom,4px);\n --c2-list--padding-left: var(--c2-select__list--padding-left,4px);\n --c2-list--max-height: var(--c2-select__list--max-height,280px);\n}";
|
|
11
12
|
//#endregion
|
|
12
13
|
//#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorate.js
|
|
13
14
|
function __decorate(decorators, target, key, desc) {
|
|
@@ -18,17 +19,28 @@ function __decorate(decorators, target, key, desc) {
|
|
|
18
19
|
}
|
|
19
20
|
//#endregion
|
|
20
21
|
//#region src/select.ts
|
|
22
|
+
/** Once per page: the cause is always the same, and one select with the problem usually means all of them. */
|
|
23
|
+
var warnedUnupgraded = false;
|
|
21
24
|
var Select = class Select extends LitElement {
|
|
22
25
|
constructor(..._args) {
|
|
23
26
|
super(..._args);
|
|
27
|
+
this.internals = this.attachInternals();
|
|
28
|
+
this.customValidityMessage = "";
|
|
29
|
+
this.disabledByForm = false;
|
|
30
|
+
this.defaultValue = [];
|
|
31
|
+
this.defaultValueCaptured = false;
|
|
24
32
|
this.open = false;
|
|
25
33
|
this.readonly = false;
|
|
26
34
|
this.disabled = false;
|
|
27
35
|
this.focused = false;
|
|
28
36
|
this.fitSize = false;
|
|
29
37
|
this.placeholder = "";
|
|
38
|
+
this.ariaLabel = null;
|
|
39
|
+
this.ariaLabelledByAttr = null;
|
|
40
|
+
this.labelText = "";
|
|
30
41
|
this.multiple = false;
|
|
31
42
|
this.required = false;
|
|
43
|
+
this.name = "";
|
|
32
44
|
this.value = [];
|
|
33
45
|
this.displayText = "";
|
|
34
46
|
this.data = [];
|
|
@@ -39,6 +51,9 @@ var Select = class Select extends LitElement {
|
|
|
39
51
|
this.toggle(true);
|
|
40
52
|
};
|
|
41
53
|
}
|
|
54
|
+
static {
|
|
55
|
+
this.formAssociated = true;
|
|
56
|
+
}
|
|
42
57
|
static {
|
|
43
58
|
this.styles = unsafeCSS(select_default);
|
|
44
59
|
}
|
|
@@ -48,12 +63,66 @@ var Select = class Select extends LitElement {
|
|
|
48
63
|
delegatesFocus: true
|
|
49
64
|
};
|
|
50
65
|
}
|
|
66
|
+
/** Scalar convenience API for a single-select. Setting it replaces the current selection. */
|
|
67
|
+
get selectedValue() {
|
|
68
|
+
return this.value[0] ?? "";
|
|
69
|
+
}
|
|
70
|
+
set selectedValue(value) {
|
|
71
|
+
this.value = value ? [value] : [];
|
|
72
|
+
}
|
|
73
|
+
/** The containing form, when this control is associated with one. */
|
|
74
|
+
get form() {
|
|
75
|
+
return this.internals.form;
|
|
76
|
+
}
|
|
77
|
+
/** Labels associated with this form control. */
|
|
78
|
+
get labels() {
|
|
79
|
+
return this.internals.labels;
|
|
80
|
+
}
|
|
81
|
+
get validity() {
|
|
82
|
+
return this.internals.validity;
|
|
83
|
+
}
|
|
84
|
+
get validationMessage() {
|
|
85
|
+
return this.internals.validationMessage;
|
|
86
|
+
}
|
|
87
|
+
get willValidate() {
|
|
88
|
+
return this.internals.willValidate;
|
|
89
|
+
}
|
|
90
|
+
connectedCallback() {
|
|
91
|
+
super.connectedCallback();
|
|
92
|
+
queueMicrotask(() => this.isConnected && this.syncLabelText());
|
|
93
|
+
if (!this.defaultValueCaptured) {
|
|
94
|
+
this.defaultValue = arrayPropertyConverter.fromAttribute(this.getAttribute("value") ?? "");
|
|
95
|
+
this.defaultValueCaptured = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
formResetCallback() {
|
|
99
|
+
this.value = [...this.defaultValue];
|
|
100
|
+
}
|
|
101
|
+
formDisabledCallback(disabled) {
|
|
102
|
+
this.disabledByForm = disabled && !this.hasAttribute("disabled");
|
|
103
|
+
}
|
|
104
|
+
formStateRestoreCallback(state) {
|
|
105
|
+
if (typeof state === "string") this.value = arrayPropertyConverter.fromAttribute(state);
|
|
106
|
+
}
|
|
107
|
+
checkValidity() {
|
|
108
|
+
return this.internals.checkValidity();
|
|
109
|
+
}
|
|
110
|
+
reportValidity() {
|
|
111
|
+
return this.internals.reportValidity();
|
|
112
|
+
}
|
|
113
|
+
setCustomValidity(message) {
|
|
114
|
+
this.customValidityMessage = message;
|
|
115
|
+
this.syncFormState();
|
|
116
|
+
}
|
|
51
117
|
onButtonBlur() {
|
|
52
118
|
this.focused = false;
|
|
53
119
|
this.button.removeEventListener("keydown", this.onKeydown);
|
|
54
120
|
}
|
|
55
121
|
toggle(value) {
|
|
56
|
-
if (this.readonly)
|
|
122
|
+
if (this.readonly || this.effectiveDisabled) {
|
|
123
|
+
if (this.menu.matches(":popover-open")) this.menu.hidePopover();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
57
126
|
if (typeof value == "undefined") {
|
|
58
127
|
this.menu.togglePopover();
|
|
59
128
|
return;
|
|
@@ -76,9 +145,16 @@ var Select = class Select extends LitElement {
|
|
|
76
145
|
this.prepareGetDisplayText();
|
|
77
146
|
await this.updateComplete;
|
|
78
147
|
const displayText = [];
|
|
148
|
+
let unupgraded = 0;
|
|
79
149
|
if (this.listItemSlot) for (const slotItem of this.listItemSlot.assignedElements({ flatten: true })) {
|
|
80
150
|
const listItem = slotItem instanceof ListItem ? slotItem : slotItem.firstChild;
|
|
81
|
-
if (listItem instanceof ListItem
|
|
151
|
+
if (listItem instanceof ListItem) {
|
|
152
|
+
if (this.value.includes(listItem.value)) displayText.push(listItem.displayText);
|
|
153
|
+
} else if (slotItem.localName === "c2-list-item" || slotItem.firstElementChild?.localName === "c2-list-item") unupgraded += 1;
|
|
154
|
+
}
|
|
155
|
+
if (unupgraded > 0 && !warnedUnupgraded) {
|
|
156
|
+
warnedUnupgraded = true;
|
|
157
|
+
console.warn(`<c2-select> has ${unupgraded} c2-list-item child(ren) that have not been upgraded, so it cannot read a label off them and its trigger will render empty. Import '@c2n/list-item' on the client, and make sure the options reach the DOM as plain tags rather than as server-rendered, never-hydrated elements.`);
|
|
82
158
|
}
|
|
83
159
|
this.displayText = displayText.join(", ");
|
|
84
160
|
}
|
|
@@ -94,6 +170,14 @@ var Select = class Select extends LitElement {
|
|
|
94
170
|
this.data = event.detail.data;
|
|
95
171
|
if (!this.multiple) this.toggle(false);
|
|
96
172
|
redispatchEvent(this, event);
|
|
173
|
+
this.dispatchEvent(new Event("input", {
|
|
174
|
+
bubbles: true,
|
|
175
|
+
composed: true
|
|
176
|
+
}));
|
|
177
|
+
this.dispatchEvent(new Event("change", {
|
|
178
|
+
bubbles: true,
|
|
179
|
+
composed: true
|
|
180
|
+
}));
|
|
97
181
|
}
|
|
98
182
|
handleOverlayToggle(event) {
|
|
99
183
|
const toggleEvent = event;
|
|
@@ -103,8 +187,24 @@ var Select = class Select extends LitElement {
|
|
|
103
187
|
if (changedProperties.has("value") && typeof this.value === "string") this.value = arrayPropertyConverter.fromAttribute(this.value);
|
|
104
188
|
}
|
|
105
189
|
updated(_changedProperties) {
|
|
106
|
-
if (_changedProperties.has("
|
|
190
|
+
if (_changedProperties.has("ariaLabelledByAttr")) this.syncLabelText();
|
|
191
|
+
if (_changedProperties.has("open") || _changedProperties.has("readonly") || _changedProperties.has("disabled")) this.toggle(this.open);
|
|
107
192
|
if (_changedProperties.has("value")) this.updateDisplayText();
|
|
193
|
+
this.syncFormState();
|
|
194
|
+
}
|
|
195
|
+
syncFormState() {
|
|
196
|
+
if (this.multiple && this.name) {
|
|
197
|
+
const formData = new FormData();
|
|
198
|
+
for (const value of this.value) formData.append(this.name, value);
|
|
199
|
+
this.internals.setFormValue(formData, this.value.join(";"));
|
|
200
|
+
} else this.internals.setFormValue(this.selectedValue || null, this.value.join(";"));
|
|
201
|
+
const missing = this.required && this.value.length === 0;
|
|
202
|
+
const flags = this.customValidityMessage ? { customError: true } : missing ? { valueMissing: true } : {};
|
|
203
|
+
const message = this.customValidityMessage || (missing ? "Please select an option." : "");
|
|
204
|
+
this.internals.setValidity(flags, message, this.button);
|
|
205
|
+
}
|
|
206
|
+
get effectiveDisabled() {
|
|
207
|
+
return this.disabled || this.disabledByForm;
|
|
108
208
|
}
|
|
109
209
|
async getUpdateComplete() {
|
|
110
210
|
const complete = await super.getUpdateComplete();
|
|
@@ -114,18 +214,37 @@ var Select = class Select extends LitElement {
|
|
|
114
214
|
handleSlotChange() {
|
|
115
215
|
this.updateDisplayText();
|
|
116
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* The accessible name for the trigger.
|
|
219
|
+
*
|
|
220
|
+
* The focusable control is a `<button>` inside the shadow root, and neither of the two ways an author
|
|
221
|
+
* names this element reaches it on its own: `aria-labelledby` on the host points at an id in the light DOM,
|
|
222
|
+
* which ARIA cannot resolve across the shadow boundary, and a wrapping `<label>` names the host rather than
|
|
223
|
+
* the button. So the label's *text* is copied onto the button instead.
|
|
224
|
+
*/
|
|
225
|
+
get triggerLabel() {
|
|
226
|
+
return this.ariaLabel || this.labelText;
|
|
227
|
+
}
|
|
228
|
+
/** Reads the text of whatever labels the host: `aria-labelledby` first, then any associated `<label>`. */
|
|
229
|
+
syncLabelText() {
|
|
230
|
+
const root = this.getRootNode();
|
|
231
|
+
const fromIds = (this.ariaLabelledByAttr ?? "").split(/\s+/).filter(Boolean).map((id) => root.getElementById?.(id)?.textContent?.trim()).filter(Boolean);
|
|
232
|
+
const text = fromIds.length > 0 ? fromIds.join(" ") : [...this.internals.labels].map((label) => label.textContent?.trim()).filter(Boolean).join(" ");
|
|
233
|
+
this.labelText = text ?? "";
|
|
234
|
+
}
|
|
117
235
|
render() {
|
|
118
236
|
return html`<div class="c2-select">
|
|
119
237
|
<button
|
|
120
238
|
type="button"
|
|
239
|
+
aria-label=${this.triggerLabel || nothing}
|
|
121
240
|
aria-haspopup="listbox"
|
|
122
241
|
aria-expanded=${this.open ? "true" : "false"}
|
|
123
242
|
id="button"
|
|
124
|
-
popovertarget
|
|
243
|
+
popovertarget=${this.readonly || this.effectiveDisabled ? nothing : "menu-overlay"}
|
|
125
244
|
class="button"
|
|
126
245
|
@blur=${this.onButtonBlur}
|
|
127
246
|
@focus=${this.onButtonFocus}
|
|
128
|
-
?disabled=${this.
|
|
247
|
+
?disabled=${this.effectiveDisabled}
|
|
129
248
|
>
|
|
130
249
|
<slot name="button-prefix-icon"></slot>
|
|
131
250
|
<slot name="button-content">${this.renderButtonContent()}</slot>
|
|
@@ -134,6 +253,7 @@ var Select = class Select extends LitElement {
|
|
|
134
253
|
<c2-overlay id="menu-overlay" popover @toggle=${this.handleOverlayToggle} ?fit-anchor=${this.fitSize}>
|
|
135
254
|
<c2-list
|
|
136
255
|
id="list"
|
|
256
|
+
aria-labelledby="button"
|
|
137
257
|
.value=${this.value}
|
|
138
258
|
class="list"
|
|
139
259
|
?multiple=${this.multiple}
|
|
@@ -146,6 +266,7 @@ var Select = class Select extends LitElement {
|
|
|
146
266
|
</div> `;
|
|
147
267
|
}
|
|
148
268
|
};
|
|
269
|
+
__decorate([state()], Select.prototype, "disabledByForm", void 0);
|
|
149
270
|
__decorate([property({
|
|
150
271
|
type: Boolean,
|
|
151
272
|
reflect: true
|
|
@@ -167,8 +288,12 @@ __decorate([property({
|
|
|
167
288
|
attribute: "fit-size"
|
|
168
289
|
})], Select.prototype, "fitSize", void 0);
|
|
169
290
|
__decorate([property({ type: String })], Select.prototype, "placeholder", void 0);
|
|
291
|
+
__decorate([property({ attribute: "aria-label" })], Select.prototype, "ariaLabel", void 0);
|
|
292
|
+
__decorate([property({ attribute: "aria-labelledby" })], Select.prototype, "ariaLabelledByAttr", void 0);
|
|
293
|
+
__decorate([state()], Select.prototype, "labelText", void 0);
|
|
170
294
|
__decorate([property({ type: Boolean })], Select.prototype, "multiple", void 0);
|
|
171
295
|
__decorate([property({ type: Boolean })], Select.prototype, "required", void 0);
|
|
296
|
+
__decorate([property({ type: String })], Select.prototype, "name", void 0);
|
|
172
297
|
__decorate([property({
|
|
173
298
|
converter: arrayPropertyConverter,
|
|
174
299
|
reflect: true
|
package/package.json
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c2n/select",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/select.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"types": "./types/src/select.d.ts",
|
|
9
|
+
"default": "./dist/select.js"
|
|
10
|
+
},
|
|
11
|
+
"./react": {
|
|
12
|
+
"types": "./react.d.ts",
|
|
13
|
+
"default": "./react.js"
|
|
14
|
+
},
|
|
15
|
+
"./vue": {
|
|
16
|
+
"types": "./vue.d.ts",
|
|
17
|
+
"default": "./vue.js"
|
|
10
18
|
},
|
|
11
19
|
"./custom-elements.json": "./custom-elements.json"
|
|
12
20
|
},
|
|
13
21
|
"files": [
|
|
14
22
|
"dist",
|
|
15
|
-
"types"
|
|
23
|
+
"types",
|
|
24
|
+
"react.d.ts",
|
|
25
|
+
"react.js",
|
|
26
|
+
"vue.d.ts",
|
|
27
|
+
"vue.js"
|
|
16
28
|
],
|
|
17
29
|
"keywords": [
|
|
18
30
|
"select",
|
|
@@ -50,15 +62,15 @@
|
|
|
50
62
|
}
|
|
51
63
|
},
|
|
52
64
|
"dependencies": {
|
|
53
|
-
"@c2n/core": "0.0.
|
|
54
|
-
"@c2n/list": "0.0.
|
|
55
|
-
"@c2n/list-item": "0.0.
|
|
56
|
-
"@c2n/overlay": "0.0.
|
|
65
|
+
"@c2n/core": "0.0.7",
|
|
66
|
+
"@c2n/list": "0.0.7",
|
|
67
|
+
"@c2n/list-item": "0.0.7",
|
|
68
|
+
"@c2n/overlay": "0.0.7",
|
|
57
69
|
"lit": "3.3.3"
|
|
58
70
|
},
|
|
59
71
|
"devDependencies": {
|
|
60
72
|
"@c2n/config": "*"
|
|
61
73
|
},
|
|
62
74
|
"customElements": "custom-elements.json",
|
|
63
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "c8db398a27f7cd417d665fdf5da455fee2d4e910"
|
|
64
76
|
}
|
package/react.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// GENERATED by @c2n/framework-types. Do not edit by hand.
|
|
2
|
+
//
|
|
3
|
+
// JSX types for the custom elements of @c2n/select. Import once, anywhere in the program:
|
|
4
|
+
//
|
|
5
|
+
// import '@c2n/select/react'
|
|
6
|
+
//
|
|
7
|
+
// Register the elements at module scope before React renders, or React writes an object prop as an
|
|
8
|
+
// attribute and it stringifies.
|
|
9
|
+
|
|
10
|
+
import type { DetailedHTMLProps, HTMLAttributes } from 'react'
|
|
11
|
+
import type { Select } from '@c2n/select'
|
|
12
|
+
|
|
13
|
+
/** Standard React host-element attributes plus the element's own public properties. */
|
|
14
|
+
type C2Props<T> = DetailedHTMLProps<HTMLAttributes<T>, T> & Partial<Omit<T, keyof HTMLElement>>
|
|
15
|
+
|
|
16
|
+
declare module 'react' {
|
|
17
|
+
namespace JSX {
|
|
18
|
+
interface IntrinsicElements {
|
|
19
|
+
'c2-select': C2Props<Select>
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {}
|
package/react.js
ADDED
package/types/src/select.d.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { LitElement, type PropertyValueMap } from 'lit';
|
|
2
|
+
import type { TypedAddEventListener, TypedRemoveEventListener } from '@c2n/core/event-helper.js';
|
|
3
|
+
import type { SelectionChangeEventDetail } from '@c2n/list';
|
|
2
4
|
import '@c2n/overlay';
|
|
3
5
|
import '@c2n/list';
|
|
6
|
+
/** Events fired by {@link Select}, keyed for `addEventListener`. */
|
|
7
|
+
export interface SelectEventMap {
|
|
8
|
+
'selection-change': CustomEvent<SelectionChangeEventDetail>;
|
|
9
|
+
input: Event;
|
|
10
|
+
change: Event;
|
|
11
|
+
}
|
|
12
|
+
export interface Select {
|
|
13
|
+
addEventListener: TypedAddEventListener<Select, SelectEventMap>;
|
|
14
|
+
removeEventListener: TypedRemoveEventListener<Select, SelectEventMap>;
|
|
15
|
+
}
|
|
4
16
|
/**
|
|
5
17
|
* A dropdown built from a trigger button and a `c2-list` of `c2-list-item` rows inside a `c2-overlay` popover. The
|
|
6
18
|
* rows are the light-DOM children of the select, so they are themed with the `--c2-list-item--*` variables directly on
|
|
@@ -15,7 +27,9 @@ import '@c2n/list';
|
|
|
15
27
|
* @slot button-suffix-icon - Icon shown at the end of the trigger. Defaults to a chevron that flips while open.
|
|
16
28
|
* @slot button-content - Replaces the trigger text entirely (selected labels or placeholder) with custom markup.
|
|
17
29
|
*
|
|
18
|
-
* @event {CustomEvent<SelectionChangeEventDetail>} selection-change - Fired after the user picks an option. `detail.value` is the array of selected values (one entry unless `multiple`), `detail.data` the `data` of each selected row.
|
|
30
|
+
* @event {CustomEvent<SelectionChangeEventDetail>} selection-change - Fired after the user picks an option. `detail.value` is the array of selected values (one entry unless `multiple`), `detail.data` the `data` of each selected row. Does not bubble: several components fire `selection-change`, so a listener belongs on the element itself rather than on an ancestor.
|
|
31
|
+
* @event {Event} input - Fired with `selection-change`, after `value` is updated. Carries no detail: it exists so the select behaves like the other form controls, which is what `v-model`, `ngModel` and any generic form binding listen for.
|
|
32
|
+
* @event {Event} change - Fired with `input`. A select commits on every pick, so the two always fire together.
|
|
19
33
|
*
|
|
20
34
|
* @cssproperty {pixel} [--c2-select__button--min-height=36px]
|
|
21
35
|
* @cssproperty {padding} [--c2-select__button--padding=6px 10px 6px 12px]
|
|
@@ -51,7 +65,7 @@ import '@c2n/list';
|
|
|
51
65
|
* @cssproperty {border} [--c2-select__button__open--border-left=1px solid #476ef9]
|
|
52
66
|
*
|
|
53
67
|
* @cssproperty {outline} [--c2-select__button__focus--outline=2px solid rgba(71, 110, 249, 0.4)]
|
|
54
|
-
* @cssproperty {pixel} [--c2-select__button__focus--outline-offset=
|
|
68
|
+
* @cssproperty {pixel} [--c2-select__button__focus--outline-offset=0px] - Flush by default: `[open]` already turns the border accent, so an offset ring reads as a second border.
|
|
55
69
|
*
|
|
56
70
|
* @cssproperty {opacity} [--c2-select__button__disabled--opacity=0.38]
|
|
57
71
|
*
|
|
@@ -82,6 +96,7 @@ import '@c2n/list';
|
|
|
82
96
|
* @cssproperty {padding} [--c2-select__list--padding-right=4px]
|
|
83
97
|
* @cssproperty {padding} [--c2-select__list--padding-bottom=4px]
|
|
84
98
|
* @cssproperty {padding} [--c2-select__list--padding-left=4px]
|
|
99
|
+
* @cssproperty {pixel} [--c2-select__list--min-width=auto] - Floor for the dropdown's width; it is never narrower than the trigger.
|
|
85
100
|
* @cssproperty {pixel} [--c2-select__list--max-height=280px] - The dropdown scrolls past this height.
|
|
86
101
|
*
|
|
87
102
|
* @internalcomponent c2-list
|
|
@@ -90,6 +105,7 @@ import '@c2n/list';
|
|
|
90
105
|
* @slotcomponent c2-list-item
|
|
91
106
|
*/
|
|
92
107
|
export declare class Select extends LitElement {
|
|
108
|
+
static formAssociated: boolean;
|
|
93
109
|
static styles: import("lit").CSSResult;
|
|
94
110
|
static shadowRootOptions: {
|
|
95
111
|
delegatesFocus: boolean;
|
|
@@ -99,6 +115,11 @@ export declare class Select extends LitElement {
|
|
|
99
115
|
serializable?: boolean;
|
|
100
116
|
slotAssignment?: SlotAssignmentMode;
|
|
101
117
|
};
|
|
118
|
+
private readonly internals;
|
|
119
|
+
private customValidityMessage;
|
|
120
|
+
private disabledByForm;
|
|
121
|
+
private defaultValue;
|
|
122
|
+
private defaultValueCaptured;
|
|
102
123
|
/** Whether the dropdown is showing. Reflects the popover state; set it to open or close programmatically. */
|
|
103
124
|
open: boolean;
|
|
104
125
|
/** Shows the current value but never opens the dropdown. */
|
|
@@ -111,18 +132,46 @@ export declare class Select extends LitElement {
|
|
|
111
132
|
fitSize: boolean;
|
|
112
133
|
/** Text shown in the trigger while nothing is selected. */
|
|
113
134
|
placeholder: string;
|
|
135
|
+
/** Accessible name of the trigger and option list. */
|
|
136
|
+
ariaLabel: string | null;
|
|
137
|
+
/**
|
|
138
|
+
* Mirrors the host's `aria-labelledby` so the trigger re-renders when a label claims the element — a
|
|
139
|
+
* `c2-label[for]` sets it after both have upgraded.
|
|
140
|
+
*/
|
|
141
|
+
ariaLabelledByAttr: string | null;
|
|
142
|
+
/** Text of whatever labels the host, resolved once the DOM settles; see {@link triggerLabel}. */
|
|
143
|
+
private labelText;
|
|
114
144
|
/** Allow several options to be selected; the dropdown stays open after a pick and the trigger lists every label. */
|
|
115
145
|
multiple: boolean;
|
|
116
146
|
/** Keep at least one option selected: picking the only selected option again does not clear it. */
|
|
117
147
|
required: boolean;
|
|
148
|
+
/** Name used when the select participates in a form. */
|
|
149
|
+
name: string;
|
|
118
150
|
/** Selected values. Written as `value="a;b"` in markup, read as `['a', 'b']` from the property. */
|
|
119
151
|
value: string[];
|
|
152
|
+
/** Scalar convenience API for a single-select. Setting it replaces the current selection. */
|
|
153
|
+
get selectedValue(): string;
|
|
154
|
+
set selectedValue(value: string);
|
|
155
|
+
/** The containing form, when this control is associated with one. */
|
|
156
|
+
get form(): HTMLFormElement | null;
|
|
157
|
+
/** Labels associated with this form control. */
|
|
158
|
+
get labels(): NodeList;
|
|
159
|
+
get validity(): ValidityState;
|
|
160
|
+
get validationMessage(): string;
|
|
161
|
+
get willValidate(): boolean;
|
|
120
162
|
button: HTMLButtonElement;
|
|
121
163
|
menu: HTMLElement;
|
|
122
164
|
listItemSlot?: HTMLSlotElement;
|
|
123
165
|
private displayText;
|
|
124
166
|
protected childItemsUpdated: Promise<unknown[]>;
|
|
125
167
|
data: unknown[];
|
|
168
|
+
connectedCallback(): void;
|
|
169
|
+
formResetCallback(): void;
|
|
170
|
+
formDisabledCallback(disabled: boolean): void;
|
|
171
|
+
formStateRestoreCallback(state: string | File | FormData | null): void;
|
|
172
|
+
checkValidity(): boolean;
|
|
173
|
+
reportValidity(): boolean;
|
|
174
|
+
setCustomValidity(message: string): void;
|
|
126
175
|
private onButtonBlur;
|
|
127
176
|
private onKeydown;
|
|
128
177
|
toggle(value?: boolean): void;
|
|
@@ -135,8 +184,21 @@ export declare class Select extends LitElement {
|
|
|
135
184
|
private handleOverlayToggle;
|
|
136
185
|
protected willUpdate(changedProperties: PropertyValueMap<this>): void;
|
|
137
186
|
protected updated(_changedProperties: PropertyValueMap<this>): void;
|
|
187
|
+
private syncFormState;
|
|
188
|
+
private get effectiveDisabled();
|
|
138
189
|
protected getUpdateComplete(): Promise<boolean>;
|
|
139
190
|
private handleSlotChange;
|
|
191
|
+
/**
|
|
192
|
+
* The accessible name for the trigger.
|
|
193
|
+
*
|
|
194
|
+
* The focusable control is a `<button>` inside the shadow root, and neither of the two ways an author
|
|
195
|
+
* names this element reaches it on its own: `aria-labelledby` on the host points at an id in the light DOM,
|
|
196
|
+
* which ARIA cannot resolve across the shadow boundary, and a wrapping `<label>` names the host rather than
|
|
197
|
+
* the button. So the label's *text* is copied onto the button instead.
|
|
198
|
+
*/
|
|
199
|
+
private get triggerLabel();
|
|
200
|
+
/** Reads the text of whatever labels the host: `aria-labelledby` first, then any associated `<label>`. */
|
|
201
|
+
private syncLabelText;
|
|
140
202
|
render(): import("lit-html").TemplateResult<1>;
|
|
141
203
|
}
|
|
142
204
|
declare global {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/select.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/select.ts"],"names":[],"mappings":"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"}
|
package/vue.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// GENERATED by @c2n/framework-types. Do not edit by hand.
|
|
2
|
+
//
|
|
3
|
+
// Vue template types for the custom elements of @c2n/select. Import once, anywhere in the program:
|
|
4
|
+
//
|
|
5
|
+
// import '@c2n/select/vue'
|
|
6
|
+
//
|
|
7
|
+
// Tell the compiler about the tags as well, or every c2-* tag is resolved as a Vue component and renders
|
|
8
|
+
// nothing: template.compilerOptions.isCustomElement = (tag) => tag.startsWith('c2-') in vite.config.ts.
|
|
9
|
+
|
|
10
|
+
import type { DefineComponent, HTMLAttributes } from 'vue'
|
|
11
|
+
import type { Select, SelectEventMap } from '@c2n/select'
|
|
12
|
+
|
|
13
|
+
/** The element's own public properties, plus every attribute Vue understands on a host element. */
|
|
14
|
+
type C2Props<T> = Partial<Omit<T, keyof HTMLElement>> & HTMLAttributes
|
|
15
|
+
|
|
16
|
+
declare module 'vue' {
|
|
17
|
+
interface GlobalComponents {
|
|
18
|
+
'c2-select': DefineComponent<
|
|
19
|
+
C2Props<Select> & {
|
|
20
|
+
'fit-size'?: unknown
|
|
21
|
+
'aria-label'?: unknown
|
|
22
|
+
'aria-labelledby'?: unknown
|
|
23
|
+
onInput?: (event: SelectEventMap['input']) => void
|
|
24
|
+
onChange?: (event: SelectEventMap['change']) => void
|
|
25
|
+
onSelectionChange?: (event: SelectEventMap['selection-change']) => void
|
|
26
|
+
}
|
|
27
|
+
>
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module '@vue/runtime-dom' {
|
|
32
|
+
interface HTMLAttributes {
|
|
33
|
+
/** Vue's own definition omits it, and slotting a plain element into a component needs it. */
|
|
34
|
+
slot?: string
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export {}
|
package/vue.js
ADDED