+
Skip to content
This repository was archived by the owner on Oct 14, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cyan-bugs-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@crowdstrike/glide-core': patch
---

Select now supports multiselection via a new `multiple` attribute.
40 changes: 40 additions & 0 deletions custom-elements.json
Original file line number Diff line number Diff line change
Expand Up @@ -5321,6 +5321,16 @@
"attribute": "id",
"reflects": true
},
{
"kind": "field",
"name": "multiple",
"type": {
"text": "boolean"
},
"default": "false",
"attribute": "multiple",
"reflects": true
},
{
"kind": "field",
"name": "privateActive",
Expand Down Expand Up @@ -5391,6 +5401,12 @@
}
],
"events": [
{
"name": "click",
"type": {
"text": "Event"
}
},
{
"name": "disabled",
"type": {
Expand Down Expand Up @@ -5457,6 +5473,14 @@
"readonly": true,
"fieldName": "id"
},
{
"name": "multiple",
"type": {
"text": "boolean"
},
"default": "false",
"fieldName": "multiple"
},
{
"name": "privateActive",
"fieldName": "privateActive"
Expand Down Expand Up @@ -6768,6 +6792,15 @@
"attribute": "loading",
"reflects": true
},
{
"kind": "field",
"name": "multiple",
"type": {
"text": "boolean"
},
"attribute": "multiple",
"reflects": true
},
{
"kind": "field",
"name": "name",
Expand Down Expand Up @@ -6940,6 +6973,13 @@
"default": "false",
"fieldName": "loading"
},
{
"name": "multiple",
"type": {
"text": "boolean"
},
"fieldName": "multiple"
},
{
"name": "name",
"type": {
Expand Down
26 changes: 17 additions & 9 deletions src/option.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,24 +69,32 @@ export default [
grid-template-columns: 1fr auto;
}

&.selected {
grid-template-columns: 1fr auto;
&:not(.multiple) {
&.selected {
grid-template-columns: 1fr auto;

&.icon {
grid-template-columns: auto 1fr auto;
&.icon {
grid-template-columns: auto 1fr auto;

&.submenu {
grid-template-columns: auto 1fr auto auto;
&.submenu {
grid-template-columns: auto 1fr auto auto;
}
}
}

&.submenu {
grid-template-columns: 1fr auto auto;
&.submenu {
grid-template-columns: 1fr auto auto;
}
}
}
}
}

.checkbox {
align-items: center;
column-gap: var(--glide-core-spacing-base-xs);
display: flex;
}

.label {
overflow-x: hidden;
text-overflow: ellipsis;
Expand Down
38 changes: 38 additions & 0 deletions src/option.test.interactions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { aTimeout, expect, fixture, html } from '@open-wc/testing';
import Option from './option.js';
import type Tooltip from './tooltip.js';
import { click } from './library/mouse.js';

it('sets `ariaDisabled` when enabled programmatically', async () => {
const host = await fixture<Option>(
Expand Down Expand Up @@ -180,3 +181,40 @@ it('disables its tooltip when its description is changed programmatically and no
await aTimeout(0); // Wait for the timeout in the setter
expect(tooltip?.disabled).to.be.true;
});

it('checks its checkbox on click', async () => {
const host = await fixture<Option>(
html`<glide-core-option
label="Label"
role="option"
multiple
></glide-core-option>`,
);

const checkbox = host.shadowRoot?.querySelector<HTMLInputElement>(
'[data-test="checkbox"]',
);

await click(checkbox);

expect(checkbox?.checked).to.be.true;
});

it('unchecks its checkbox on click', async () => {
const host = await fixture<Option>(
html`<glide-core-option
label="Label"
role="option"
multiple
selected
></glide-core-option>`,
);

const checkbox = host.shadowRoot?.querySelector<HTMLInputElement>(
'[data-test="checkbox"]',
);

await click(checkbox);

expect(checkbox?.checked).to.be.false;
});
64 changes: 53 additions & 11 deletions src/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import final from './library/final.js';
import uniqueId from './library/unique-id.js';
import Menu from './menu.js';
import './options.js';
import './checkbox.js';
import Tooltip from './tooltip.js';
import required from './library/required.js';

Expand All @@ -31,6 +32,7 @@ declare global {
* @readonly
* @attr {string} [id]
*
* @attr {boolean} [multiple=false]
* @attr {'menuitem'|'option'} [role='menuitem']
* @attr {boolean} [selected=false]
*
Expand All @@ -46,6 +48,7 @@ declare global {
* @slot {Element} [icon]
* @slot {Menu} [submenu]
*
* @fires {Event} click
* @fires {Event} deselected
* @fires {Event} disabled
* @fires {Event} enabled
Expand All @@ -59,7 +62,7 @@ export default class Option extends LitElement {
...LitElement.shadowRootOptions,
mode: window.navigator.webdriver ? 'open' : 'closed',
};
/* c8 ignore end */
/* c8 ignore stop */

static override styles = styles;

Expand Down Expand Up @@ -132,6 +135,9 @@ export default class Option extends LitElement {
@property({ reflect: true })
override readonly id: string = uniqueId();

@property({ reflect: true, type: Boolean })
multiple = false;

@property({ type: Boolean, useDefault: true })
get privateActive() {
return this.#isActive;
Expand Down Expand Up @@ -266,6 +272,7 @@ export default class Option extends LitElement {
'content-slot': true,
fallback: !this.hasContentSlot,
icon: this.hasIconSlot,
multiple: this.multiple,
selected: this.selected && this.role === 'option',
submenu: this.hasSubMenuSlot,
})}
Expand Down Expand Up @@ -297,15 +304,37 @@ export default class Option extends LitElement {
<!-- @type {Element} -->
</slot>

<div
class=${classMap({
label: true,
bold: Boolean(this.description),
})}
${ref(this.#labelElementRef)}
>
${this.label}
</div>
${when(
this.role === 'option' && this.multiple,
() => {
return html`
<div class="checkbox">
<glide-core-checkbox
label=${ifDefined(this.label)}
data-test="checkbox"
hide-label
.checked=${this.selected}
?disabled=${this.disabled}
@click=${this.#onCheckboxClick}
></glide-core-checkbox>

<span aria-hidden="true"> ${this.label} </span>
</div>
`;
},
() => {
return html`<div
class=${classMap({
label: true,
bold: Boolean(this.description),
})}
${ref(this.#labelElementRef)}
>
${this.label}
</div>`;
},
)}


<slot
class=${classMap({
Expand All @@ -324,7 +353,10 @@ export default class Option extends LitElement {
</slot>

${when(
this.selected && !this.disabled && this.role === 'option',
!this.multiple &&
this.selected &&
!this.disabled &&
this.role === 'option',
() => {
return html`<div
class="checked-icon-container"
Expand Down Expand Up @@ -430,6 +462,16 @@ export default class Option extends LitElement {
}
}

#onCheckboxClick(event: Event) {
event.stopPropagation();

// So consumers of Option who cancel the event to prevent Menu from closing don't
// stop the checkbox from being checked or unchecked.
this.dispatchEvent(
new Event('click', { bubbles: true, cancelable: true, composed: true }),
);
}

#onContentSlotChange(event: Event) {
if (event.target === this.#contentSlotElementRef.value) {
this.hasContentSlot =
Expand Down
14 changes: 13 additions & 1 deletion src/select.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const meta: Meta = {
'checkValidity()': '',
disabled: false,
loading: false,
multiple: false,
name: '',
offset: 4,
open: false,
Expand Down Expand Up @@ -121,6 +122,14 @@ const meta: Meta = {
},
},
},
multiple: {
table: {
defaultValue: { summary: 'false' },
type: {
summary: 'boolean',
},
},
},
name: {
table: {
defaultValue: { summary: '""' },
Expand Down Expand Up @@ -440,11 +449,12 @@ const meta: Meta = {
placement=${arguments_.placement === 'bottom-start'
? nothing
: arguments_.placement}
.value=${arguments_.value}
?disabled=${arguments_.disabled}
?loading=${arguments_.loading}
?multiple=${arguments_.multiple}
?open=${arguments_.open}
?required=${arguments_.required}
.value=${arguments_.value}
>
<glide-core-icon-button label="Toggle" slot="target">
<glide-core-example-icon name="chevron-down"></glide-core-example-icon>
Expand Down Expand Up @@ -537,6 +547,7 @@ export const WithGroups: StoryObj = {
: arguments_.placement}
?disabled=${arguments_.disabled}
?loading=${arguments_.loading}
?multiple=${arguments_.multiple}
?open=${arguments_.open}
?required=${arguments_.required}
.value=${arguments_.value}
Expand Down Expand Up @@ -657,6 +668,7 @@ export const WithIcons: StoryObj = {
: arguments_.placement}
?disabled=${arguments_.disabled}
?loading=${arguments_.loading}
?multiple=${arguments_.multiple}
?open=${arguments_.open}
?required=${arguments_.required}
.value=${arguments_.value}
Expand Down
Loading
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载