mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-14 04:59:15 +00:00
Compare commits
5 Commits
subcompone
...
with-caret
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2b05600a5 | ||
|
|
dc556e5379 | ||
|
|
c11d6129a3 | ||
|
|
dd5c32680a | ||
|
|
81996cbc63 |
@@ -258,14 +258,14 @@
|
||||
<th><code>small</code>/<code>s</code></th>
|
||||
<td>
|
||||
<wa-dropdown size="small">
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
<wa-dropdown-item>Menu Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Menu Item 2</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</td>
|
||||
<td>
|
||||
<wa-dropdown class="wa-size-s">
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
<wa-dropdown-item>Menu Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Menu Item 2</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
@@ -275,14 +275,14 @@
|
||||
<th><code>medium</code>/<code>m</code></th>
|
||||
<td>
|
||||
<wa-dropdown size="medium">
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
<wa-dropdown-item>Menu Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Menu Item 2</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</td>
|
||||
<td>
|
||||
<wa-dropdown class="wa-size-m">
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
<wa-dropdown-item>Menu Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Menu Item 2</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
@@ -292,14 +292,14 @@
|
||||
<th><code>large</code>/<code>l</code></th>
|
||||
<td>
|
||||
<wa-dropdown size="large">
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
<wa-dropdown-item>Menu Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Menu Item 2</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</td>
|
||||
<td>
|
||||
<wa-dropdown class="wa-size-l">
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
<wa-dropdown-item>Menu Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Menu Item 2</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
|
||||
@@ -165,7 +165,7 @@ Dropdowns can be placed into button groups.
|
||||
<wa-button-group label="Example Button Group">
|
||||
<wa-button>Button</wa-button>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
<wa-dropdown-item>Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Item 2</wa-dropdown-item>
|
||||
<wa-dropdown-item>Item 3</wa-dropdown-item>
|
||||
|
||||
@@ -186,12 +186,12 @@ Use the `start` and `end` slots to add presentational elements like `<wa-icon>`
|
||||
|
||||
### Caret
|
||||
|
||||
Use the `caret` attribute to add a dropdown indicator when a button will trigger a dropdown, menu, or popover.
|
||||
Use the `with-caret` attribute to add a dropdown indicator when a button will trigger a dropdown, menu, or popover.
|
||||
|
||||
```html {.example}
|
||||
<wa-button size="small" caret>Small</wa-button>
|
||||
<wa-button size="medium" caret>Medium</wa-button>
|
||||
<wa-button size="large" caret>Large</wa-button>
|
||||
<wa-button size="small" with-caret>Small</wa-button>
|
||||
<wa-button size="medium" with-caret>Medium</wa-button>
|
||||
<wa-button size="large" with-caret>Large</wa-button>
|
||||
```
|
||||
|
||||
### Loading
|
||||
|
||||
@@ -59,7 +59,7 @@ Use dividers in [menus](/docs/components/menu) to visually group menu items.
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown style="max-width: 200px;">
|
||||
<wa-button slot="trigger" caret>Menu</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Menu</wa-button>
|
||||
<wa-dropdown-item value="1">Option 1</wa-dropdown-item>
|
||||
<wa-dropdown-item value="2">Option 2</wa-dropdown-item>
|
||||
<wa-dropdown-item value="3">Option 3</wa-dropdown-item>
|
||||
|
||||
@@ -11,7 +11,7 @@ Dropdowns are designed to work well with [dropdown items](/docs/components/dropd
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="icon" name="scissors"></wa-icon>
|
||||
@@ -51,7 +51,7 @@ When an item is selected, the `wa-select` event will be emitted by the dropdown.
|
||||
```html {.example}
|
||||
<div class="dropdown-selection">
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>View</wa-button>
|
||||
<wa-button slot="trigger" with-caret>View</wa-button>
|
||||
<wa-dropdown-item value="full-screen">Enter full screen</wa-dropdown-item>
|
||||
<wa-dropdown-item value="actual">Actual size</wa-dropdown-item>
|
||||
<wa-dropdown-item value="zoom-in">Zoom in</wa-dropdown-item>
|
||||
@@ -79,7 +79,7 @@ Use the `icon` slot to add icons to [dropdown items](/docs/components/dropdown-i
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Edit</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Edit</wa-button>
|
||||
|
||||
<wa-dropdown-item value="cut">
|
||||
<wa-icon slot="icon" name="scissors"></wa-icon>
|
||||
@@ -109,7 +109,7 @@ Use any heading, e.g. `<h1>`–`<h6>` to add labels and the [`<wa-divider>`](/do
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Device</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Device</wa-button>
|
||||
|
||||
<h3>Type</h3>
|
||||
<wa-dropdown-item value="phone">Phone</wa-dropdown-item>
|
||||
@@ -128,7 +128,7 @@ Use the `details` slot to display details, such as keyboard shortcuts, inside [d
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Message</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Message</wa-button>
|
||||
|
||||
<wa-dropdown-item value="reply">
|
||||
Reply
|
||||
@@ -166,7 +166,7 @@ You can turn a [dropdown item](/docs/components/dropdown-item) into a checkable
|
||||
```html {.example}
|
||||
<div class="dropdown-checkboxes">
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>View</wa-button>
|
||||
<wa-button slot="trigger" with-caret>View</wa-button>
|
||||
|
||||
<wa-dropdown-item type="checkbox" value="canvas" checked>Show canvas</wa-dropdown-item>
|
||||
<wa-dropdown-item type="checkbox" value="grid" checked>Show grid</wa-dropdown-item>
|
||||
@@ -204,7 +204,7 @@ Add `variant="danger"` to any [dropdown item](/docs/components/dropdown-item) to
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Project</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Project</wa-button>
|
||||
|
||||
<wa-dropdown-item value="share">
|
||||
<wa-icon slot="icon" name="share"></wa-icon>
|
||||
@@ -258,7 +258,7 @@ The distance from the panel to the trigger can be customized using the `distance
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown distance="30">
|
||||
<wa-button slot="trigger" caret>Edit</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Edit</wa-button>
|
||||
|
||||
<wa-dropdown-item>Cut</wa-dropdown-item>
|
||||
<wa-dropdown-item>Copy</wa-dropdown-item>
|
||||
@@ -277,7 +277,7 @@ The offset of the panel along the trigger can be customized using the `offset` a
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown offset="30">
|
||||
<wa-button slot="trigger" caret>Edit</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Edit</wa-button>
|
||||
|
||||
<wa-dropdown-item>Cut</wa-dropdown-item>
|
||||
<wa-dropdown-item>Copy</wa-dropdown-item>
|
||||
@@ -297,7 +297,7 @@ To create submenus, nest [dropdown items](/docs/components/dropdown-item) inside
|
||||
```html {.example}
|
||||
<div class="dropdown-submenus">
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Export</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Export</wa-button>
|
||||
|
||||
<wa-dropdown-item>
|
||||
Documents
|
||||
@@ -359,7 +359,7 @@ Add the `disabled` attribute to any [dropdown item](/docs/components/dropdown-it
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Payment method</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Payment method</wa-button>
|
||||
|
||||
<wa-dropdown-item value="cash">Cash</wa-dropdown-item>
|
||||
<wa-dropdown-item value="check" disabled>Personal check</wa-dropdown-item>
|
||||
|
||||
@@ -44,7 +44,7 @@ Set the `appearance` attribute to `button` on all radios to render a radio butto
|
||||
<wa-radio appearance="button" value="3">Option 3</wa-radio>
|
||||
</wa-radio-group>
|
||||
|
||||
<br>
|
||||
<br />
|
||||
|
||||
<wa-radio-group
|
||||
label="Vertical options"
|
||||
@@ -60,12 +60,22 @@ Set the `appearance` attribute to `button` on all radios to render a radio butto
|
||||
</wa-radio-group>
|
||||
```
|
||||
|
||||
### Disabling Options
|
||||
### Disabling
|
||||
|
||||
Radios and radio buttons can be disabled by adding the `disabled` attribute to the respective options inside the radio group.
|
||||
To disable the entire radio group, add the `disabled` attribute to the radio group.
|
||||
|
||||
```html {.example}
|
||||
<wa-radio-group label="Select an option" name="a" value="1">
|
||||
<wa-radio-group label="Select an option" disabled>
|
||||
<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>
|
||||
```
|
||||
|
||||
To disable individual options, add the `disabled` attribute to the respective options.
|
||||
|
||||
```html {.example}
|
||||
<wa-radio-group label="Select an option">
|
||||
<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>
|
||||
|
||||
@@ -109,13 +109,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>
|
||||
@@ -123,33 +123,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.
|
||||
@@ -240,23 +244,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>
|
||||
@@ -267,9 +265,9 @@ Remember that custom tags are rendered in a shadow root. To style them, you can
|
||||
</wa-select>
|
||||
|
||||
<script type="module">
|
||||
await customElements.whenDefined("wa-select")
|
||||
await customElements.whenDefined('wa-select');
|
||||
const select = document.querySelector('.custom-tag');
|
||||
await select.updateComplete
|
||||
await select.updateComplete;
|
||||
|
||||
select.getTag = (option, index) => {
|
||||
// Use the same icon used in wa-option
|
||||
@@ -292,17 +290,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">
|
||||
@@ -311,93 +307,100 @@ This can be hard to conceptualize, so heres a fairly large example showing how l
|
||||
<wa-option value="bar">Bar</wa-option>
|
||||
<wa-option value="baz">Baz</wa-option>
|
||||
</wa-select>
|
||||
<br>
|
||||
<br />
|
||||
<wa-button type="button">Add "foo" option</wa-button>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<br />
|
||||
|
||||
<div>
|
||||
<wa-select name="select-2" value="foo" label="Single select (with no existing options)">
|
||||
</wa-select>
|
||||
<br>
|
||||
<wa-select name="select-2" value="foo" label="Single select (with no existing options)"> </wa-select>
|
||||
<br />
|
||||
<wa-button type="button">Add "foo" option</wa-button>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<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>
|
||||
<br />
|
||||
<wa-button type="button">Add "foo" option (selected)</wa-button>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<br />
|
||||
|
||||
<div>
|
||||
<wa-select name="select-4" value="foo" multiple label="Multiple Select (with no existing options)">
|
||||
</wa-select>
|
||||
<br>
|
||||
<wa-select name="select-4" value="foo" multiple label="Multiple Select (with no existing options)"> </wa-select>
|
||||
<br />
|
||||
<wa-button type="button">Add "foo" option</wa-button>
|
||||
</div>
|
||||
|
||||
<br><br>
|
||||
<br /><br />
|
||||
|
||||
<div style="display: flex; gap: 16px;">
|
||||
<wa-button type="reset">Reset</wa-button>
|
||||
<wa-button type="submit" variant="brand">Show FormData</wa-button>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<br />
|
||||
|
||||
<pre hidden><code id="lazy-options-example-form-data"></code></pre>
|
||||
|
||||
<br>
|
||||
<br />
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
function addFooOption(e) {
|
||||
const addFooButton = e.target.closest("wa-button[type='button']")
|
||||
const addFooButton = e.target.closest("wa-button[type='button']");
|
||||
if (!addFooButton) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const select = addFooButton.parentElement.querySelector("wa-select")
|
||||
const select = addFooButton.parentElement.querySelector('wa-select');
|
||||
|
||||
if (select.querySelector("wa-option[value='foo']")) {
|
||||
// Foo already exists. no-op.
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const option = document.createElement("wa-option")
|
||||
option.setAttribute("value", "foo")
|
||||
option.innerText = "Foo"
|
||||
select.append(option)
|
||||
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);
|
||||
}
|
||||
|
||||
function handleLazySubmit (event) {
|
||||
event.preventDefault()
|
||||
function handleLazySubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(event.target)
|
||||
const codeElement = document.querySelector("#lazy-options-example-form-data")
|
||||
const formData = new FormData(event.target);
|
||||
const codeElement = document.querySelector('#lazy-options-example-form-data');
|
||||
|
||||
const obj = {}
|
||||
const obj = {};
|
||||
for (const key of formData.keys()) {
|
||||
const val = formData.getAll(key).length > 1 ? formData.getAll(key) : formData.get(key)
|
||||
obj[key] = val
|
||||
const val = formData.getAll(key).length > 1 ? formData.getAll(key) : formData.get(key);
|
||||
obj[key] = val;
|
||||
}
|
||||
|
||||
codeElement.textContent = JSON.stringify(obj, null, 2)
|
||||
codeElement.textContent = JSON.stringify(obj, null, 2);
|
||||
|
||||
const preElement = codeElement.parentElement
|
||||
preElement.removeAttribute("hidden")
|
||||
const preElement = codeElement.parentElement;
|
||||
preElement.removeAttribute('hidden');
|
||||
}
|
||||
|
||||
const container = document.querySelector("#lazy-options-example")
|
||||
container.addEventListener("click", addFooOption)
|
||||
container.addEventListener("submit", handleLazySubmit)
|
||||
const container = document.querySelector('#lazy-options-example');
|
||||
container.addEventListener('click', addFooOption);
|
||||
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.
|
||||
:::
|
||||
|
||||
@@ -39,7 +39,7 @@ Dropdown:
|
||||
<p>
|
||||
Small dropdown:
|
||||
<wa-dropdown size="small">
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
<wa-dropdown-item>Dropdown Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Dropdown Item 2</wa-dropdown-item>
|
||||
<wa-dropdown-item>Dropdown Item 3</wa-dropdown-item>
|
||||
@@ -49,7 +49,7 @@ Dropdown:
|
||||
<p>
|
||||
Small menu:
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
<wa-dropdown-item size="small">Dropdown Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item size="small">Dropdown Item 2</wa-dropdown-item>
|
||||
<wa-dropdown-item size="small">Dropdown Item 3</wa-dropdown-item>
|
||||
@@ -59,7 +59,7 @@ Dropdown:
|
||||
<p>
|
||||
Small menu item:
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
<wa-dropdown-item size="small">Dropdown Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item size="small">Dropdown Item 2</wa-dropdown-item>
|
||||
<wa-dropdown-item size="small">Dropdown Item 3</wa-dropdown-item>
|
||||
@@ -69,7 +69,7 @@ Dropdown:
|
||||
<p>
|
||||
No size:
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-button slot="trigger" with-caret>Dropdown</wa-button>
|
||||
<wa-dropdown-item>Dropdown Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Dropdown Item 2</wa-dropdown-item>
|
||||
<wa-dropdown-item>Dropdown Item 3</wa-dropdown-item>
|
||||
|
||||
@@ -2319,7 +2319,7 @@ hasOutline: false
|
||||
<td style="text-align: center;"><wa-tag variant="warning" size="small">Pending</wa-tag></td>
|
||||
<td>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret size="small">Action</wa-button>
|
||||
<wa-button slot="trigger" with-caret size="small">Action</wa-button>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
@@ -2347,7 +2347,7 @@ hasOutline: false
|
||||
<td style="text-align: center;"><wa-tag variant="success" size="small">Resolved</wa-tag></td>
|
||||
<td>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret size="small">Action</wa-button>
|
||||
<wa-button slot="trigger" with-caret size="small">Action</wa-button>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
@@ -2375,7 +2375,7 @@ hasOutline: false
|
||||
<td style="text-align: center;"><wa-tag variant="warning" size="small">Pending</wa-tag></td>
|
||||
<td>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret size="small">Action</wa-button>
|
||||
<wa-button slot="trigger" with-caret size="small">Action</wa-button>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
@@ -2403,7 +2403,7 @@ hasOutline: false
|
||||
<td style="text-align: center;"><wa-tag variant="danger" size="small">Bounced</wa-tag></td>
|
||||
<td>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret size="small">Action</wa-button>
|
||||
<wa-button slot="trigger" with-caret size="small">Action</wa-button>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
@@ -2431,7 +2431,7 @@ hasOutline: false
|
||||
<td style="text-align: center;"><wa-tag variant="neutral" size="small">Expired</wa-tag></td>
|
||||
<td>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret size="small">Action</wa-button>
|
||||
<wa-button slot="trigger" with-caret size="small">Action</wa-button>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
|
||||
@@ -23,7 +23,8 @@ During the alpha period, things might break! We take breaking changes very serio
|
||||
- 🚨 BREAKING: Renamed the `classic` theme to `shoelace`
|
||||
- 🚨 BREAKING: Renamed `pulse` attribute in `<wa-badge>` to `attention="pulse"` and added `attention="bounce"` [issue:#940]
|
||||
- 🚨 BREAKING: Renamed the `vertical` attribute to `orientation="vertical"` in `<wa-split-panel>` and `<wa-divider>` to align with other components and the platform [issue:674]
|
||||
- 🚨 BREAKING: Renamed certain boolean attributes to be more consistent using the `with-*` and `without-*` pattern:
|
||||
- 🚨 BREAKING: Renamed certain boolean attributes to be consistent using the `with-*` and `without-*` pattern:
|
||||
- `<wa-button caret>` => `<wa-button with-caret>`
|
||||
- `<wa-color-picker no-format-toggle>` => `<wa-color-picker without-format-toggle>`
|
||||
- `<wa-format-number no-grouping>` => `<wa-format-number without-grouping>`
|
||||
- `<wa-input no-spin-buttons>` => `<wa-input without-spin-buttons>`
|
||||
@@ -59,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]
|
||||
@@ -378,4 +380,4 @@ Here's a list of some of the things that have changed since Shoelace v2. For que
|
||||
|
||||
Did we miss something? [Let us know!](https://github.com/shoelace-style/webawesome-alpha/discussions)
|
||||
|
||||
Are you coming from Shoelace? [The 2.x changelog can be found here.](https://shoelace.style/resources/changelog/)
|
||||
Are you coming from Shoelace? [The 2.x changelog can be found here.](https://shoelace.style/resources/changelog/)
|
||||
|
||||
@@ -75,9 +75,9 @@ describe('<wa-button>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when caret', () => {
|
||||
describe('when with-caret', () => {
|
||||
it('should have a caret present', async () => {
|
||||
const el = await fixture<WaButton>(html` <wa-button caret>Button Label</wa-button> `);
|
||||
const el = await fixture<WaButton>(html` <wa-button with-caret>Button Label</wa-button> `);
|
||||
expect(el.shadowRoot!.querySelector('[part~="caret"]')).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,7 +81,7 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
|
||||
/** Draws the button with a caret. Used to indicate that the button triggers a dropdown menu or similar behavior. */
|
||||
@property({ type: Boolean, reflect: true }) caret = false;
|
||||
@property({ attribute: 'with-caret', type: Boolean, reflect: true }) withCaret = false;
|
||||
|
||||
/** Disables the button. Does not apply to link buttons. */
|
||||
@property({ type: Boolean }) disabled = false;
|
||||
@@ -258,7 +258,7 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
part="base"
|
||||
class=${classMap({
|
||||
button: true,
|
||||
caret: this.caret,
|
||||
caret: this.withCaret,
|
||||
disabled: this.disabled,
|
||||
loading: this.loading,
|
||||
rtl: this.localize.dir() === 'rtl',
|
||||
@@ -286,7 +286,7 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
<slot part="label" class="label" @slotchange=${this.handleLabelSlotChange}></slot>
|
||||
<slot name="end" part="end" class="end"></slot>
|
||||
${
|
||||
this.caret
|
||||
this.withCaret
|
||||
? html`
|
||||
<wa-icon part="caret" class="caret" library="system" name="chevron-down" variant="solid"></wa-icon>
|
||||
`
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { PropertyValues } from 'lit';
|
||||
import { html, isServer } from 'lit';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { uniqueId } from '../../internal/math.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { RequiredValidator } from '../../internal/validators/required-validator.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
|
||||
import formControlStyles from '../../styles/component/form-control.css';
|
||||
import sizeStyles from '../../styles/utilities/size.css';
|
||||
@@ -73,6 +73,9 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
/** The name of the radio group, submitted as a name/value pair with form data. */
|
||||
@property({ reflect: true }) name: string | null = null;
|
||||
|
||||
/** Disables the radio group and all child radios. */
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
/** The orientation in which to show radio items. */
|
||||
@property({ reflect: true }) orientation: 'horizontal' | 'vertical' = 'vertical';
|
||||
|
||||
@@ -141,6 +144,12 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
return radio;
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has('disabled') || changedProperties.has('value')) {
|
||||
this.syncRadioElements();
|
||||
}
|
||||
}
|
||||
|
||||
formResetCallback(...args: Parameters<WebAwesomeFormAssociatedElement['formResetCallback']>) {
|
||||
this.value = this.defaultValue;
|
||||
|
||||
@@ -152,7 +161,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
private handleRadioClick = (e: Event) => {
|
||||
const clickedRadio = (e.target as HTMLElement).closest<WaRadio>('wa-radio');
|
||||
|
||||
if (!clickedRadio || clickedRadio.disabled) {
|
||||
if (!clickedRadio || clickedRadio.disabled || (clickedRadio as any).forceDisabled || this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -199,6 +208,9 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
radio.toggleAttribute('data-wa-radio-first', index === 0);
|
||||
radio.toggleAttribute('data-wa-radio-inner', index !== 0 && index !== radios.length - 1);
|
||||
radio.toggleAttribute('data-wa-radio-last', index === radios.length - 1);
|
||||
|
||||
// Set forceDisabled state based on radio group's disabled state
|
||||
(radio as WaRadio).forceDisabled = this.disabled;
|
||||
});
|
||||
|
||||
// If at least one radio button exists, we assume it's a radio button group
|
||||
@@ -216,18 +228,42 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
}),
|
||||
);
|
||||
|
||||
if (radios.length > 0 && !radios.some(radio => radio.checked)) {
|
||||
radios[0].setAttribute('tabindex', '0');
|
||||
// Manage tabIndex based on disabled state and checked status
|
||||
if (this.disabled) {
|
||||
// If radio group is disabled, all radios should not be tabbable
|
||||
radios.forEach(radio => {
|
||||
radio.tabIndex = -1;
|
||||
});
|
||||
} else {
|
||||
// Normal tabbing behavior
|
||||
const enabledRadios = radios.filter(radio => !radio.disabled);
|
||||
const checkedRadio = enabledRadios.find(radio => radio.checked);
|
||||
|
||||
if (enabledRadios.length > 0) {
|
||||
if (checkedRadio) {
|
||||
// If there's a checked radio, it should be tabbable
|
||||
enabledRadios.forEach(radio => {
|
||||
radio.tabIndex = radio.checked ? 0 : -1;
|
||||
});
|
||||
} else {
|
||||
// If no radio is checked, first enabled radio should be tabbable
|
||||
enabledRadios.forEach((radio, index) => {
|
||||
radio.tabIndex = index === 0 ? 0 : -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Disabled radios should never be tabbable
|
||||
radios
|
||||
.filter(radio => radio.disabled)
|
||||
.forEach(radio => {
|
||||
radio.tabIndex = -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@watch('value')
|
||||
handleValueChange() {
|
||||
this.syncRadioElements();
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent) {
|
||||
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(event.key)) {
|
||||
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(event.key) || this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -287,6 +323,8 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
/** Sets focus on the radio group. */
|
||||
public focus(options?: FocusOptions) {
|
||||
if (this.disabled) return;
|
||||
|
||||
const radios = this.getAllRadios();
|
||||
const checked = radios.find(radio => radio.checked);
|
||||
const firstEnabledRadio = radios.find(radio => !radio.disabled);
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
}
|
||||
|
||||
/* Disabled */
|
||||
:host([disabled]) {
|
||||
:host(:state(disabled)) {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@@ -142,7 +142,7 @@
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
:host([appearance='button']:hover:not([disabled], :state(checked))) {
|
||||
:host([appearance='button']:hover:not(:state(disabled), :state(checked))) {
|
||||
background-color: color-mix(in srgb, var(--wa-color-surface-default) 95%, var(--wa-color-mix-hover));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
@state() checked = false;
|
||||
|
||||
/** @internal Used by radio group to force disable radios while preserving their original disabled state. */
|
||||
@state() forceDisabled = false;
|
||||
|
||||
/**
|
||||
* The string pointing to a form's id.
|
||||
*/
|
||||
@@ -79,7 +82,7 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
private setInitialAttributes() {
|
||||
this.setAttribute('role', 'radio');
|
||||
this.tabIndex = 0;
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
this.setAttribute('aria-disabled', this.disabled || this.forceDisabled ? 'true' : 'false');
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
@@ -88,12 +91,24 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
if (changedProperties.has('checked')) {
|
||||
this.customStates.set('checked', this.checked);
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
this.tabIndex = this.checked ? 0 : -1;
|
||||
// Only set tabIndex if not disabled
|
||||
if (!this.disabled && !this.forceDisabled) {
|
||||
this.tabIndex = this.checked ? 0 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProperties.has('disabled')) {
|
||||
this.customStates.set('disabled', this.disabled);
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
if (changedProperties.has('disabled') || changedProperties.has('forceDisabled')) {
|
||||
const effectivelyDisabled = this.disabled || this.forceDisabled;
|
||||
this.customStates.set('disabled', effectivelyDisabled);
|
||||
this.setAttribute('aria-disabled', effectivelyDisabled ? 'true' : 'false');
|
||||
|
||||
// Set tabIndex based on disabled state
|
||||
if (effectivelyDisabled) {
|
||||
this.tabIndex = -1;
|
||||
} else {
|
||||
// Restore proper tabIndex - this will be managed by the radio group
|
||||
this.tabIndex = this.checked ? 0 : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,8 +119,9 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
// We override `setValue` because we don't want to set form values from here. We want to do that in "RadioGroup" itself.
|
||||
}
|
||||
|
||||
// Update the handleClick method (around line 75)
|
||||
private handleClick = () => {
|
||||
if (!this.disabled) {
|
||||
if (!this.disabled && !this.forceDisabled) {
|
||||
this.checked = true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
:host {
|
||||
display: inline-flex;
|
||||
gap: 0.5em;
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
align-items: center;
|
||||
background-color: var(--background-color, var(--wa-color-fill-quiet));
|
||||
border-color: var(--border-color, transparent);
|
||||
border-style: var(--wa-border-style);
|
||||
border-width: var(--wa-border-width-s);
|
||||
color: var(--text-color, var(--wa-color-on-normal));
|
||||
font-size: inherit;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
height: calc(var(--wa-form-control-height) * 0.8);
|
||||
line-height: calc(var(--wa-form-control-height) - var(--wa-form-control-border-width) * 2);
|
||||
padding: 0 0.75em;
|
||||
@layer wa-component {
|
||||
:host {
|
||||
display: inline-flex;
|
||||
gap: 0.5em;
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
align-items: center;
|
||||
background-color: var(--background-color, var(--wa-color-fill-quiet));
|
||||
border-color: var(--border-color, transparent);
|
||||
border-style: var(--wa-border-style);
|
||||
border-width: var(--wa-border-width-s);
|
||||
color: var(--text-color, var(--wa-color-on-normal));
|
||||
font-size: inherit;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
height: calc(var(--wa-form-control-height) * 0.8);
|
||||
line-height: calc(var(--wa-form-control-height) - var(--wa-form-control-border-width) * 2);
|
||||
padding: 0 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -28,7 +28,7 @@ import styles from './tag.css';
|
||||
*/
|
||||
@customElement('wa-tag')
|
||||
export default class WaTag extends WebAwesomeElement {
|
||||
static css = [sizeStyles, variantStyles, appearanceStyles, styles];
|
||||
static css = [styles, variantStyles, sizeStyles, appearanceStyles];
|
||||
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user