Merge branch 'next' into themer-rework

This commit is contained in:
Cory LaViska
2025-06-23 13:48:07 -04:00
15 changed files with 212 additions and 358 deletions

View File

@@ -5,17 +5,4 @@ layout: component
category: Navigation
---
```html {.example}
<wa-breadcrumb>
<wa-breadcrumb-item>
<wa-icon slot="start" name="house" variant="solid"></wa-icon>
Home
</wa-breadcrumb-item>
<wa-breadcrumb-item>Clothing</wa-breadcrumb-item>
<wa-breadcrumb-item>Shirts</wa-breadcrumb-item>
</wa-breadcrumb>
```
:::info
Additional demonstrations can be found in the [breadcrumb examples](/docs/components/breadcrumb).
:::
This component must be used as a child of `<wa-breadcrumb>`. Please see the [Breadcrumb docs](/docs/components/breadcrumb) to see examples of this component in action.

View File

@@ -5,41 +5,4 @@ layout: component
category: Imagery
---
```html {.example}
<wa-carousel pagination>
<wa-carousel-item>
<img
alt="The sun shines on the mountains and trees - Photo by Adam Kool on Unsplash"
src="/assets/examples/carousel/mountains.jpg"
/>
</wa-carousel-item>
<wa-carousel-item>
<img
alt="A waterfall in the middle of a forest - Photo by Thomas Kelly on Unsplash"
src="/assets/examples/carousel/waterfall.jpg"
/>
</wa-carousel-item>
<wa-carousel-item>
<img
alt="The sun is setting over a lavender field - Photo by Leonard Cotte on Unsplash"
src="/assets/examples/carousel/sunset.jpg"
/>
</wa-carousel-item>
<wa-carousel-item>
<img
alt="A field of grass with the sun setting in the background - Photo by Sapan Patel on Unsplash"
src="/assets/examples/carousel/field.jpg"
/>
</wa-carousel-item>
<wa-carousel-item>
<img
alt="A scenic view of a mountain with clouds rolling in - Photo by V2osk on Unsplash"
src="/assets/examples/carousel/valley.jpg"
/>
</wa-carousel-item>
</wa-carousel>
```
:::info
Additional demonstrations can be found in the [carousel examples](/docs/components/carousel).
:::
This component must be used as a child of `<wa-carousel>`. Please see the [Carousel docs](/docs/components/carousel) to see examples of this component in action.

View File

@@ -5,50 +5,4 @@ layout: component
category: Form Controls
---
```html {.example}
<wa-select label="Select one">
<wa-option value="option-1">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
</wa-select>
```
## Examples
### Disabled
Use the `disabled` attribute to disable an option and prevent it from being selected.
```html {.example}
<wa-select label="Select one">
<wa-option value="option-1">Option 1</wa-option>
<wa-option value="option-2" disabled>Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
</wa-select>
```
### Start & End Decorations
Use the `start` and `end` slots to add presentational elements like `<wa-icon>` next to the option label.
```html {.example}
<wa-select label="Select one">
<wa-option value="option-1">
<wa-icon slot="start" name="envelope"></wa-icon>
Email
<wa-icon slot="end" name="circle-check"></wa-icon>
</wa-option>
<wa-option value="option-2">
<wa-icon slot="start" name="phone"></wa-icon>
Phone
<wa-icon slot="end" name="circle-check"></wa-icon>
</wa-option>
<wa-option value="option-3">
<wa-icon slot="start" name="comment"></wa-icon>
Chat
<wa-icon slot="end" name="circle-check"></wa-icon>
</wa-option>
</wa-select>
```
This component must be used as a child of `<wa-select>`. Please see the [Select docs](/docs/components/select) to see examples of this component in action.

View File

@@ -5,70 +5,4 @@ layout: component
category: Form Controls
---
Radios are designed to be used with [radio groups](/docs/components/radio-group).
```html {.example}
<wa-radio-group label="Select an option" name="a" value="1">
<wa-radio value="1">Option 1</wa-radio>
<wa-radio value="2">Option 2</wa-radio>
<wa-radio value="3">Option 3</wa-radio>
</wa-radio-group>
```
:::info
This component works with standard `<form>` elements. Please refer to the section on [form controls](/docs/form-controls) to learn more about form submission and client-side validation.
:::
## Examples
### Initial Value
To set the initial value and checked state, use the `value` attribute on the containing radio group.
```html {.example}
<wa-radio-group label="Select an option" name="a" value="3">
<wa-radio value="1">Option 1</wa-radio>
<wa-radio value="2">Option 2</wa-radio>
<wa-radio value="3">Option 3</wa-radio>
</wa-radio-group>
```
### Disabled
Use the `disabled` attribute to disable a radio.
```html {.example}
<wa-radio-group label="Select an option" name="a" value="1">
<wa-radio value="1">Option 1</wa-radio>
<wa-radio value="2" disabled>Option 2</wa-radio>
<wa-radio value="3">Option 3</wa-radio>
</wa-radio-group>
```
### Sizes
Add the `size` attribute to the [Radio Group](/docs/components/radio-group) to change the radios' size.
```html {.example}
<wa-radio-group size="small" value="1">
<wa-radio value="1">Small 1</wa-radio>
<wa-radio value="2">Small 2</wa-radio>
<wa-radio value="3">Small 3</wa-radio>
</wa-radio-group>
<br />
<wa-radio-group size="medium" value="1">
<wa-radio value="1">Medium 1</wa-radio>
<wa-radio value="2">Medium 2</wa-radio>
<wa-radio value="3">Medium 3</wa-radio>
</wa-radio-group>
<br />
<wa-radio-group size="large" value="1">
<wa-radio value="1">Large 1</wa-radio>
<wa-radio value="2">Large 2</wa-radio>
<wa-radio value="3">Large 3</wa-radio>
</wa-radio-group>
```
This component must be used as a child of `<wa-radio-group>`. Please see the [Radio Group docs](/docs/components/radio-group) to see examples of this component in action.

View File

@@ -108,13 +108,13 @@ Use the `disabled` attribute to disable a select.
### Multiple
To allow multiple options to be selected, use the `multiple` attribute. It's a good practice to use `with-clear` when this option is enabled. To set multiple values at once, set `value` to a space-delimited list of values.
To allow multiple options to be selected, use the `multiple` attribute. It's a good practice to use `with-clear` when this option is enabled. You can select multiple options by adding the `selected` attribute to individual options.
```html {.example}
<wa-select label="Select a Few" value="option-1 option-2 option-3" multiple with-clear>
<wa-option value="option-1">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
<wa-select label="Select a Few" multiple with-clear>
<wa-option value="option-1" selected>Option 1</wa-option>
<wa-option value="option-2" selected>Option 2</wa-option>
<wa-option value="option-3" selected>Option 3</wa-option>
<wa-option value="option-4">Option 4</wa-option>
<wa-option value="option-5">Option 5</wa-option>
<wa-option value="option-6">Option 6</wa-option>
@@ -122,33 +122,37 @@ To allow multiple options to be selected, use the `multiple` attribute. It's a g
```
:::info
Note that multi-select options may wrap, causing the control to expand vertically. You can use the `max-options-visible` attribute to control the maximum number of selected options to show at once.
Selecting multiple options may result in wrapping, causing the control to expand vertically. You can use the `max-options-visible` attribute to control the maximum number of selected options to show at once.
:::
### Setting Initial Values
Use the `value` attribute to set the initial selection.
Use the `selected` attribute on individual options to set the initial selection, similar to native HTML.
```html {.example}
<wa-select value="option-1">
<wa-option value="option-1">Option 1</wa-option>
<wa-select>
<wa-option value="option-1" selected>Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
<wa-option value="option-4">Option 4</wa-option>
</wa-select>
```
When using `multiple`, the `value` _attribute_ uses space-delimited values to select more than one option. Because of this, `<wa-option>` values cannot contain spaces. If you're accessing the `value` _property_ through Javascript, it will be an array.
For multiple selections, apply it to all selected options.
```html {.example}
<wa-select value="option-1 option-2" multiple with-clear>
<wa-option value="option-1">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-select multiple with-clear>
<wa-option value="option-1" selected>Option 1</wa-option>
<wa-option value="option-2" selected>Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
<wa-option value="option-4">Option 4</wa-option>
</wa-select>
```
:::info
Framework users can bind directly to the `value` property for reactive data binding and form state management.
:::
### Grouping Options
Use `<wa-divider>` to group listbox items visually. You can also use `<small>` to provide labels, but they won't be announced by most assistive devices.
@@ -239,17 +243,17 @@ Use the `start` and `end` slots to add presentational elements like `<wa-icon>`
### Custom Tags
When multiple options can be selected, you can provide custom tags by passing a function to the `getTag` property. Your function can return a string of HTML, a <a href="https://lit.dev/docs/templates/overview/">Lit Template</a>, or an [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement). The `getTag()` function will be called for each option. The first argument is an `<wa-option>` element and the second argument is the tag's index (its position in the tag list).
When multiple options can be selected, you can provide custom tags by passing a function to the `getTag` property. Your function can return a string of HTML, a [Lit Template](https://lit.dev/docs/templates/overview/), or an [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement). The `getTag()` function will be called for each option. The first argument is an `<wa-option>` element and the second argument is the tag's index (its position in the tag list).
Remember that custom tags are rendered in a shadow root. To style them, you can use the `style` attribute in your template or you can add your own [parts](/docs/customizing/#css-parts) and target them with the [`::part()`](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) selector.
```html {.example}
<wa-select placeholder="Select one" value="email phone" multiple with-clear class="custom-tag">
<wa-option value="email">
<wa-select placeholder="Select one" multiple with-clear class="custom-tag">
<wa-option value="email" selected>
<wa-icon slot="start" name="envelope" variant="solid"></wa-icon>
Email
</wa-option>
<wa-option value="phone">
<wa-option value="phone" selected>
<wa-icon slot="start" name="phone" variant="solid"></wa-icon>
Phone
</wa-option>
@@ -285,17 +289,15 @@ Be sure you trust the content you are outputting! Passing unsanitized user input
### Lazy loading options
Lazy loading options is very hard to get right. `<wa-select>` largely follows how a native `<select>` works.
Lazy loading options works similarly to native `<select>` elements. The select component handles various scenarios intelligently:
Here are the following conditions:
#### Basic lazy loading scenarios:
- If a `<wa-select>` is created without any options, but is given a `value` attribute, its `value` will be `""`, and then when options are added, if any of the options have a value equal to the `<wa-select>` value, the value of the `<wa-select>` will equal that of the option.
- **Empty select with value**: If a `<wa-select>` is created without any options but given a `value` attribute, its value will be `""` initially. When options are added later, if any option has a value matching the select's value attribute, the select's value will update to match.
EX: `<wa-select value="foo">` will have a value of `""` until `<wa-option value="foo">Foo</wa-option>` connects, at which point its value will become `"foo"` when submitting.
- **Multiple select with partial options**: If a `<wa-select multiple>` has an initial value with multiple options, but only some options are present in the DOM, it will respect only the available options. When additional selected options are loaded later (and the user hasn't changed the selection), those options will be automatically added to the selection.
- If a `<wa-select multiple>` with an initial value has multiple values, but only some of the options are present, it will only respect the options that are present, and if a selected option is loaded in later, _AND_ the value of the select has not changed via user interaction or direct property assignment, it will add the selected option to the form value and to the `.value` of the select.
This can be hard to conceptualize, so heres a fairly large example showing how lazy loaded options work with `<wa-select>` and `<wa-select multiple>` when given initial value attributes. Feel free to play around with it in a codepen.
Here's a comprehensive example showing different lazy loading scenarios:
```html {.example}
<form id="lazy-options-example">
@@ -319,12 +321,12 @@ This can be hard to conceptualize, so heres a fairly large example showing how l
<br />
<div>
<wa-select name="select-3" value="foo bar baz" multiple label="Multiple Select (with existing options)">
<wa-option value="bar">Bar</wa-option>
<wa-option value="baz">Baz</wa-option>
<wa-select name="select-3" multiple label="Multiple Select (with existing selected options)">
<wa-option value="bar" selected>Bar</wa-option>
<wa-option value="baz" selected>Baz</wa-option>
</wa-select>
<br />
<wa-button type="button">Add "foo" option</wa-button>
<wa-button type="button">Add "foo" option (selected)</wa-button>
</div>
<br />
@@ -365,6 +367,12 @@ This can be hard to conceptualize, so heres a fairly large example showing how l
const option = document.createElement('wa-option');
option.setAttribute('value', 'foo');
option.innerText = 'Foo';
// For the multiple select with existing selected options, make the new option selected
if (select.getAttribute('name') === 'select-3') {
option.selected = true;
}
select.append(option);
}
@@ -391,3 +399,7 @@ This can be hard to conceptualize, so heres a fairly large example showing how l
container.addEventListener('submit', handleLazySubmit);
</script>
```
:::info
The key principle is that the select component prioritizes user interactions and explicit selections over programmatic changes, ensuring a predictable user experience even with dynamically loaded content.
:::

View File

@@ -5,20 +5,4 @@ layout: component
category: Navigation
---
```html {.example}
<wa-tab-group>
<wa-tab panel="general">General</wa-tab>
<wa-tab panel="custom">Custom</wa-tab>
<wa-tab panel="advanced">Advanced</wa-tab>
<wa-tab panel="disabled" disabled>Disabled</wa-tab>
<wa-tab-panel name="general">This is the general tab panel.</wa-tab-panel>
<wa-tab-panel name="custom">This is the custom tab panel.</wa-tab-panel>
<wa-tab-panel name="advanced">This is the advanced tab panel.</wa-tab-panel>
<wa-tab-panel name="disabled">This is a disabled tab panel.</wa-tab-panel>
</wa-tab-group>
```
:::info
Additional demonstrations can be found in the [tab group examples](/docs/components/tab-group).
:::
This component must be used as a child of `<wa-tab-group>`. Please see the [Tab Group docs](/docs/components/tab-group) to see examples of this component in action.

View File

@@ -5,6 +5,4 @@ layout: component
category: Navigation
---
:::info
Additional demonstrations can be found in the [tab group examples](/docs/components/tab-group).
:::
This component must be used as a child of `<wa-tab-group>`. Please see the [Tab Group docs](/docs/components/tab-group) to see examples of this component in action.

View File

@@ -5,78 +5,4 @@ layout: component
category: Navigation
---
```html {.example}
<wa-tree>
<wa-tree-item>
Item 1
<wa-tree-item>Item A</wa-tree-item>
<wa-tree-item>Item B</wa-tree-item>
<wa-tree-item>Item C</wa-tree-item>
</wa-tree-item>
<wa-tree-item>Item 2</wa-tree-item>
<wa-tree-item>Item 3</wa-tree-item>
</wa-tree>
```
## Examples
### Nested tree items
A tree item can contain other tree items. This allows the node to be expanded or collapsed by the user.
```html {.example}
<wa-tree>
<wa-tree-item>
Item 1
<wa-tree-item>
Item A
<wa-tree-item>Item Z</wa-tree-item>
<wa-tree-item>Item Y</wa-tree-item>
<wa-tree-item>Item X</wa-tree-item>
</wa-tree-item>
<wa-tree-item>Item B</wa-tree-item>
<wa-tree-item>Item C</wa-tree-item>
</wa-tree-item>
<wa-tree-item>Item 2</wa-tree-item>
<wa-tree-item>Item 3</wa-tree-item>
</wa-tree>
```
### Selected
Use the `selected` attribute to select a tree item initially.
```html {.example}
<wa-tree>
<wa-tree-item selected>
Item 1
<wa-tree-item>Item A</wa-tree-item>
<wa-tree-item>Item B</wa-tree-item>
<wa-tree-item>Item C</wa-tree-item>
</wa-tree-item>
<wa-tree-item>Item 2</wa-tree-item>
<wa-tree-item>Item 3</wa-tree-item>
</wa-tree>
```
### Expanded
Use the `expanded` attribute to expand a tree item initially.
```html {.example}
<wa-tree>
<wa-tree-item expanded>
Item 1
<wa-tree-item expanded>
Item A
<wa-tree-item>Item Z</wa-tree-item>
<wa-tree-item>Item Y</wa-tree-item>
<wa-tree-item>Item X</wa-tree-item>
</wa-tree-item>
<wa-tree-item>Item B</wa-tree-item>
<wa-tree-item>Item C</wa-tree-item>
</wa-tree-item>
<wa-tree-item>Item 2</wa-tree-item>
<wa-tree-item>Item 3</wa-tree-item>
</wa-tree>
```
This component must be used as a child of `<wa-tree>`. Please see the [Tree docs](/docs/components/tree) to see examples of this component in action.

View File

@@ -60,6 +60,7 @@ During the alpha period, things might break! We take breaking changes very serio
- `<wa-input>`
- `<wa-select>`
- `<wa-option>`
- 🚨 BREAKING: reworked `<wa-select>` to use `<wa-option selected>` to set initially selected options, removing the "no spaces allowed" restrictions for option values
- Added a new free component: `<wa-popover>` (#2 of 14 per stretch goals)
- Added a new free component: `<wa-zoomable-frame>` (#3 of 14 per stretch goals)
- Added a `min-block-size` to `<wa-divider orientation="vertical">` to ensure the divider is visible regardless of container height [issue:675]

View File

@@ -6,7 +6,7 @@ import WebAwesomeElement from '../../internal/webawesome-element.js';
import styles from './breadcrumb-item.css';
/**
* @summary Breadcrumb Items are used inside [breadcrumbs](/docs/components/breadcrumb) to represent different links.
* @summary Breadcrumb Items are used inside breadcrumbs to represent different links.
* @documentation https://backers.webawesome.com/docs/components/breadcrumb-item
* @status stable
* @since 2.0

View File

@@ -4,7 +4,7 @@ import WebAwesomeElement from '../../internal/webawesome-element.js';
import styles from './carousel-item.css';
/**
* @summary A carousel item represent a slide within a [carousel](/docs/components/carousel).
* @summary A carousel item represent a slide within a carousel.
*
* @since 2.0
* @status experimental

View File

@@ -8,7 +8,7 @@ import '../icon/icon.js';
import styles from './option.css';
/**
* @summary Options define the selectable items within various form controls such as [select](/docs/components/select).
* @summary Options define the selectable items within a select component.
* @documentation https://backers.webawesome.com/docs/components/option
* @status stable
* @since 2.0
@@ -46,8 +46,6 @@ export default class WaOption extends WebAwesomeElement {
// Set via the parent select
@state() current = false;
@state() selected = false;
/**
* The option's value. When selected, the containing form control will receive this value. The value must be unique
* from other options in the same group. Values may not contain spaces, as spaces are used as delimiters when listing
@@ -56,7 +54,13 @@ export default class WaOption extends WebAwesomeElement {
@property({ reflect: true }) value = '';
/** Draws the option in a disabled state, preventing selection. */
@property({ type: Boolean, reflect: true }) disabled = false;
@property({ type: Boolean }) disabled = false;
/** @internal */
@property({ type: Boolean, attribute: false }) selected = false;
/** Selects an option initially. */
@property({ type: Boolean, attribute: 'selected' }) defaultSelected = false;
_label: string = '';
/**
@@ -107,10 +111,6 @@ export default class WaOption extends WebAwesomeElement {
private handleDefaultSlotChange() {
// Tell the controller to update the label
if (customElements.get('wa-select')) {
this.closest('wa-select')?.selectionChanged();
}
this.updateDefaultLabel();
if (this.isInitialized) {
@@ -119,6 +119,7 @@ export default class WaOption extends WebAwesomeElement {
const controller = this.closest('wa-select');
if (controller) {
controller.handleDefaultSlotChange();
controller.selectionChanged?.();
}
});
} else {
@@ -136,6 +137,17 @@ export default class WaOption extends WebAwesomeElement {
}
};
protected willUpdate(changedProperties: PropertyValues<this>): void {
if (changedProperties.has('defaultSelected')) {
if (!this.closest('wa-select')?.hasInteracted) {
const oldVal = this.selected;
this.selected = this.defaultSelected;
this.requestUpdate('selected', oldVal);
}
}
super.willUpdate(changedProperties);
}
updated(changedProperties: PropertyValues<this>) {
super.updated(changedProperties);
@@ -146,6 +158,7 @@ export default class WaOption extends WebAwesomeElement {
if (changedProperties.has('selected')) {
this.setAttribute('aria-selected', this.selected ? 'true' : 'false');
this.customStates.set('selected', this.selected);
this.handleDefaultSlotChange();
}
if (changedProperties.has('value')) {
@@ -155,12 +168,6 @@ export default class WaOption extends WebAwesomeElement {
this.value = String(this.value);
}
if (this.value.includes(' ')) {
// eslint-disable-next-line no-console
console.error(`Option values cannot include a space. All spaces have been replaced with underscores.`, this);
this.value = this.value.replace(/ /g, '_');
}
this.handleDefaultSlotChange();
}

View File

@@ -223,7 +223,7 @@ describe('<wa-select>', () => {
it('should not throw on incomplete events', async () => {
const el = await fixture<WaSelect>(html`
<wa-select required>
<sl-option value="option-1">Option 1</sl-option>
<wa-option value="option-1">Option 1</wa-option>
</wa-select>
`);
@@ -416,10 +416,10 @@ describe('<wa-select>', () => {
it('should serialize its name and value in FormData when multiple options are selected', async () => {
const form = await fixture<HTMLFormElement>(html`
<form>
<wa-select name="a" value="option-2 option-3" multiple>
<wa-select name="a" multiple>
<wa-option value="option-1">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
<wa-option value="option-2" selected>Option 2</wa-option>
<wa-option value="option-3" selected>Option 3</wa-option>
</wa-select>
</form>
`);
@@ -445,10 +445,10 @@ describe('<wa-select>', () => {
it('should serialize its name and value in JSON when multiple options are selected', async () => {
const form = await fixture<HTMLFormElement>(html`
<form>
<wa-select name="a" value="option-2 option-3" multiple>
<wa-select name="a" multiple>
<wa-option value="option-1">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
<wa-option value="option-2" selected>Option 2</wa-option>
<wa-option value="option-3" selected>Option 3</wa-option>
</wa-select>
</form>
`);
@@ -576,10 +576,10 @@ describe('<wa-select>', () => {
it('should emit change and input when a tag is removed', async () => {
const el = await fixture<WaSelect>(html`
<wa-select value="option-1 option-2 option-3" multiple>
<wa-option value="option-1">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
<wa-select multiple>
<wa-option value="option-1" selected>Option 1</wa-option>
<wa-option value="option-2" selected>Option 2</wa-option>
<wa-option value="option-3" selected>Option 3</wa-option>
</wa-select>
`);
const changeHandler = sinon.spy();
@@ -628,9 +628,9 @@ describe('<wa-select>', () => {
it('should have rounded tags when using the pill attribute', async () => {
const el = await fixture<WaSelect>(html`
<wa-select value="option-1 option-2" multiple pill>
<wa-option value="option-1">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-select multiple pill>
<wa-option value="option-1" selected>Option 1</wa-option>
<wa-option value="option-2" selected>Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
</wa-select>
`);
@@ -714,24 +714,17 @@ describe('<wa-select>', () => {
it('Should not select the option if options already exists for multiple select', async () => {
const form = await fixture<HTMLFormElement>(
html` <form>
<wa-select name="select" value="foo" multiple>
<wa-select name="select" multiple>
<wa-option value="bar">Bar</wa-option>
<wa-option value="baz">Baz</wa-option>
<wa-option value="foo" selected>Foo</wa-option>
</wa-select>
</form>`,
);
const el = form.querySelector<WaSelect>('wa-select')!;
expect(el.value).to.be.an('array');
expect(el.value!.length).to.equal(0);
const option = document.createElement('wa-option');
option.value = 'foo';
option.innerText = 'Foo';
el.append(option);
await aTimeout(10);
await el.updateComplete;
expect(el.value!.length).to.equal(1);
expect(el.value).to.have.members(['foo']);
expect(new FormData(form).getAll('select')).to.have.members(['foo']);
});
@@ -739,9 +732,9 @@ describe('<wa-select>', () => {
it('Should only select the existing options if options already exists for multiple select', async () => {
const form = await fixture<HTMLFormElement>(
html` <form>
<wa-select name="select" value="foo bar baz" multiple>
<wa-option value="bar">Bar</wa-option>
<wa-option value="baz">Baz</wa-option>
<wa-select name="select" multiple>
<wa-option value="bar" selected>Bar</wa-option>
<wa-option value="baz" selected>Baz</wa-option>
</wa-select>
</form>`,
);
@@ -756,12 +749,13 @@ describe('<wa-select>', () => {
const option = document.createElement('wa-option');
option.value = 'foo';
option.innerText = 'Foo';
option.selected = true;
el.append(option);
await aTimeout(10);
await el.updateComplete;
expect(el.value).to.have.members(['foo', 'bar', 'baz']);
expect(new FormData(form).getAll('select')).to.have.members(['foo', 'bar', 'baz']);
expect(el.value).to.have.members(['bar', 'baz', 'foo']);
expect(new FormData(form).getAll('select')).to.have.members(['bar', 'baz', 'foo']);
});
});
@@ -796,6 +790,74 @@ describe('<wa-select>', () => {
});
});
describe('with selected attribute', () => {
it('should select options using the selected attribute for single select', async () => {
const el = await fixture<WaSelect>(html`
<wa-select>
<wa-option value="option-1">Option 1</wa-option>
<wa-option value="option-2" selected>Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
</wa-select>
`);
expect(el.value).to.equal('option-2');
expect(el.displayInput.value).to.equal('Option 2');
});
it('should select multiple options using the selected attribute', async () => {
const el = await fixture<WaSelect>(html`
<wa-select multiple>
<wa-option value="option-1" selected>Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3" selected>Option 3</wa-option>
</wa-select>
`);
expect(el.value).to.have.members(['option-1', 'option-3']);
expect(el.value).to.have.length(2);
});
it('should handle options with spaces in values', async () => {
const el = await fixture<WaSelect>(html`
<wa-select>
<wa-option value="option with spaces">Option with spaces</wa-option>
<wa-option value="another option" selected>Another option</wa-option>
</wa-select>
`);
expect(el.value).to.equal('another option');
expect(el.displayInput.value).to.equal('Another option');
});
it('should handle multiple options with spaces in values', async () => {
const el = await fixture<WaSelect>(html`
<wa-select multiple>
<wa-option value="option with spaces" selected>Option with spaces</wa-option>
<wa-option value="another option">Another option</wa-option>
<wa-option value="third option" selected>Third option</wa-option>
</wa-select>
`);
expect(el.value).to.have.members(['option with spaces', 'third option']);
expect(el.value).to.have.length(2);
});
it('should serialize options with spaces correctly in FormData', async () => {
const form = await fixture<HTMLFormElement>(html`
<form>
<wa-select name="test" multiple>
<wa-option value="option with spaces" selected>Option with spaces</wa-option>
<wa-option value="another option" selected>Another option</wa-option>
</wa-select>
</form>
`);
const formData = new FormData(form);
const values = formData.getAll('test');
expect(values).to.have.members(['option with spaces', 'another option']);
});
});
// https://github.com/shoelace-style/webawesome-alpha/issues/263
it('should allow interaction after being disabled and re-enabled', async () => {
const el = await fixture<WaSelect>(html`

View File

@@ -127,21 +127,13 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
private _defaultValue: string | string[] = '';
@property({
attribute: 'value',
reflect: true,
converter: {
fromAttribute: (value: string) => value.split(' '),
toAttribute: (value: string | string[]) => (Array.isArray(value) ? value.join(' ') : value),
},
attribute: false,
})
set defaultValue(val: string | string[]) {
this._defaultValue = this.convertDefaultValue(val);
}
get defaultValue() {
if (!this.hasUpdated) {
this._defaultValue = this.convertDefaultValue(this._defaultValue);
}
return this._defaultValue;
}
@@ -154,28 +146,32 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
const isMultiple = this.multiple || this.hasAttribute('multiple');
if (!isMultiple && Array.isArray(val)) {
val = val.join(' ');
val = val[0];
}
return val;
}
private _value: string[] | undefined;
@property({ attribute: false })
/** The select's value. This will be a string for single select or an array for multi-select. */
@property({ attribute: 'value', reflect: false })
set value(val: string | string[]) {
let oldValue = this.value;
if (!Array.isArray(val)) {
val = val.split(' ');
if ((val as any) instanceof FormData) {
val = (val as unknown as FormData).getAll(this.name) as string[];
}
if (!this._value || this._value.join(' ') !== val.join(' ')) {
this._value = val;
let newValue = this.value;
if (!Array.isArray(val)) {
val = [val];
}
if (newValue !== oldValue) {
this.requestUpdate('value', oldValue);
}
this._value = val;
let newValue = this.value;
if (newValue !== oldValue) {
this.requestUpdate('value', oldValue);
}
}
@@ -300,6 +296,17 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Because this is a form control, it shouldn't be opened initially
this.open = false;
if (!this._defaultValue) {
const allOptions = this.getAllOptions();
const selectedOptions = allOptions.filter(el => el.selected || el.defaultSelected);
if (selectedOptions.length > 0) {
const selectedValues = selectedOptions.map(el => el.value);
this._defaultValue = this.multiple ? selectedValues : selectedValues[0];
} else if (this.hasAttribute('value')) {
this._defaultValue = this.getAttribute('value') || '';
}
}
}
private addOpenListeners() {
@@ -563,10 +570,19 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
const allOptions = this.getAllOptions();
this.optionValues = undefined; // dirty the value so it gets recalculated
// Update defaultValue if it hasn't been explicitly set and we have selected options
if (!this._defaultValue && !this.hasUpdated) {
const selectedOptions = allOptions.filter(el => el.selected || el.defaultSelected);
if (selectedOptions.length > 0) {
const selectedValues = selectedOptions.map(el => el.value);
this._defaultValue = this.multiple ? selectedValues : selectedValues[0];
}
}
const value = this.value;
// Select only the options that match the new value
this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));
this.setSelectedOptions(allOptions.filter(el => value.includes(el.value) || el.selected));
}
private handleTagRemove(event: WaRemoveEvent, directOption?: WaOption) {
@@ -645,7 +661,12 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
const newSelectedOptions = Array.isArray(option) ? option : [option];
// Clear existing selection
allOptions.forEach(el => (el.selected = false));
allOptions.forEach(el => {
if (newSelectedOptions.includes(el)) {
return;
}
el.selected = false;
});
// Set the new selection
if (newSelectedOptions.length) {
@@ -673,7 +694,9 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
const options = this.getAllOptions();
// Update selected options cache
this.selectedOptions = options.filter(el => el.selected);
this.selectedOptions = options.filter(el => {
return el.selected;
});
let selectedValues = new Set(this.selectedOptions.map(el => el.value));
// Toggle values present in the DOM from this.value, while preserving options NOT present in the DOM (for lazy loading)
@@ -847,6 +870,11 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
this.value = this.defaultValue;
super.formResetCallback();
this.handleValueChange();
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
render() {

View File

@@ -25,8 +25,6 @@ import styles from './zoomable-frame.css';
* @csspart controls - The container that surrounds zoom control buttons.
* @csspart zoom-in-button - The zoom in button.
* @csspart zoom-out-button - The zoom out button.
*
* @cssproperty [--aspect-ratio=16/9] - The aspect ratio of the frame.
*/
@customElement('wa-zoomable-frame')
export default class WaZoomableFrame extends WebAwesomeElement {