mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-17 06:29:13 +00:00
Compare commits
11 Commits
kj/layout-
...
subcompone
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2a9c33ee6 | ||
|
|
d20945d78b | ||
|
|
07327be95e | ||
|
|
b99d7771dc | ||
|
|
10cc9bdc68 | ||
|
|
bc598dad92 | ||
|
|
ff61ac002f | ||
|
|
63ec9d5bc1 | ||
|
|
555327b2fc | ||
|
|
15c6eaec90 | ||
|
|
6f856fbd89 |
@@ -20,7 +20,7 @@
|
||||
</head>
|
||||
<body class="layout-{{ layout | stripExtension }}{{ ' page-wide' if wide }}">
|
||||
<!-- use view="desktop" as default to reduce layout jank on desktop site. -->
|
||||
<wa-page view="desktop" disable-navigation-toggle="" mobile-breakpoint="1140">
|
||||
<wa-page view="desktop" disable-navigation-toggle="" mobile-breakpoint="1180">
|
||||
<header slot="header" class="wa-split">
|
||||
{# Logo #}
|
||||
<div id="docs-branding">
|
||||
@@ -46,9 +46,9 @@
|
||||
|
||||
{# Search #}
|
||||
<wa-button id="search-trigger" appearance="outlined" size="small" data-search>
|
||||
<wa-icon slot="prefix" name="magnifying-glass"></wa-icon>
|
||||
<wa-icon slot="start" name="magnifying-glass"></wa-icon>
|
||||
Search
|
||||
<kbd slot="suffix" class="wa-desktop-only">/</kbd>
|
||||
<kbd slot="end" class="wa-desktop-only">/</kbd>
|
||||
</wa-button>
|
||||
|
||||
{# Login #}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{# Color scheme selector #}
|
||||
<wa-select class="color-scheme-selector" appearance="filled" size="small" value="auto" pill title="Press \ to toggle">
|
||||
<wa-icon class="only-light" slot="prefix" name="sun" variant="regular"></wa-icon>
|
||||
<wa-icon class="only-dark" slot="prefix" name="moon" variant="regular"></wa-icon>
|
||||
<wa-icon class="only-light" slot="start" name="sun" variant="regular"></wa-icon>
|
||||
<wa-icon class="only-dark" slot="start" name="moon" variant="regular"></wa-icon>
|
||||
<wa-option value="light">
|
||||
<wa-icon slot="prefix" name="sun" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="sun" variant="regular"></wa-icon>
|
||||
Light
|
||||
</wa-option>
|
||||
<wa-option value="dark">
|
||||
<wa-icon slot="prefix" name="moon" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="moon" variant="regular"></wa-icon>
|
||||
Dark
|
||||
</wa-option>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-option value="auto">
|
||||
<wa-icon class="only-light" slot="prefix" name="sun" variant="regular"></wa-icon>
|
||||
<wa-icon class="only-dark" slot="prefix" name="moon" variant="regular"></wa-icon>
|
||||
<wa-icon class="only-light" slot="start" name="sun" variant="regular"></wa-icon>
|
||||
<wa-icon class="only-dark" slot="start" name="moon" variant="regular"></wa-icon>
|
||||
System
|
||||
</wa-option>
|
||||
</wa-select>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{# Preset theme selector #}
|
||||
<wa-select appearance="filled" size="small" value="default" pill class="preset-theme-selector">
|
||||
<wa-icon slot="prefix" name="paintbrush" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="paintbrush" variant="regular"></wa-icon>
|
||||
{% for theme in collections.theme | sort %}
|
||||
<wa-option value="{{ theme.page.fileSlug }}">
|
||||
{{ theme.data.title }}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
aria-haspopup="listbox"
|
||||
aria-activedescendant
|
||||
>
|
||||
<wa-icon slot="prefix" name="search"></wa-icon>
|
||||
<wa-icon slot="start" name="search"></wa-icon>
|
||||
</wa-input>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -121,20 +121,18 @@
|
||||
<li><a href="/docs/components/dialog/">Dialog</a></li>
|
||||
<li><a href="/docs/components/divider/">Divider</a></li>
|
||||
<li><a href="/docs/components/drawer/">Drawer</a></li>
|
||||
<li><a href="/docs/components/dropdown/">Dropdown</a></li>
|
||||
<li>
|
||||
<a href="/docs/components/dropdown">Dropdown</a>
|
||||
<ul>
|
||||
<li><a href="/docs/components/dropdown-item">Dropdown Item</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/docs/components/format-bytes/">Format Bytes</a></li>
|
||||
<li><a href="/docs/components/format-date/">Format Date</a></li>
|
||||
<li><a href="/docs/components/format-number/">Format Number</a></li>
|
||||
<li><a href="/docs/components/icon/">Icon</a></li>
|
||||
<li><a href="/docs/components/include/">Include</a></li>
|
||||
<li><a href="/docs/components/input/">Input</a></li>
|
||||
<li>
|
||||
<a href="/docs/components/menu/">Menu</a>
|
||||
<ul>
|
||||
<li><a href="/docs/components/menu-item/">Menu Item</a></li>
|
||||
<li><a href="/docs/components/menu-label/">Menu Label</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/docs/components/mutation-observer/">Mutation Observer</a></li>
|
||||
<li><a href="/docs/components/popover/">Popover</a></li>
|
||||
<li><a href="/docs/components/popup/">Popup</a></li>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
<span class="wa-caption-m">Shipping and taxes calculated at checkout.</span>
|
||||
<wa-button tabindex="-1" variant="brand">
|
||||
<wa-icon slot="prefix" name="shopping-bag"></wa-icon>
|
||||
<wa-icon slot="start" name="shopping-bag"></wa-icon>
|
||||
Checkout
|
||||
</wa-button>
|
||||
</div>
|
||||
@@ -62,10 +62,10 @@
|
||||
<div class="wa-stack">
|
||||
<h3 class="wa-heading-m">Sign In</h3>
|
||||
<wa-input tabindex="-1" label="Email" placeholder="ddjarin@mandalore.gov">
|
||||
<wa-icon slot="prefix" name="envelope" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="envelope" variant="regular"></wa-icon>
|
||||
</wa-input>
|
||||
<wa-input tabindex="-1" label="Password" type="password">
|
||||
<wa-icon slot="prefix" name="lock" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="lock" variant="regular"></wa-icon>
|
||||
</wa-input>
|
||||
<wa-button tabindex="-1" variant="brand">Sign In</wa-button>
|
||||
<a href="#" tabindex="-1" class="wa-body-s">I forgot my password</a>
|
||||
@@ -244,11 +244,11 @@
|
||||
</div>
|
||||
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
|
||||
<wa-button appearance="outlined" tabindex="-1">
|
||||
<wa-icon slot="prefix" name="at"></wa-icon>
|
||||
<wa-icon slot="start" name="at"></wa-icon>
|
||||
Email
|
||||
</wa-button>
|
||||
<wa-button appearance="outlined" tabindex="-1">
|
||||
<wa-icon slot="prefix" name="phone"></wa-icon>
|
||||
<wa-icon slot="start" name="phone"></wa-icon>
|
||||
Phone
|
||||
</wa-button>
|
||||
</div>
|
||||
@@ -268,11 +268,9 @@
|
||||
<wa-button id="more-actions-2" slot="trigger" appearance="plain" size="small" tabindex="-1">
|
||||
<wa-icon name="ellipsis-vertical" label="View menu"></wa-icon>
|
||||
</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Copy link</wa-menu-item>
|
||||
<wa-menu-item>Rename</wa-menu-item>
|
||||
<wa-menu-item>Move to trash</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item>Copy link</wa-dropdown-item>
|
||||
<wa-dropdown-item>Rename</wa-dropdown-item>
|
||||
<wa-dropdown-item>Move to trash</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
<wa-tooltip for="more-actions-2">View menu</wa-tooltip>
|
||||
</div>
|
||||
|
||||
@@ -259,19 +259,15 @@
|
||||
<td>
|
||||
<wa-dropdown size="small">
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
<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-menu>
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item>Menu Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Menu Item 2</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -280,19 +276,15 @@
|
||||
<td>
|
||||
<wa-dropdown size="medium">
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
<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-menu>
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item>Menu Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Menu Item 2</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -301,19 +293,15 @@
|
||||
<td>
|
||||
<wa-dropdown size="large">
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
<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-menu>
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item>Menu Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Menu Item 2</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -322,66 +310,6 @@
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<h3>Menu</h3>
|
||||
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<th></th>
|
||||
<th><code>size=""</code></th>
|
||||
<th><code>.wa-size-[s|m|l]</code></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><code>small</code>/<code>s</code></th>
|
||||
<td>
|
||||
<wa-menu size="small">
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
</td>
|
||||
<td>
|
||||
<wa-menu class="wa-size-s">
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><code>medium</code>/<code>m</code></th>
|
||||
<td>
|
||||
<wa-menu size="medium">
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
</td>
|
||||
<td>
|
||||
<wa-menu class="wa-size-m">
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><code>large</code>/<code>l</code></th>
|
||||
<td>
|
||||
<wa-menu size="large">
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
</td>
|
||||
<td>
|
||||
<wa-menu class="wa-size-l">
|
||||
<wa-menu-item>Menu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Menu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<h3>Input</h3>
|
||||
|
||||
<div class="table-scroll">
|
||||
@@ -799,4 +727,4 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ layout: page-outline
|
||||
|
||||
<div id="block-filter">
|
||||
<wa-input type="search" placeholder="Search {{ title }}" with-clear autofocus>
|
||||
<wa-icon slot="prefix" name="search"></wa-icon>
|
||||
<wa-icon slot="start" name="search"></wa-icon>
|
||||
</wa-input>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
</div>
|
||||
|
||||
<wa-button @click="reset()" appearance="outlined" variant="danger" size="small">
|
||||
<wa-icon slot="prefix" name="circle-xmark" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="circle-xmark" variant="regular"></wa-icon>
|
||||
Reset
|
||||
</wa-button>
|
||||
</wa-callout>
|
||||
|
||||
@@ -24,12 +24,12 @@ if (location.pathname.endsWith('/custom/') && !location.search) {
|
||||
<h4 slot="summary" data-no-anchor data-no-outline id="remix">
|
||||
<wa-icon name="arrows-rotate"></wa-icon>
|
||||
Remix this theme
|
||||
<wa-icon id="what-is-remixing" href="#remixing" name="circle-question" slot="suffix" variant="regular"></wa-icon>
|
||||
<wa-icon id="what-is-remixing" href="#remixing" name="circle-question" slot="end" variant="regular"></wa-icon>
|
||||
<wa-tooltip for="what-is-remixing">Customize this theme by changing its colors and/or remixing it with design elements from other themes!</wa-tooltip>
|
||||
</h4>
|
||||
|
||||
<wa-select name="palette" label="Color palette" with-clear v-model="theme.palette">
|
||||
<wa-icon name="swatchbook" slot="prefix" variant="regular"></wa-icon>
|
||||
<wa-icon name="swatchbook" slot="start" variant="regular"></wa-icon>
|
||||
<wa-option v-for="(palette, paletteId) in palettes" :label="palette.title" :value="paletteId === baseTheme.palette ? '' : paletteId">
|
||||
<palette-card :palette="paletteId" size="small">
|
||||
<template #extra>
|
||||
@@ -43,7 +43,7 @@ if (location.pathname.endsWith('/custom/') && !location.search) {
|
||||
:values="hues"></color-select>
|
||||
|
||||
<wa-select name="colors" class="theme-colors-select" label="Color contrast from…" value="" with-clear v-model="theme.colors">
|
||||
<wa-icon name="palette" slot="prefix" variant="regular"></wa-icon>
|
||||
<wa-icon name="palette" slot="start" variant="regular"></wa-icon>
|
||||
<template v-for="(themeMeta, themeId) in themes">
|
||||
<wa-option v-if="themeId !== 'custom'" :label="themeMeta.title" :value="themeId === computed.colors ? '' : themeId">
|
||||
<theme-card :theme="themeId" type="colors" :rest="{base: computed.base, palette: computed.palette, brand: computed.brand}" size="small">
|
||||
@@ -56,7 +56,7 @@ if (location.pathname.endsWith('/custom/') && !location.search) {
|
||||
</wa-select>
|
||||
|
||||
<wa-select name="typography" label="Typography from…" with-clear v-model="theme.typography">
|
||||
<wa-icon name="font-case" slot="prefix"></wa-icon>
|
||||
<wa-icon name="font-case" slot="start"></wa-icon>
|
||||
|
||||
<wa-option v-for="(themeMeta, themeId) in themes" :label="themeMeta.title" :value="themeId === theme.base ? '' : themeId">
|
||||
<fonts-card :theme="themeId" size="small">
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
</wa-breadcrumb>
|
||||
</div>
|
||||
<wa-input id="search" class="wa-desktop-only" placeholder="Search" size="small" style="max-inline-size: 12rem">
|
||||
<wa-icon slot="prefix" name="magnifying-glass"></wa-icon>
|
||||
<wa-icon slot="start" name="magnifying-glass"></wa-icon>
|
||||
</wa-input>
|
||||
</nav>
|
||||
<nav slot="navigation-header">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<span class="wa-heading-m">radiogaga</span>
|
||||
</div>
|
||||
<wa-input id="search-header" placeholder="Search" class="wa-desktop-only" style="max-inline-size: 100%">
|
||||
<wa-icon slot="prefix" name="magnifying-glass"></wa-icon>
|
||||
<wa-icon slot="start" name="magnifying-glass"></wa-icon>
|
||||
</wa-input>
|
||||
<div class="wa-cluster">
|
||||
<wa-button appearance="outlined">Log In</wa-button>
|
||||
@@ -28,7 +28,7 @@
|
||||
</header>
|
||||
<div slot="navigation-header" class="wa-split">
|
||||
<wa-input id="search-nav-drawer" placeholder="Search" style="max-inline-size: 100%" class="wa-mobile-only">
|
||||
<wa-icon slot="prefix" name="magnifying-glass"></wa-icon>
|
||||
<wa-icon slot="start" name="magnifying-glass"></wa-icon>
|
||||
</wa-input>
|
||||
<div class="wa-split">
|
||||
<h2 class="wa-heading-s">For You</h2>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pre {
|
||||
/* Only code blocks generated by our docs get these styles */
|
||||
pre[id*='code-block-'] {
|
||||
background-color: var(--wa-color-gray-20);
|
||||
color: white;
|
||||
|
||||
@@ -8,6 +9,7 @@ pre {
|
||||
background-color: var(--wa-color-surface-lowered);
|
||||
}
|
||||
}
|
||||
|
||||
.code-comment,
|
||||
.code-prolog,
|
||||
.code-doctype,
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/* Prep our code blocks to host the copy button */
|
||||
pre[id*='code-block-']:has(code) {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
|
||||
& code {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
background-color: transparent;
|
||||
padding: var(--wa-space-m);
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
|
||||
wa-copy-button.copy-button {
|
||||
--background-color: var(--wa-color-gray-20);
|
||||
--background-color-hover: color-mix(in oklab, var(--background-color), white 5%);
|
||||
|
||||
@@ -251,12 +251,6 @@ wa-page[view='mobile'] :is([slot='navigation-header'], [slot='navigation']) {
|
||||
}
|
||||
}
|
||||
|
||||
[slot='navigation-header'] wa-menu {
|
||||
font-family: var(--wa-font-family-body);
|
||||
font-size: var(--wa-font-size-m);
|
||||
font-weight: var(--wa-font-weight-normal);
|
||||
}
|
||||
|
||||
/* Main content */
|
||||
wa-page > main {
|
||||
max-width: 80ch;
|
||||
|
||||
@@ -185,11 +185,11 @@ html.wa-theme-brutalist .preview-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.product-card wa-button::part(prefix) {
|
||||
.product-card wa-button::part(start) {
|
||||
padding-inline-start: var(--wa-space-xs);
|
||||
}
|
||||
|
||||
.product-card wa-button::part(suffix) {
|
||||
.product-card wa-button::part(end) {
|
||||
padding-inline-end: var(--wa-space-xs);
|
||||
}
|
||||
|
||||
|
||||
@@ -141,14 +141,3 @@ One of the most common use cases for badges is attaching them to buttons. To mak
|
||||
<wa-badge variant="danger" pill>6</wa-badge>
|
||||
</wa-button>
|
||||
```
|
||||
|
||||
### With Menu Items
|
||||
|
||||
When including badges in menu items, use the `suffix` slot to make sure they're aligned correctly.
|
||||
|
||||
```html {.example}
|
||||
<wa-menu style="max-width: 240px;">
|
||||
<wa-menu-label>Messages</wa-menu-label>
|
||||
<wa-menu-item>Comments <wa-badge slot="suffix" variant="neutral" pill>4</wa-badge></wa-menu-item>
|
||||
<wa-menu-item>Replies <wa-badge slot="suffix" variant="neutral" pill>12</wa-badge></wa-menu-item>
|
||||
</wa-menu>
|
||||
|
||||
@@ -5,17 +5,4 @@ tags: component
|
||||
parent: breadcrumb
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>
|
||||
<wa-icon slot="prefix" 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.
|
||||
|
||||
@@ -36,32 +36,20 @@ For websites, you'll probably want to use links instead. You can make any breadc
|
||||
</wa-breadcrumb>
|
||||
```
|
||||
|
||||
### Prefixes
|
||||
### Start & End Decorations
|
||||
|
||||
Use the `prefix` slot to add content before any breadcrumb item.
|
||||
Use the `start` and `end` slots to add presentational elements like `<wa-icon>` next to any breadcrumb item.
|
||||
|
||||
```html {.example}
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>
|
||||
<wa-icon slot="prefix" name="house" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="house"></wa-icon>
|
||||
Home
|
||||
</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Articles</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Traveling</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
```
|
||||
|
||||
### Suffixes
|
||||
|
||||
Use the `suffix` slot to add content after any breadcrumb item.
|
||||
|
||||
```html {.example}
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>Documents</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Policies</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>
|
||||
Security
|
||||
<wa-icon slot="suffix" name="shield" variant="solid"></wa-icon>
|
||||
<wa-icon slot="end" name="tree-palm"></wa-icon>
|
||||
Traveling
|
||||
</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
```
|
||||
@@ -99,7 +87,7 @@ Use the `separator` slot to change the separator that goes between breadcrumb it
|
||||
|
||||
### Custom Colors
|
||||
|
||||
Breadcrumb labels match the color set on `<wa-breadcrumb-item>`. Prefixes, suffixes, and separators can be styled using CSS parts.
|
||||
Breadcrumb labels match the color set on `<wa-breadcrumb-item>`. Content in the `start`, `end`, and `separator` slots can be styled using CSS parts.
|
||||
|
||||
```html {.example}
|
||||
<style>
|
||||
@@ -112,14 +100,14 @@ Breadcrumb labels match the color set on `<wa-breadcrumb-item>`. Prefixes, suffi
|
||||
.redcrumbs wa-breadcrumb-item::part(separator) {
|
||||
color: pink;
|
||||
}
|
||||
.redcrumbs wa-breadcrumb-item::part(prefix),
|
||||
.redcrumbs wa-breadcrumb-item::part(suffix) {
|
||||
.redcrumbs wa-breadcrumb-item::part(start),
|
||||
.redcrumbs wa-breadcrumb-item::part(end) {
|
||||
color: currentColor;
|
||||
}
|
||||
</style>
|
||||
<wa-breadcrumb class="redcrumbs">
|
||||
<wa-breadcrumb-item>
|
||||
<wa-icon slot="prefix" name="house" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="house" variant="solid"></wa-icon>
|
||||
Home
|
||||
</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Articles</wa-breadcrumb-item>
|
||||
@@ -139,11 +127,9 @@ Dropdown menus can be placed in the default slot to provide additional options.
|
||||
<wa-button slot="trigger" size="small" appearance="filled" pill>
|
||||
<wa-icon label="More options" name="ellipsis" variant="solid"></wa-icon>
|
||||
</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item type="checkbox" checked>Web Design</wa-menu-item>
|
||||
<wa-menu-item type="checkbox">Web Development</wa-menu-item>
|
||||
<wa-menu-item type="checkbox">Marketing</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item type="checkbox" checked>Web Design</wa-dropdown-item>
|
||||
<wa-dropdown-item type="checkbox">Web Development</wa-dropdown-item>
|
||||
<wa-dropdown-item type="checkbox">Marketing</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Our Services</wa-breadcrumb-item>
|
||||
@@ -151,7 +137,7 @@ Dropdown menus can be placed in the default slot to provide additional options.
|
||||
</wa-breadcrumb>
|
||||
```
|
||||
|
||||
Alternatively, you can place dropdown menus in a prefix or suffix slot.
|
||||
Alternatively, you can place dropdown menus in a `start` or `end` slot.
|
||||
|
||||
```html {.example}
|
||||
<wa-breadcrumb>
|
||||
@@ -160,15 +146,14 @@ Alternatively, you can place dropdown menus in a prefix or suffix slot.
|
||||
<wa-breadcrumb-item>Digital Media</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>
|
||||
Web Design
|
||||
<wa-dropdown slot="suffix">
|
||||
<wa-dropdown slot="end">
|
||||
<wa-button slot="trigger" size="small" appearance="filled" pill>
|
||||
<wa-icon label="More options" name="ellipsis" variant="solid"></wa-icon>
|
||||
</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item type="checkbox" checked>Web Design</wa-menu-item>
|
||||
<wa-menu-item type="checkbox">Web Development</wa-menu-item>
|
||||
<wa-menu-item type="checkbox">Marketing</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item type="checkbox" checked>Web Design</wa-dropdown-item>
|
||||
<wa-dropdown-item type="checkbox">Web Development</wa-dropdown-item>
|
||||
<wa-dropdown-item type="checkbox">Marketing</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
```
|
||||
|
||||
@@ -50,26 +50,26 @@ and it will override the inherited size,
|
||||
it is rarely a good idea to mix sizes within the same button group.
|
||||
:::
|
||||
|
||||
### Vertical button groups
|
||||
### Vertical Button Groups
|
||||
|
||||
Set the `orientation` attribute to `vertical` to make a vertical button group.
|
||||
|
||||
```html {.example}
|
||||
<wa-button-group orientation="vertical" label="Options" style="max-width: 120px;">
|
||||
<wa-button>
|
||||
<wa-icon slot="prefix" name="plus"></wa-icon>
|
||||
<wa-icon slot="start" name="plus"></wa-icon>
|
||||
New
|
||||
</wa-button>
|
||||
<wa-button>
|
||||
<wa-icon slot="prefix" name="folder-open"></wa-icon>
|
||||
<wa-icon slot="start" name="folder-open"></wa-icon>
|
||||
Open
|
||||
</wa-button>
|
||||
<wa-button>
|
||||
<wa-icon slot="prefix" name="save"></wa-icon>
|
||||
<wa-icon slot="start" name="save"></wa-icon>
|
||||
Save
|
||||
</wa-button>
|
||||
<wa-button>
|
||||
<wa-icon slot="prefix" name="print"></wa-icon>
|
||||
<wa-icon slot="start" name="print"></wa-icon>
|
||||
Print
|
||||
</wa-button>
|
||||
</wa-button-group>
|
||||
@@ -166,11 +166,9 @@ Dropdowns can be placed into button groups.
|
||||
<wa-button>Button</wa-button>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item>Item 1</wa-dropdown-item>
|
||||
<wa-dropdown-item>Item 2</wa-dropdown-item>
|
||||
<wa-dropdown-item>Item 3</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
<wa-button>Button</wa-button>
|
||||
</wa-button-group>
|
||||
@@ -187,11 +185,9 @@ Create a split button using a button and a dropdown. Use a [visually hidden](/do
|
||||
<wa-button slot="trigger" variant="brand">
|
||||
<wa-icon name="chevron-down" label="More options"></wa-icon>
|
||||
</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Save</wa-menu-item>
|
||||
<wa-menu-item>Save as…</wa-menu-item>
|
||||
<wa-menu-item>Save all</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item>Save</wa-dropdown-item>
|
||||
<wa-dropdown-item>Save as…</wa-dropdown-item>
|
||||
<wa-dropdown-item>Save all</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</wa-button-group>
|
||||
```
|
||||
@@ -230,9 +226,15 @@ Create interactive toolbars with button groups.
|
||||
</wa-button-group>
|
||||
|
||||
<wa-button-group label="Alignment">
|
||||
<wa-button id="button-align-left"><wa-icon name="align-left" variant="solid" label="Align Left"></wa-icon></wa-button>
|
||||
<wa-button id="button-align-center"><wa-icon name="align-center" variant="solid" label="Align Center"></wa-icon></wa-button>
|
||||
<wa-button id="button-align-right"><wa-icon name="align-right" variant="solid" label="Align Right"></wa-icon></wa-button>
|
||||
<wa-button id="button-align-left">
|
||||
<wa-icon name="align-left" variant="solid" label="Align Left"></wa-icon>
|
||||
</wa-button>
|
||||
<wa-button id="button-align-center">
|
||||
<wa-icon name="align-center" variant="solid" label="Align Center"></wa-icon>
|
||||
</wa-button>
|
||||
<wa-button id="button-align-right">
|
||||
<wa-icon name="align-right" variant="solid" label="Align Right"></wa-icon>
|
||||
</wa-button>
|
||||
</wa-button-group>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -126,60 +126,60 @@ As expected, buttons can be given a custom width by setting the `width` CSS prop
|
||||
<wa-button size="large" style="width: 100%;">Large</wa-button>
|
||||
```
|
||||
|
||||
### Prefix and Suffix Icons
|
||||
### Start & End Decorations
|
||||
|
||||
Use the `prefix` and `suffix` slots to add icons.
|
||||
Use the `start` and `end` slots to add presentational elements like `<wa-icon>` next to the button label.
|
||||
|
||||
```html {.example}
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="gear"></wa-icon>
|
||||
Settings
|
||||
</wa-button>
|
||||
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="suffix" name="undo" variant="solid"></wa-icon>
|
||||
<wa-icon slot="end" name="undo"></wa-icon>
|
||||
Refresh
|
||||
</wa-button>
|
||||
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="prefix" name="link" variant="solid"></wa-icon>
|
||||
<wa-icon slot="suffix" name="arrow-up-right-from-square" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="link"></wa-icon>
|
||||
<wa-icon slot="end" name="arrow-up-right-from-square"></wa-icon>
|
||||
Open
|
||||
</wa-button>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<wa-button>
|
||||
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="gear"></wa-icon>
|
||||
Settings
|
||||
</wa-button>
|
||||
|
||||
<wa-button>
|
||||
<wa-icon slot="suffix" name="undo" variant="solid"></wa-icon>
|
||||
<wa-icon slot="end" name="undo"></wa-icon>
|
||||
Refresh
|
||||
</wa-button>
|
||||
|
||||
<wa-button>
|
||||
<wa-icon slot="prefix" name="link" variant="solid"></wa-icon>
|
||||
<wa-icon slot="suffix" name="arrow-up-right-from-square" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="link"></wa-icon>
|
||||
<wa-icon slot="end" name="arrow-up-right-from-square"></wa-icon>
|
||||
Open
|
||||
</wa-button>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<wa-button size="large">
|
||||
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="gear"></wa-icon>
|
||||
Settings
|
||||
</wa-button>
|
||||
|
||||
<wa-button size="large">
|
||||
<wa-icon slot="suffix" name="undo" variant="solid"></wa-icon>
|
||||
<wa-icon slot="end" name="undo"></wa-icon>
|
||||
Refresh
|
||||
</wa-button>
|
||||
|
||||
<wa-button size="large">
|
||||
<wa-icon slot="prefix" name="link" variant="solid"></wa-icon>
|
||||
<wa-icon slot="suffix" name="arrow-up-right-from-square" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="link"></wa-icon>
|
||||
<wa-icon slot="end" name="arrow-up-right-from-square"></wa-icon>
|
||||
Open
|
||||
</wa-button>
|
||||
```
|
||||
|
||||
@@ -6,41 +6,4 @@ parent: carousel
|
||||
icon: carousel-item
|
||||
---
|
||||
|
||||
```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.
|
||||
|
||||
@@ -58,13 +58,14 @@ The default orientation for dividers is `horizontal`. Set `orientation` attribut
|
||||
Use dividers in [menus](/docs/components/menu) to visually group menu items.
|
||||
|
||||
```html {.example}
|
||||
<wa-menu style="max-width: 200px;">
|
||||
<wa-menu-item value="1">Option 1</wa-menu-item>
|
||||
<wa-menu-item value="2">Option 2</wa-menu-item>
|
||||
<wa-menu-item value="3">Option 3</wa-menu-item>
|
||||
<wa-dropdown style="max-width: 200px;">
|
||||
<wa-button slot="trigger" 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>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item value="4">Option 4</wa-menu-item>
|
||||
<wa-menu-item value="5">Option 5</wa-menu-item>
|
||||
<wa-menu-item value="6">Option 6</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item value="4">Option 4</wa-dropdown-item>
|
||||
<wa-dropdown-item value="5">Option 5</wa-dropdown-item>
|
||||
<wa-dropdown-item value="6">Option 6</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
```
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Dropdown Item
|
||||
description: Description of component.
|
||||
layout: component
|
||||
---
|
||||
|
||||
This component must be used as a child of `<wa-dropdown>`. Please see the [Dropdown docs](/docs/components/dropdown) to see examples of this component in action.
|
||||
@@ -7,28 +7,38 @@ icon: dropdown
|
||||
|
||||
Dropdowns consist of a trigger and a panel. By default, activating the trigger will expose the panel and interacting outside of the panel will close it.
|
||||
|
||||
Dropdowns are designed to work well with [menus](/docs/components/menu) to provide a list of options the user can select from. However, dropdowns can also be used in lower-level applications (e.g. [color picker](/docs/components/color-picker)). The API gives you complete control over showing, hiding, and positioning the panel.
|
||||
Dropdowns are designed to work well with [dropdown items](/docs/components/dropdown-item) to provide a list of options the user can select from. However, dropdowns can also be used in lower-level applications. The API gives you complete control over showing, hiding, and positioning the panel.
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Dropdown Item 1</wa-menu-item>
|
||||
<wa-menu-item>Dropdown Item 2</wa-menu-item>
|
||||
<wa-menu-item>Dropdown Item 3</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item type="checkbox" checked>Checkbox</wa-menu-item>
|
||||
<wa-menu-item disabled>Disabled</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item>
|
||||
Prefix
|
||||
<wa-icon slot="prefix" name="gift" variant="solid"></wa-icon>
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
Suffix Icon
|
||||
<wa-icon slot="suffix" name="heart" variant="solid"></wa-icon>
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="icon" name="scissors"></wa-icon>
|
||||
Cut
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="icon" name="copy"></wa-icon>
|
||||
Copy
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="icon" name="paste"></wa-icon>
|
||||
Paste
|
||||
</wa-dropdown-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-dropdown-item>
|
||||
Show images
|
||||
<wa-dropdown-item slot="submenu" value="show-all-images">Show All Images</wa-dropdown-item>
|
||||
<wa-dropdown-item slot="submenu" value="show-thumbnails">Show Thumbnails</wa-dropdown-item>
|
||||
</wa-dropdown-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-dropdown-item type="checkbox" checked>Emoji Shortcuts<wa-dropdown-item>
|
||||
<wa-dropdown-item type="checkbox" checked>Word Wrap</wa-dropdown-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-dropdown-item variant="danger">
|
||||
<wa-icon slot="icon" name="trash"></wa-icon>
|
||||
Delete
|
||||
</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
```
|
||||
|
||||
@@ -36,17 +46,16 @@ Dropdowns are designed to work well with [menus](/docs/components/menu) to provi
|
||||
|
||||
### Getting the Selected Item
|
||||
|
||||
When dropdowns are used with [menus](/docs/components/menu), you can listen for the [`wa-select`](/docs/components/menu#events) event to determine which menu item was selected. The menu item element will be exposed in `event.detail.item`. You can set `value` props to make it easier to identify commands.
|
||||
When an item is selected, the `wa-select` event will be emitted by the dropdown. You can inspect `event.detail.item` to get a reference to the selected item. If you've provided a value for each [dropdown item](/docs/components/dropdown-item), it will be available in `event.detail.item.value`.
|
||||
|
||||
```html {.example}
|
||||
<div class="dropdown-selection">
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Edit</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item value="cut">Cut</wa-menu-item>
|
||||
<wa-menu-item value="copy">Copy</wa-menu-item>
|
||||
<wa-menu-item value="paste">Paste</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-button slot="trigger" 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>
|
||||
<wa-dropdown-item value="zoom-out">Zoom out</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</div>
|
||||
|
||||
@@ -55,53 +64,191 @@ When dropdowns are used with [menus](/docs/components/menu), you can listen for
|
||||
const dropdown = container.querySelector('wa-dropdown');
|
||||
|
||||
dropdown.addEventListener('wa-select', event => {
|
||||
const selectedItem = event.detail.item;
|
||||
console.log(selectedItem.value);
|
||||
console.log(event.detail.item.value);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
Alternatively, you can listen for the `click` event on individual menu items. Note that, using this approach, disabled menu items will still emit a `click` event.
|
||||
:::info
|
||||
To keep the dropdown open after selection, call `event.preventDefault()` in the `wa-select` event's callback.
|
||||
:::
|
||||
|
||||
### Showing Icons
|
||||
|
||||
Use the `icon` slot to add icons to [dropdown items](/docs/components/dropdown-item). This works best with [icon](/docs/components/icon) elements.
|
||||
|
||||
```html {.example}
|
||||
<div class="dropdown-selection-alt">
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Edit</wa-button>
|
||||
|
||||
<wa-dropdown-item value="cut">
|
||||
<wa-icon slot="icon" name="scissors"></wa-icon>
|
||||
Cut
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-dropdown-item value="copy">
|
||||
<wa-icon slot="icon" name="copy"></wa-icon>
|
||||
Copy
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-dropdown-item value="paste">
|
||||
<wa-icon slot="icon" name="paste"></wa-icon>
|
||||
Paste
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-dropdown-item value="delete">
|
||||
<wa-icon slot="icon" name="trash"></wa-icon>
|
||||
Delete
|
||||
</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
```
|
||||
|
||||
### Showing Labels & Dividers
|
||||
|
||||
Use any heading, e.g. `<h1>`–`<h6>` to add labels and the [`<wa-divider>`](/docs/components/divider) element for separators.
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Device</wa-button>
|
||||
|
||||
<h3>Type</h3>
|
||||
<wa-dropdown-item value="phone">Phone</wa-dropdown-item>
|
||||
<wa-dropdown-item value="tablet">Tablet</wa-dropdown-item>
|
||||
<wa-dropdown-item value="desktop">Desktop</wa-dropdown-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<wa-dropdown-item value="more">More options…</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
```
|
||||
|
||||
### Showing Details
|
||||
|
||||
Use the `details` slot to display details, such as keyboard shortcuts, inside [dropdown items](/docs/components/dropdown-item).
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Message</wa-button>
|
||||
|
||||
<wa-dropdown-item value="reply">
|
||||
Reply
|
||||
<span slot="details">⌘R</span>
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-dropdown-item value="forward">
|
||||
Forward
|
||||
<span slot="details">⌘F</span>
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-dropdown-item value="move">
|
||||
Move
|
||||
<span slot="details">⌘M</span>
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<wa-dropdown-item value="archive">
|
||||
Archive
|
||||
<span slot="details">⌘A</span>
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-dropdown-item value="delete" variant="danger">
|
||||
Delete
|
||||
<span slot="details">Del</span>
|
||||
</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
```
|
||||
|
||||
### Checkable Items
|
||||
|
||||
You can turn a [dropdown item](/docs/components/dropdown-item) into a checkable option by setting `type="checkbox"`. Add the `checked` attribute to make it checked initially. When clicked, the item's checked state will toggle and the dropdown will close. You can cancel the `wa-select` event if you want to keep it open instead.
|
||||
|
||||
```html {.example}
|
||||
<div class="dropdown-checkboxes">
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Edit</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item value="cut">Cut</wa-menu-item>
|
||||
<wa-menu-item value="copy">Copy</wa-menu-item>
|
||||
<wa-menu-item value="paste">Paste</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-button slot="trigger" 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>
|
||||
<wa-dropdown-item type="checkbox" value="source">Show source</wa-dropdown-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<wa-dropdown-item value="preferences">Preferences…</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const container = document.querySelector('.dropdown-selection-alt');
|
||||
const cut = container.querySelector('wa-menu-item[value="cut"]');
|
||||
const copy = container.querySelector('wa-menu-item[value="copy"]');
|
||||
const paste = container.querySelector('wa-menu-item[value="paste"]');
|
||||
const container = document.querySelector('.dropdown-checkboxes');
|
||||
const dropdown = container.querySelector('wa-dropdown');
|
||||
|
||||
cut.addEventListener('click', () => console.log('cut'));
|
||||
copy.addEventListener('click', () => console.log('copy'));
|
||||
paste.addEventListener('click', () => console.log('paste'));
|
||||
dropdown.addEventListener('wa-select', event => {
|
||||
if (event.detail.item.type === 'checkbox') {
|
||||
// Checkbox
|
||||
console.log(event.detail.item.value, event.detail.item.checked ? 'checked' : 'unchecked');
|
||||
} else {
|
||||
// Not a checkbox
|
||||
console.log(event.detail.item.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
:::info
|
||||
When a checkable option exists anywhere in the dropdown, all items will receive additional padding so they align properly.
|
||||
:::
|
||||
|
||||
### Destructive Items
|
||||
|
||||
Add `variant="danger"` to any [dropdown item](/docs/components/dropdown-item) to highlight that it's a dangerous action.
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Project</wa-button>
|
||||
|
||||
<wa-dropdown-item value="share">
|
||||
<wa-icon slot="icon" name="share"></wa-icon>
|
||||
Share
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-dropdown-item value="preferences">
|
||||
<wa-icon slot="icon" name="gear"></wa-icon>
|
||||
Preferences
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<h3>Danger zone</h3>
|
||||
|
||||
<wa-dropdown-item value="archive">
|
||||
<wa-icon slot="icon" name="archive"></wa-icon>
|
||||
Archive
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-dropdown-item value="delete" variant="danger">
|
||||
<wa-icon slot="icon" name="trash"></wa-icon>
|
||||
Delete
|
||||
</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
```
|
||||
|
||||
### Placement
|
||||
|
||||
The preferred placement of the dropdown can be set with the `placement` attribute. Note that the actual position may vary to ensure the panel remains in the viewport.
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown placement="top-start">
|
||||
<wa-button slot="trigger" caret>Edit</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Cut</wa-menu-item>
|
||||
<wa-menu-item>Copy</wa-menu-item>
|
||||
<wa-menu-item>Paste</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item>Find</wa-menu-item>
|
||||
<wa-menu-item>Replace</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown placement="right-start">
|
||||
<wa-button slot="trigger">
|
||||
File formats
|
||||
<wa-icon slot="end" name="chevron-right"></wa-icon>
|
||||
</wa-button>
|
||||
|
||||
<wa-dropdown-item value="pdf">PDF Document</wa-dropdown-item>
|
||||
<wa-dropdown-item value="docx">Word Document</wa-dropdown-item>
|
||||
<wa-dropdown-item value="xlsx">Excel Spreadsheet</wa-dropdown-item>
|
||||
<wa-dropdown-item value="pptx">PowerPoint Presentation</wa-dropdown-item>
|
||||
<wa-dropdown-item value="txt">Plain Text</wa-dropdown-item>
|
||||
<wa-dropdown-item value="json">JSON File</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
```
|
||||
|
||||
@@ -112,71 +259,111 @@ 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-menu>
|
||||
<wa-menu-item>Cut</wa-menu-item>
|
||||
<wa-menu-item>Copy</wa-menu-item>
|
||||
<wa-menu-item>Paste</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item>Find</wa-menu-item>
|
||||
<wa-menu-item>Replace</wa-menu-item>
|
||||
</wa-menu>
|
||||
|
||||
<wa-dropdown-item>Cut</wa-dropdown-item>
|
||||
<wa-dropdown-item>Copy</wa-dropdown-item>
|
||||
<wa-dropdown-item>Paste</wa-dropdown-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<wa-dropdown-item>Find</wa-dropdown-item>
|
||||
<wa-dropdown-item>Replace</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
```
|
||||
|
||||
### Skidding
|
||||
### Offset
|
||||
|
||||
The offset of the panel along the trigger can be customized using the `skidding` attribute. This value is specified in pixels.
|
||||
The offset of the panel along the trigger can be customized using the `offset` attribute. This value is specified in pixels.
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown skidding="30">
|
||||
<wa-dropdown offset="30">
|
||||
<wa-button slot="trigger" caret>Edit</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Cut</wa-menu-item>
|
||||
<wa-menu-item>Copy</wa-menu-item>
|
||||
<wa-menu-item>Paste</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item>Find</wa-menu-item>
|
||||
<wa-menu-item>Replace</wa-menu-item>
|
||||
</wa-menu>
|
||||
|
||||
<wa-dropdown-item>Cut</wa-dropdown-item>
|
||||
<wa-dropdown-item>Copy</wa-dropdown-item>
|
||||
<wa-dropdown-item>Paste</wa-dropdown-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<wa-dropdown-item>Find</wa-dropdown-item>
|
||||
<wa-dropdown-item>Replace</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
```
|
||||
|
||||
### Submenus
|
||||
|
||||
To create a submenu, nest an `<wa-menu slot="submenu">` element in a [menu item](/docs/components/menu-item).
|
||||
To create submenus, nest [dropdown items](/docs/components/dropdown-item) inside of a dropdown item and assign `slot="submenu"` to each one. You can also add [dividers](/docs/components/divider) as needed.
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Edit</wa-button>
|
||||
<div class="dropdown-submenus">
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Export</wa-button>
|
||||
|
||||
<wa-dropdown-item>
|
||||
Documents
|
||||
<wa-dropdown-item slot="submenu" value="pdf">PDF</wa-dropdown-item>
|
||||
<wa-dropdown-item slot="submenu" value="docx">Word Document</wa-dropdown-item>
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-dropdown-item>
|
||||
Spreadsheets
|
||||
<wa-dropdown-item slot="submenu">
|
||||
Excel Formats
|
||||
<wa-dropdown-item slot="submenu" value="xlsx">Excel (.xlsx)</wa-dropdown-item>
|
||||
<wa-dropdown-item slot="submenu" value="xls">Excel 97-2003 (.xls)</wa-dropdown-item>
|
||||
<wa-dropdown-item slot="submenu" value="csv">CSV (.csv)</wa-dropdown-item>
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-dropdown-item slot="submenu">
|
||||
Other Formats
|
||||
<wa-dropdown-item slot="submenu" value="ods">OpenDocument (.ods)</wa-dropdown-item>
|
||||
<wa-dropdown-item slot="submenu" value="tsv">Tab-separated (.tsv)</wa-dropdown-item>
|
||||
<wa-dropdown-item slot="submenu" value="json">JSON (.json)</wa-dropdown-item>
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-dropdown-item slot="submenu" value="numbers">Apple Numbers</wa-dropdown-item>
|
||||
</wa-dropdown-item>
|
||||
|
||||
<wa-menu style="max-width: 200px;">
|
||||
<wa-menu-item value="undo">Undo</wa-menu-item>
|
||||
<wa-menu-item value="redo">Redo</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item value="cut">Cut</wa-menu-item>
|
||||
<wa-menu-item value="copy">Copy</wa-menu-item>
|
||||
<wa-menu-item value="paste">Paste</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item>
|
||||
Find
|
||||
<wa-menu slot="submenu">
|
||||
<wa-menu-item value="find">Find…</wa-menu-item>
|
||||
<wa-menu-item value="find-previous">Find Next</wa-menu-item>
|
||||
<wa-menu-item value="find-next">Find Previous</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
Transformations
|
||||
<wa-menu slot="submenu">
|
||||
<wa-menu-item value="uppercase">Make uppercase</wa-menu-item>
|
||||
<wa-menu-item value="lowercase">Make lowercase</wa-menu-item>
|
||||
<wa-menu-item value="capitalize">Capitalize</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
|
||||
<wa-dropdown-item>
|
||||
Options
|
||||
<wa-dropdown-item slot="submenu" type="checkbox" value="compress">Compress files</wa-dropdown-item>
|
||||
<wa-dropdown-item slot="submenu" type="checkbox" checked value="metadata">Include metadata</wa-dropdown-item>
|
||||
<wa-dropdown-item slot="submenu" type="checkbox" value="password">Password protect</wa-dropdown-item>
|
||||
</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const container = document.querySelector('.dropdown-submenus');
|
||||
const dropdown = container.querySelector('wa-dropdown');
|
||||
|
||||
dropdown.addEventListener('wa-select', event => {
|
||||
console.log(event.detail.item.value);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
:::info
|
||||
Dropdown items that have a submenu will not dispatch the `wa-select` event. However, items inside the submenu will, unless they also have a submenu.
|
||||
:::
|
||||
|
||||
:::warning
|
||||
As a UX best practice, avoid using more than one level of submenu when possible.
|
||||
:::
|
||||
|
||||
### Disabling Items
|
||||
|
||||
Add the `disabled` attribute to any [dropdown item](/docs/components/dropdown-item) to disable it.
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" 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>
|
||||
<wa-dropdown-item value="credit">Credit card</wa-dropdown-item>
|
||||
<wa-dropdown-item value="gift-card">Gift card</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
```
|
||||
|
||||
@@ -109,24 +109,24 @@ The `type` attribute controls the type of input the browser renders.
|
||||
<wa-input type="date" placeholder="Date"></wa-input>
|
||||
```
|
||||
|
||||
### Prefix & Suffix Icons
|
||||
### Start & End Decorations
|
||||
|
||||
Use the `prefix` and `suffix` slots to add icons.
|
||||
Use the `start` and `end` slots to add presentational elements like `<wa-icon>` within the input.
|
||||
|
||||
```html {.example}
|
||||
<wa-input placeholder="Small" size="small">
|
||||
<wa-icon name="house" variant="solid" slot="prefix"></wa-icon>
|
||||
<wa-icon name="comment" variant="solid" slot="suffix"></wa-icon>
|
||||
<wa-icon name="house" slot="start"></wa-icon>
|
||||
<wa-icon name="comment" slot="end"></wa-icon>
|
||||
</wa-input>
|
||||
<br />
|
||||
<wa-input placeholder="Medium" size="medium">
|
||||
<wa-icon name="house" variant="solid" slot="prefix"></wa-icon>
|
||||
<wa-icon name="comment" variant="solid" slot="suffix"></wa-icon>
|
||||
<wa-icon name="house" slot="start"></wa-icon>
|
||||
<wa-icon name="comment" slot="end"></wa-icon>
|
||||
</wa-input>
|
||||
<br />
|
||||
<wa-input placeholder="Large" size="large">
|
||||
<wa-icon name="house" variant="solid" slot="prefix"></wa-icon>
|
||||
<wa-icon name="comment" variant="solid" slot="suffix"></wa-icon>
|
||||
<wa-icon name="house" slot="start"></wa-icon>
|
||||
<wa-icon name="comment" slot="end"></wa-icon>
|
||||
</wa-input>
|
||||
```
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
---
|
||||
title: Menu Item
|
||||
description: Menu items provide options for the user to pick from in a menu.
|
||||
tags: component
|
||||
parent: menu
|
||||
icon: menu
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
<wa-menu style="max-width: 200px;">
|
||||
<wa-menu-item>Option 1</wa-menu-item>
|
||||
<wa-menu-item>Option 2</wa-menu-item>
|
||||
<wa-menu-item>Option 3</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item type="checkbox" checked>Checkbox</wa-menu-item>
|
||||
<wa-menu-item disabled>Disabled</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item>
|
||||
Prefix Icon
|
||||
<wa-icon slot="prefix" name="gift" variant="solid"></wa-icon>
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
Suffix Icon
|
||||
<wa-icon slot="suffix" name="heart" variant="solid"></wa-icon>
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Prefix & Suffix
|
||||
|
||||
Add content to the start and end of menu items using the `prefix` and `suffix` slots.
|
||||
|
||||
```html {.example}
|
||||
<wa-menu style="max-width: 200px;">
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="house" variant="solid"></wa-icon>
|
||||
Home
|
||||
</wa-menu-item>
|
||||
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="envelope" variant="solid"></wa-icon>
|
||||
Messages
|
||||
<wa-badge slot="suffix" variant="brand" pill>12</wa-badge>
|
||||
</wa-menu-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon>
|
||||
Settings
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
Add the `disabled` attribute to disable the menu item so it cannot be selected.
|
||||
|
||||
```html {.example}
|
||||
<wa-menu style="max-width: 200px;">
|
||||
<wa-menu-item>Option 1</wa-menu-item>
|
||||
<wa-menu-item disabled>Option 2</wa-menu-item>
|
||||
<wa-menu-item>Option 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
```
|
||||
|
||||
### Loading
|
||||
|
||||
Use the `loading` attribute to indicate that a menu item is busy. Like a disabled menu item, clicks will be suppressed until the loading state is removed.
|
||||
|
||||
```html {.example}
|
||||
<wa-menu style="max-width: 200px;">
|
||||
<wa-menu-item>Option 1</wa-menu-item>
|
||||
<wa-menu-item loading>Option 2</wa-menu-item>
|
||||
<wa-menu-item>Option 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
```
|
||||
|
||||
### Checkbox Menu Items
|
||||
|
||||
Set the `type` attribute to `checkbox` to create a menu item that will toggle on and off when selected. You can use the `checked` attribute to set the initial state.
|
||||
|
||||
Checkbox menu items are visually indistinguishable from regular menu items. Their ability to be toggled is primarily inferred from context, much like you'd find in the menu of a native app.
|
||||
|
||||
```html {.example}
|
||||
<wa-menu style="max-width: 200px;">
|
||||
<wa-menu-item type="checkbox">Autosave</wa-menu-item>
|
||||
<wa-menu-item type="checkbox" checked>Check Spelling</wa-menu-item>
|
||||
<wa-menu-item type="checkbox">Word Wrap</wa-menu-item>
|
||||
</wa-menu>
|
||||
```
|
||||
|
||||
### Value & Selection
|
||||
|
||||
The `value` attribute can be used to assign a hidden value, such as a unique identifier, to a menu item. When an item is selected, the `wa-select` event will be emitted and a reference to the item will be available at `event.detail.item`. You can use this reference to access the selected item's value, its checked state, and more.
|
||||
|
||||
```html {.example}
|
||||
<wa-menu class="menu-value" style="max-width: 200px;">
|
||||
<wa-menu-item value="opt-1">Option 1</wa-menu-item>
|
||||
<wa-menu-item value="opt-2">Option 2</wa-menu-item>
|
||||
<wa-menu-item value="opt-3">Option 3</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item type="checkbox" value="opt-4">Checkbox 4</wa-menu-item>
|
||||
<wa-menu-item type="checkbox" value="opt-5">Checkbox 5</wa-menu-item>
|
||||
<wa-menu-item type="checkbox" value="opt-6">Checkbox 6</wa-menu-item>
|
||||
</wa-menu>
|
||||
|
||||
<script>
|
||||
const menu = document.querySelector('.menu-value');
|
||||
|
||||
menu.addEventListener('wa-select', event => {
|
||||
const item = event.detail.item;
|
||||
|
||||
// Log value
|
||||
if (item.type === 'checkbox') {
|
||||
console.log(`Selected value: ${item.value} (${item.checked ? 'checked' : 'unchecked'})`);
|
||||
} else {
|
||||
console.log(`Selected value: ${item.value}`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
title: Menu Label
|
||||
description: Menu labels are used to describe a group of menu items.
|
||||
tags: component
|
||||
parent: menu
|
||||
icon: menu
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
<wa-menu style="max-width: 200px;">
|
||||
<wa-menu-label>Fruits</wa-menu-label>
|
||||
<wa-menu-item value="apple">Apple</wa-menu-item>
|
||||
<wa-menu-item value="banana">Banana</wa-menu-item>
|
||||
<wa-menu-item value="orange">Orange</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-label>Vegetables</wa-menu-label>
|
||||
<wa-menu-item value="broccoli">Broccoli</wa-menu-item>
|
||||
<wa-menu-item value="carrot">Carrot</wa-menu-item>
|
||||
<wa-menu-item value="zucchini">Zucchini</wa-menu-item>
|
||||
</wa-menu>
|
||||
```
|
||||
@@ -1,77 +0,0 @@
|
||||
---
|
||||
title: Menu
|
||||
description: Menus provide a list of options for the user to choose from.
|
||||
tags: [actions, apps]
|
||||
icon: menu
|
||||
---
|
||||
|
||||
You can use [menu items](/docs/components/menu-item), [menu labels](/docs/components/menu-label), and [dividers](/docs/components/divider) to compose a menu. Menus support keyboard interactions, including type-to-select an option.
|
||||
|
||||
```html {.example}
|
||||
<wa-menu style="max-width: 200px;">
|
||||
<wa-menu-item value="undo">Undo</wa-menu-item>
|
||||
<wa-menu-item value="redo">Redo</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item value="cut">Cut</wa-menu-item>
|
||||
<wa-menu-item value="copy">Copy</wa-menu-item>
|
||||
<wa-menu-item value="paste">Paste</wa-menu-item>
|
||||
<wa-menu-item value="delete">Delete</wa-menu-item>
|
||||
</wa-menu>
|
||||
```
|
||||
|
||||
:::info
|
||||
Menus are intended for system menus (dropdown menus, select menus, context menus, etc.). They should not be mistaken for navigation menus which serve a different purpose and have a different semantic meaning. If you're building navigation, use `<nav>` and `<a>` elements instead.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
### In Dropdowns
|
||||
|
||||
Menus work really well when used inside [dropdowns](/docs/components/dropdown).
|
||||
|
||||
```html {.example}
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Edit</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item value="cut">Cut</wa-menu-item>
|
||||
<wa-menu-item value="copy">Copy</wa-menu-item>
|
||||
<wa-menu-item value="paste">Paste</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
```
|
||||
|
||||
### Submenus
|
||||
|
||||
To create a submenu, nest an `<wa-menu slot="submenu">` in any [menu item](/docs/components/menu-item).
|
||||
|
||||
```html {.example}
|
||||
<wa-menu style="max-width: 200px;">
|
||||
<wa-menu-item value="undo">Undo</wa-menu-item>
|
||||
<wa-menu-item value="redo">Redo</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item value="cut">Cut</wa-menu-item>
|
||||
<wa-menu-item value="copy">Copy</wa-menu-item>
|
||||
<wa-menu-item value="paste">Paste</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item>
|
||||
Find
|
||||
<wa-menu slot="submenu">
|
||||
<wa-menu-item value="find">Find…</wa-menu-item>
|
||||
<wa-menu-item value="find-previous">Find Next</wa-menu-item>
|
||||
<wa-menu-item value="find-next">Find Previous</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
Transformations
|
||||
<wa-menu slot="submenu">
|
||||
<wa-menu-item value="uppercase">Make uppercase</wa-menu-item>
|
||||
<wa-menu-item value="lowercase">Make lowercase</wa-menu-item>
|
||||
<wa-menu-item value="capitalize">Capitalize</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
```
|
||||
|
||||
:::warning
|
||||
As a UX best practice, avoid using more than one level of submenus when possible.
|
||||
:::
|
||||
@@ -6,50 +6,4 @@ parent: select
|
||||
icon: option
|
||||
---
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
### Prefix & Suffix
|
||||
|
||||
Add icons to the start and end of menu items using the `prefix` and `suffix` slots.
|
||||
|
||||
```html {.example}
|
||||
<wa-select label="Select one">
|
||||
<wa-option value="option-1">
|
||||
<wa-icon slot="prefix" name="envelope" variant="solid"></wa-icon>
|
||||
Email
|
||||
<wa-icon slot="suffix" name="circle-check" variant="solid"></wa-icon>
|
||||
</wa-option>
|
||||
|
||||
<wa-option value="option-2">
|
||||
<wa-icon slot="prefix" name="phone" variant="solid"></wa-icon>
|
||||
Phone
|
||||
<wa-icon slot="suffix" name="circle-check" variant="solid"></wa-icon>
|
||||
</wa-option>
|
||||
|
||||
<wa-option value="option-3">
|
||||
<wa-icon slot="prefix" name="comment" variant="solid"></wa-icon>
|
||||
Chat
|
||||
<wa-icon slot="suffix" name="circle-check" variant="solid"></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.
|
||||
|
||||
@@ -137,7 +137,7 @@ Use the [`autofocus`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_a
|
||||
</wa-popover>
|
||||
|
||||
<wa-button id="popover__autofocus">
|
||||
<wa-icon name="comment" slot="prefix"></wa-icon>
|
||||
<wa-icon name="comment" slot="start"></wa-icon>
|
||||
Feedback
|
||||
</wa-button>
|
||||
```
|
||||
@@ -7,9 +7,7 @@ icon: progress-bar
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
<wa-progress-bar value="40">
|
||||
<wa-icon slot="prefix" name="tasks"></wa-icon>
|
||||
</wa-progress-bar>
|
||||
<wa-progress-bar value="40"></wa-progress-bar>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -6,9 +6,7 @@ icon: progress-ring
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
<wa-progress-ring value="25">
|
||||
<wa-icon slot="prefix" name="circle-notch"></wa-icon>
|
||||
</wa-progress-ring>
|
||||
<wa-progress-ring value="25"></wa-progress-ring>
|
||||
```
|
||||
|
||||
## Examples
|
||||
@@ -18,9 +16,7 @@ icon: progress-ring
|
||||
Use the `--size` custom property to set the diameter of the progress ring.
|
||||
|
||||
```html {.example}
|
||||
<wa-progress-ring value="50" style="--size: 200px;">
|
||||
<wa-icon slot="prefix" name="expand"></wa-icon>
|
||||
</wa-progress-ring>
|
||||
<wa-progress-ring value="50" style="--size: 200px;"></wa-progress-ring>
|
||||
```
|
||||
|
||||
### Track and Indicator Width
|
||||
@@ -28,9 +24,7 @@ Use the `--size` custom property to set the diameter of the progress ring.
|
||||
Use the `--track-width` and `--indicator-width` custom properties to set the width of the progress ring's track and indicator.
|
||||
|
||||
```html {.example}
|
||||
<wa-progress-ring value="50" style="--track-width: 6px; --indicator-width: 12px;">
|
||||
<wa-icon slot="prefix" name="arrows-alt"></wa-icon>
|
||||
</wa-progress-ring>
|
||||
<wa-progress-ring value="50" style="--track-width: 6px; --indicator-width: 12px;"></wa-progress-ring>
|
||||
```
|
||||
|
||||
### Colors
|
||||
@@ -45,7 +39,6 @@ To change the color, use the `--track-color` and `--indicator-color` custom prop
|
||||
--indicator-color: deeppink;
|
||||
"
|
||||
>
|
||||
<wa-icon slot="prefix" name="palette"></wa-icon>
|
||||
</wa-progress-ring>
|
||||
```
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ QR codes are useful for providing small pieces of information to users who can q
|
||||
<br />
|
||||
|
||||
<wa-input maxlength="255" with-clear label="Value">
|
||||
<wa-icon slot="prefix" name="link"></wa-icon>
|
||||
<wa-icon slot="start" name="link"></wa-icon>
|
||||
</wa-input>
|
||||
</div>
|
||||
|
||||
@@ -46,9 +46,7 @@ QR codes are useful for providing small pieces of information to users who can q
|
||||
Use the `fill` and `background` attributes to modify the QR code's colors. You should always ensure good contrast for optimal compatibility with QR code scanners.
|
||||
|
||||
```html {.example}
|
||||
<wa-qr-code value="https://shoelace.style/" fill="deeppink" background="white">
|
||||
<wa-icon slot="prefix" name="palette"></wa-icon>
|
||||
</wa-qr-code>
|
||||
<wa-qr-code value="https://shoelace.style/" fill="deeppink" background="white"></wa-qr-code>
|
||||
```
|
||||
|
||||
### Size
|
||||
@@ -56,9 +54,7 @@ Use the `fill` and `background` attributes to modify the QR code's colors. You s
|
||||
Use the `size` attribute to change the size of the QR code.
|
||||
|
||||
```html {.example}
|
||||
<wa-qr-code value="https://shoelace.style/" size="64">
|
||||
<wa-icon slot="prefix" name="expand"></wa-icon>
|
||||
</wa-qr-code>
|
||||
<wa-qr-code value="https://shoelace.style/" size="64"></wa-qr-code>
|
||||
```
|
||||
|
||||
### Radius
|
||||
@@ -66,9 +62,7 @@ Use the `size` attribute to change the size of the QR code.
|
||||
Create a rounded effect with the `radius` attribute.
|
||||
|
||||
```html {.example}
|
||||
<wa-qr-code value="https://shoelace.style/" radius="0.5">
|
||||
<wa-icon slot="prefix" name="circle"></wa-icon>
|
||||
</wa-qr-code>
|
||||
<wa-qr-code value="https://shoelace.style/" radius="0.5"></wa-qr-code>
|
||||
```
|
||||
|
||||
### Error Correction
|
||||
@@ -77,18 +71,10 @@ QR codes can be rendered with various levels of [error correction](https://www.q
|
||||
|
||||
```html {.example}
|
||||
<div class="qr-error-correction">
|
||||
<wa-qr-code value="https://shoelace.style/" error-correction="L">
|
||||
<wa-icon slot="prefix" name="shield"></wa-icon>
|
||||
</wa-qr-code>
|
||||
<wa-qr-code value="https://shoelace.style/" error-correction="M">
|
||||
<wa-icon slot="prefix" name="shield"></wa-icon>
|
||||
</wa-qr-code>
|
||||
<wa-qr-code value="https://shoelace.style/" error-correction="Q">
|
||||
<wa-icon slot="prefix" name="shield"></wa-icon>
|
||||
</wa-qr-code>
|
||||
<wa-qr-code value="https://shoelace.style/" error-correction="H">
|
||||
<wa-icon slot="prefix" name="shield"></wa-icon>
|
||||
</wa-qr-code>
|
||||
<wa-qr-code value="https://shoelace.style/" error-correction="L"></wa-qr-code>
|
||||
<wa-qr-code value="https://shoelace.style/" error-correction="M"></wa-qr-code>
|
||||
<wa-qr-code value="https://shoelace.style/" error-correction="Q"></wa-qr-code>
|
||||
<wa-qr-code value="https://shoelace.style/" error-correction="H"></wa-qr-code>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -7,71 +7,4 @@ native: radio
|
||||
icon: radio-group
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
@@ -208,54 +208,30 @@ The preferred placement of the select's listbox can be set with the `placement`
|
||||
</wa-select>
|
||||
```
|
||||
|
||||
### Prefix Icons
|
||||
### Start & End Decorations
|
||||
|
||||
Use the `prefix` slot to prepend an icon to the control.
|
||||
Use the `start` and `end` slots to add presentational elements like `<wa-icon>` within the combobox.
|
||||
|
||||
```html {.example}
|
||||
<wa-select placeholder="Small" size="small" with-clear>
|
||||
<wa-icon slot="prefix" name="house" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="house" variant="solid"></wa-icon>
|
||||
<wa-icon slot="end" name="flag-checkered"></wa-icon>
|
||||
<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>
|
||||
<br />
|
||||
<wa-select placeholder="Medium" size="medium" with-clear>
|
||||
<wa-icon slot="prefix" name="house" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="house" variant="solid"></wa-icon>
|
||||
<wa-icon slot="end" name="flag-checkered"></wa-icon>
|
||||
<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>
|
||||
<br />
|
||||
<wa-select placeholder="Large" size="large" with-clear>
|
||||
<wa-icon slot="prefix" name="house" variant="solid"></wa-icon>
|
||||
<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>
|
||||
```
|
||||
|
||||
### Suffix Icons
|
||||
|
||||
Use the `suffix` slot to append an icon to the control.
|
||||
|
||||
```html {.example}
|
||||
<wa-select placeholder="Small" size="small" with-clear>
|
||||
<wa-icon name="house" slot="suffix"></wa-icon>
|
||||
<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>
|
||||
<br />
|
||||
<wa-select placeholder="Medium" size="medium" with-clear>
|
||||
<wa-icon name="house" slot="suffix"></wa-icon>
|
||||
<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>
|
||||
<br />
|
||||
<wa-select placeholder="Large" size="large" with-clear>
|
||||
<wa-icon name="house" slot="suffix"></wa-icon>
|
||||
<wa-icon slot="start" name="house" variant="solid"></wa-icon>
|
||||
<wa-icon slot="end" name="flag-checkered"></wa-icon>
|
||||
<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>
|
||||
@@ -277,15 +253,15 @@ Remember that custom tags are rendered in a shadow root. To style them, you can
|
||||
class="custom-tag"
|
||||
>
|
||||
<wa-option value="email">
|
||||
<wa-icon slot="prefix" name="envelope" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="envelope" variant="solid"></wa-icon>
|
||||
Email
|
||||
</wa-option>
|
||||
<wa-option value="phone">
|
||||
<wa-icon slot="prefix" name="phone" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="phone" variant="solid"></wa-icon>
|
||||
Phone
|
||||
</wa-option>
|
||||
<wa-option value="chat">
|
||||
<wa-icon slot="prefix" name="comment" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="comment" variant="solid"></wa-icon>
|
||||
Chat
|
||||
</wa-option>
|
||||
</wa-select>
|
||||
@@ -297,7 +273,7 @@ Remember that custom tags are rendered in a shadow root. To style them, you can
|
||||
|
||||
select.getTag = (option, index) => {
|
||||
// Use the same icon used in wa-option
|
||||
const name = option.querySelector('wa-icon[slot="prefix"]').name;
|
||||
const name = option.querySelector('wa-icon[slot="start"]').name;
|
||||
|
||||
// You can return a string, a Lit Template, or an HTMLElement here
|
||||
return `
|
||||
|
||||
@@ -6,20 +6,4 @@ parent: tab-group
|
||||
icon: tab-panel
|
||||
---
|
||||
|
||||
```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.
|
||||
|
||||
@@ -6,6 +6,4 @@ parent: tab-group
|
||||
icon: tab
|
||||
---
|
||||
|
||||
:::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.
|
||||
|
||||
@@ -5,78 +5,4 @@ tags: [navigation, disclosure, apps]
|
||||
icon: tree
|
||||
---
|
||||
|
||||
```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.
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
title: Clamped Color Tokens
|
||||
layout: block
|
||||
---
|
||||
|
||||
{% set tints = ['max-50', 'max-60', 'max-70', 'min-50', 'min-60', 'min-70'] %}
|
||||
{% set hues = ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'indigo', 'purple', 'pink', 'gray'] %}
|
||||
|
||||
<table class="colors">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="core-column">Core tint</th>
|
||||
{% for tint in tints -%}
|
||||
<th>{{ tint }}</th>
|
||||
{%- endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
{% for hue in hues -%}
|
||||
<tr class="wa-color-{{ hue }}">
|
||||
<th>{{ hue | capitalize }}</th>
|
||||
<td class="core-column">
|
||||
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-on); --key: var(--wa-color-{{ hue }}-key);">
|
||||
{{ palettes[paletteId][hue].maxChromaTint }}
|
||||
<wa-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
|
||||
</div>
|
||||
</td>
|
||||
{% for tint in tints -%}
|
||||
<td>
|
||||
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint }})">
|
||||
<wa-copy-button value="--wa-color-{{ hue }}-{{ tint }}" copy-label="--wa-color-{{ hue }}-{{ tint }}"></wa-copy-button>
|
||||
</div>
|
||||
</td>
|
||||
{%- endfor -%}
|
||||
</tr>
|
||||
{%- endfor %}
|
||||
</table>
|
||||
|
||||
<style>
|
||||
.core-column .color.swatch::before {
|
||||
counter-reset: key var(--key);
|
||||
content: counter(key);
|
||||
}
|
||||
</style>
|
||||
@@ -5,71 +5,74 @@ title: Size tests
|
||||
Button size should default to `medium`:
|
||||
|
||||
```html {.example}
|
||||
<wa-button size=small>Small</wa-button>
|
||||
<wa-button size="small">Small</wa-button>
|
||||
<wa-button>Medium</wa-button>
|
||||
<wa-button size=medium>Medium</wa-button>
|
||||
<wa-button size=large>Large</wa-button>
|
||||
<wa-button size="medium">Medium</wa-button>
|
||||
<wa-button size="large">Large</wa-button>
|
||||
```
|
||||
|
||||
If no button size is specified, it should default to that of its ancestor:
|
||||
|
||||
```html {.example}
|
||||
<wa-button-group size="small">
|
||||
<wa-button>Small 1</wa-button>
|
||||
<wa-button>Small 2</wa-button>
|
||||
<wa-button>Small 3</wa-button>
|
||||
<wa-button>Small 1</wa-button>
|
||||
<wa-button>Small 2</wa-button>
|
||||
<wa-button>Small 3</wa-button>
|
||||
</wa-button-group>
|
||||
<br><br>
|
||||
<br /><br />
|
||||
<wa-button-group>
|
||||
<wa-button>Medium 1</wa-button>
|
||||
<wa-button>Medium 2</wa-button>
|
||||
<wa-button>Medium 3</wa-button>
|
||||
<wa-button>Medium 1</wa-button>
|
||||
<wa-button>Medium 2</wa-button>
|
||||
<wa-button>Medium 3</wa-button>
|
||||
</wa-button-group>
|
||||
<br><br>
|
||||
<br /><br />
|
||||
<wa-button-group size="large">
|
||||
<wa-button>Large 1</wa-button>
|
||||
<wa-button>Large 2</wa-button>
|
||||
<wa-button>Large 3</wa-button>
|
||||
<wa-button>Large 1</wa-button>
|
||||
<wa-button>Large 2</wa-button>
|
||||
<wa-button>Large 3</wa-button>
|
||||
</wa-button-group>
|
||||
```
|
||||
|
||||
Dropdown:
|
||||
|
||||
```html {.example}
|
||||
<p>Small dropdown:
|
||||
<wa-dropdown size="small">
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Dropdown Item 1</wa-menu-item>
|
||||
<wa-menu-item>Dropdown Item 2</wa-menu-item>
|
||||
<wa-menu-item>Dropdown Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<p>Small menu:
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu size="small">
|
||||
<wa-menu-item>Dropdown Item 1</wa-menu-item>
|
||||
<wa-menu-item>Dropdown Item 2</wa-menu-item>
|
||||
<wa-menu-item>Dropdown Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<p>Small menu item:
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item size="small">Dropdown Item 1</wa-menu-item>
|
||||
<wa-menu-item size="small">Dropdown Item 2</wa-menu-item>
|
||||
<wa-menu-item size="small">Dropdown Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<p>No size:
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Dropdown</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Dropdown Item 1</wa-menu-item>
|
||||
<wa-menu-item>Dropdown Item 2</wa-menu-item>
|
||||
<wa-menu-item>Dropdown Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
<p>
|
||||
Small dropdown:
|
||||
<wa-dropdown size="small">
|
||||
<wa-button slot="trigger" 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>
|
||||
</wa-dropdown>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Small menu:
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" 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>
|
||||
</wa-dropdown>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Small menu item:
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" 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>
|
||||
</wa-dropdown>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
No size:
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" 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>
|
||||
</wa-dropdown>
|
||||
</p>
|
||||
```
|
||||
|
||||
@@ -671,7 +671,7 @@ hasOutline: false
|
||||
<div style="display: grid; grid-template-rows: minmax(0, auto) minmax(0, 1fr); height: 100%; gap: 1rem;">
|
||||
<div style="display: flex; gap: 1.25rem;">
|
||||
<wa-input name="icon-search" autofocus placeholder="Search Icons" with-clear style="flex: 1 1 auto;">
|
||||
<wa-icon slot="prefix" name="magnifying-glass"></wa-icon>
|
||||
<wa-icon slot="start" name="magnifying-glass"></wa-icon>
|
||||
</wa-input>
|
||||
<wa-select name="icon-variant" value="solid" style="flex: 0 1 auto;">
|
||||
<wa-option value="solid">Solid</wa-option>
|
||||
@@ -2017,7 +2017,7 @@ hasOutline: false
|
||||
<div class="title">
|
||||
<h1 class="hero-title">What you know you can't explain, but you feel it.</h1>
|
||||
<wa-button variant="brand" class="hero-cta">
|
||||
<wa-icon slot="prefix" name="arrow-down"></wa-icon>
|
||||
<wa-icon slot="start" name="arrow-down"></wa-icon>
|
||||
Free Your Mind
|
||||
</wa-button>
|
||||
</div>
|
||||
@@ -2037,11 +2037,11 @@ hasOutline: false
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="prefix" name="plus" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="plus" variant="regular"></wa-icon>
|
||||
Add to Cart
|
||||
</wa-button>
|
||||
<wa-button size="small" appearance="outline">
|
||||
<wa-icon slot="prefix" name="bookmark" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="bookmark" variant="regular"></wa-icon>
|
||||
Save
|
||||
</wa-button>
|
||||
</div>
|
||||
@@ -2060,11 +2060,11 @@ hasOutline: false
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="prefix" name="plus" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="plus" variant="regular"></wa-icon>
|
||||
Add to Cart
|
||||
</wa-button>
|
||||
<wa-button size="small" appearance="outline">
|
||||
<wa-icon slot="prefix" name="bookmark" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="bookmark" variant="regular"></wa-icon>
|
||||
Save
|
||||
</wa-button>
|
||||
</div>
|
||||
@@ -2082,11 +2082,11 @@ hasOutline: false
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="prefix" name="plus" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="plus" variant="regular"></wa-icon>
|
||||
Add to Cart
|
||||
</wa-button>
|
||||
<wa-button size="small" appearance="outline">
|
||||
<wa-icon slot="prefix" name="bookmark" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="bookmark" variant="regular"></wa-icon>
|
||||
Save
|
||||
</wa-button>
|
||||
</div>
|
||||
@@ -2236,7 +2236,7 @@ hasOutline: false
|
||||
</div>
|
||||
<div class="send">
|
||||
<wa-button variant="brand" size="small">
|
||||
<wa-icon slot="prefix" name="paper-plane-top" variant="solid" label="Add File"></wa-icon>
|
||||
<wa-icon slot="start" name="paper-plane-top" variant="solid" label="Add File"></wa-icon>
|
||||
Send
|
||||
</wa-button>
|
||||
</div>
|
||||
@@ -2286,11 +2286,11 @@ hasOutline: false
|
||||
<div style="display: flex; align-items: end; gap: 1rem;">
|
||||
<wa-input type="number" label="How many?"></wa-input>
|
||||
<wa-button variant="brand">
|
||||
<wa-icon slot="prefix" name="bag-shopping" variant="solid" label="Add to Basket"></wa-icon>
|
||||
<wa-icon slot="start" name="bag-shopping" variant="solid" label="Add to Basket"></wa-icon>
|
||||
Add to Basket
|
||||
</wa-button>
|
||||
<wa-button variant="neutral">
|
||||
<wa-icon slot="prefix" name="bookmark" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="bookmark" variant="regular"></wa-icon>
|
||||
Save
|
||||
</wa-button>
|
||||
</div>
|
||||
@@ -2320,24 +2320,22 @@ hasOutline: false
|
||||
<td>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret size="small">Action</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="clock" variant="regular"></wa-icon>
|
||||
Pending
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="arrow-rotate-left" variant="regular"></wa-icon>
|
||||
Re-open
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="xmark" variant="regular"></wa-icon>
|
||||
Delete
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="clock" variant="regular"></wa-icon>
|
||||
Pending
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="arrow-rotate-left" variant="regular"></wa-icon>
|
||||
Re-open
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="xmark" variant="regular"></wa-icon>
|
||||
Delete
|
||||
</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -2348,26 +2346,24 @@ hasOutline: false
|
||||
<td><wa-avatar image="/assets/images/themer/avatar-char.jpg" label="Char" style="margin-right: var(--wa-space-xs)"></wa-avatar> Char McCoy</td>
|
||||
<td style="text-align: center;"><wa-tag variant="success" size="small">Resolved</wa-tag></td>
|
||||
<td>
|
||||
<wa-dropdown>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret size="small">Action</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="clock" variant="regular"></wa-icon>
|
||||
Pending
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="arrow-rotate-left" variant="regular"></wa-icon>
|
||||
Re-open
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="xmark" variant="regular"></wa-icon>
|
||||
Delete
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="clock" variant="regular"></wa-icon>
|
||||
Pending
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="arrow-rotate-left" variant="regular"></wa-icon>
|
||||
Re-open
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="xmark" variant="regular"></wa-icon>
|
||||
Delete
|
||||
</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -2378,26 +2374,24 @@ hasOutline: false
|
||||
<td><wa-avatar initials="DE" label="Avatar with initials: DE" style="margin-right: var(--wa-space-xs)"></wa-avatar> Debbie Evans</td>
|
||||
<td style="text-align: center;"><wa-tag variant="warning" size="small">Pending</wa-tag></td>
|
||||
<td>
|
||||
<wa-dropdown>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret size="small">Action</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="clock" variant="regular"></wa-icon>
|
||||
Pending
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="arrow-rotate-left" variant="regular"></wa-icon>
|
||||
Re-open
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="xmark" variant="regular"></wa-icon>
|
||||
Delete
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="clock" variant="regular"></wa-icon>
|
||||
Pending
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="arrow-rotate-left" variant="regular"></wa-icon>
|
||||
Re-open
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="xmark" variant="regular"></wa-icon>
|
||||
Delete
|
||||
</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -2408,26 +2402,24 @@ hasOutline: false
|
||||
<td></td>
|
||||
<td style="text-align: center;"><wa-tag variant="danger" size="small">Bounced</wa-tag></td>
|
||||
<td>
|
||||
<wa-dropdown>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret size="small">Action</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="clock" variant="regular"></wa-icon>
|
||||
Pending
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="arrow-rotate-left" variant="regular"></wa-icon>
|
||||
Re-open
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="xmark" variant="regular"></wa-icon>
|
||||
Delete
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="clock" variant="regular"></wa-icon>
|
||||
Pending
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="arrow-rotate-left" variant="regular"></wa-icon>
|
||||
Re-open
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="xmark" variant="regular"></wa-icon>
|
||||
Delete
|
||||
</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -2438,26 +2430,24 @@ hasOutline: false
|
||||
<td><wa-avatar image="/assets/images/themer/avatar-dara.jpg" label="Dara" style="margin-right: var(--wa-space-xs)"></wa-avatar> Dara Prescott</td>
|
||||
<td style="text-align: center;"><wa-tag variant="neutral" size="small">Expired</wa-tag></td>
|
||||
<td>
|
||||
<wa-dropdown>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret size="small">Action</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="clock" variant="regular"></wa-icon>
|
||||
Pending
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="arrow-rotate-left" variant="regular"></wa-icon>
|
||||
Re-open
|
||||
</wa-menu-item>
|
||||
<wa-menu-item>
|
||||
<wa-icon slot="prefix" name="xmark" variant="regular"></wa-icon>
|
||||
Delete
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="check" variant="regular"></wa-icon>
|
||||
Resolved
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="clock" variant="regular"></wa-icon>
|
||||
Pending
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="arrow-rotate-left" variant="regular"></wa-icon>
|
||||
Re-open
|
||||
</wa-dropdown-item>
|
||||
<wa-dropdown-item>
|
||||
<wa-icon slot="start" name="xmark" variant="regular"></wa-icon>
|
||||
Delete
|
||||
</wa-dropdown-item>
|
||||
</wa-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -2470,30 +2460,30 @@ hasOutline: false
|
||||
<h2 style="margin-bottom: var(--wa-space-3xl);">Payment</h2>
|
||||
<form>
|
||||
<wa-input type="email" placeholder="ex. tanderson@metacortex.com" label="Email">
|
||||
<wa-icon name="envelope" variant="regular" slot="prefix"></wa-icon>
|
||||
<wa-icon name="envelope" variant="regular" slot="start"></wa-icon>
|
||||
</wa-input>
|
||||
<wa-input placeholder="1234 1234 1234 1234" label="Card Number">
|
||||
<wa-icon name="credit-card" variant="regular" slot="prefix"></wa-icon>
|
||||
<wa-icon name="credit-card" variant="regular" slot="start"></wa-icon>
|
||||
</wa-input>
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
<wa-input placeholder="MM / YY" label="Expiration">
|
||||
<wa-icon name="calendar" variant="regular" slot="prefix"></wa-icon>
|
||||
<wa-icon name="calendar" variant="regular" slot="start"></wa-icon>
|
||||
</wa-input>
|
||||
<wa-input placeholder="CVC" label="CVC">
|
||||
<wa-icon name="lock" variant="regular" slot="prefix"></wa-icon>
|
||||
<wa-icon name="lock" variant="regular" slot="start"></wa-icon>
|
||||
</wa-input>
|
||||
</div>
|
||||
<wa-input placeholder="Thomas Anderson" label="Cardholder Name">
|
||||
<wa-icon name="user" variant="regular" slot="prefix"></wa-icon>
|
||||
<wa-icon name="user" variant="regular" slot="start"></wa-icon>
|
||||
</wa-input>
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
<wa-select label="Country" value="USA">
|
||||
<wa-icon slot="prefix" name="globe" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="globe" variant="regular"></wa-icon>
|
||||
<wa-option value="USA">United States</wa-option>
|
||||
<wa-option value="CAN">Canada</wa-option>
|
||||
</wa-select>
|
||||
<wa-input placeholder="12345" label="Zip">
|
||||
<wa-icon name="location-dot" variant="regular" slot="prefix"></wa-icon>
|
||||
<wa-icon name="location-dot" variant="regular" slot="start"></wa-icon>
|
||||
</wa-input>
|
||||
</div>
|
||||
<wa-switch checked style="margin: var(--wa-space-2xl) 0 var(--wa-space-3xl) 0;">Sign me up for more offers from this store</wa-switch>
|
||||
|
||||
@@ -14,6 +14,7 @@ During the alpha period, things might break! We take breaking changes very serio
|
||||
|
||||
## Next
|
||||
|
||||
- 🚨 BREAKING: `input` and `change` events on form controls like `<wa-input>` now are always set to `bubble` and `compose`.
|
||||
- 🚨 BREAKING: Greatly simplified how native styles work and removed redundant utilities
|
||||
- Removed `.wa-button`, `.wa-callout` classes
|
||||
- Removed `themes/native/*.css` files; use `native.css` to opt into native styles
|
||||
@@ -31,7 +32,7 @@ During the alpha period, things might break! We take breaking changes very serio
|
||||
- `<wa-tab-group no-scroll-controls>` => `<wa-tab-group without-scroll-controls>`
|
||||
- `<wa-tag removable>` => `<wa-tag with-remove>`
|
||||
- 🚨 BREAKING: removed the `size` attribute from `<wa-card>`; please set the size of child elements on the children directly
|
||||
- 🚨 BREAKING: Greatly simplified the sizing strategy across components and utilities
|
||||
- 🚨 BREAKING: greatly simplified the sizing strategy across components and utilities
|
||||
- Removed `--wa-size`, `--wa-size-smaller`, `--wa-size-larger`, `--wa-space`, `--wa-space-smaller`, and `--wa-space-larger`
|
||||
- Added tokens for `--wa-form-control-padding-inline`, `--wa-form-control-padding-block`, and `--wa-form-control-toggle-size`
|
||||
- Refactored default `--wa-font-size-*` values to use an apparent 1.125 ratio and round rendered values to the nearest whole pixel
|
||||
@@ -49,6 +50,15 @@ During the alpha period, things might break! We take breaking changes very serio
|
||||
- Improved the styling API to be consistent and more powerful (no more browser-specific selectors and pseudo elements to style)
|
||||
- Updated to use consistent `with-*` attribute naming pattern
|
||||
- 🚨 BREAKING: removed `<wa-icon-button>`; use `<wa-button><wa-icon name="..." label="..."></wa-icon></wa-button>` instead
|
||||
- 🚨 BREAKING: completely reworked `<wa-dropdown>` to be easier to use
|
||||
- Added `<wa-dropdown-item>`, greatly simplifying the dropdown's markup structure
|
||||
- Removed `<wa-menu>`, `<wa-menu-item>`, and `<wa-menu-label>`; use `<wa-dropdown-item>` and native headings instead
|
||||
- 🚨 BREAKING: renamed all `prefix` and `suffix` slots to `start` and `end`, affecting the following components:
|
||||
- `<wa-breadcrumb-item>`
|
||||
- `<wa-button>`
|
||||
- `<wa-input>`
|
||||
- `<wa-select>`
|
||||
- `<wa-option>`
|
||||
- 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]
|
||||
@@ -368,4 +378,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/)
|
||||
@@ -18,7 +18,7 @@ The [discussion forum](https://github.com/shoelace-style/shoelace/discussions) i
|
||||
- Learn more about the project, its values, and its roadmap
|
||||
|
||||
<wa-button variant="brand" href="https://github.com/shoelace-style/shoelace/discussions" target="_blank" style="margin-block-end: var(--wa-flow-spacing);">
|
||||
<wa-icon name="github" family="brands" slot="prefix"></wa-icon>
|
||||
<wa-icon name="github" family="brands" slot="start"></wa-icon>
|
||||
Join the Discussion
|
||||
</wa-button>
|
||||
|
||||
@@ -32,7 +32,7 @@ The [community chat](https://discord.gg/mg8f26C) is open to the public and power
|
||||
- Chat live with other designers, developers, and Web Awesome fans
|
||||
|
||||
<wa-button variant="brand" href="https://discord.gg/mg8f26C" target="_blank" style="margin-block-end: var(--wa-flow-spacing);">
|
||||
<wa-icon name="discord" family="brands" slot="prefix"></wa-icon>
|
||||
<wa-icon name="discord" family="brands" slot="start"></wa-icon>
|
||||
Join the Chat
|
||||
</wa-button>
|
||||
|
||||
@@ -43,6 +43,6 @@ Follow [@webawesomer](https://twitter.com/webawesomer) on Twitter for general up
|
||||
**Please avoid using Twitter for support questions.** The [discussion forum](https://github.com/shoelace-style/shoelace/discussions) is a much better place to share code snippets, screenshots, and other troubleshooting info. You'll have much better luck there, as more users will have a chance to help you.
|
||||
|
||||
<wa-button variant="brand" href="https://twitter.com/webawesomer" target="_blank" style="margin-block-end: var(--wa-flow-spacing);">
|
||||
<wa-icon name="twitter" family="brands" slot="prefix"></wa-icon>
|
||||
<wa-icon name="twitter" family="brands" slot="start"></wa-icon>
|
||||
Follow on Twitter
|
||||
</wa-button>
|
||||
|
||||
@@ -26,7 +26,7 @@ unlisted: true
|
||||
{% if theme.fileSlug === 'custom' %}
|
||||
<p>
|
||||
<wa-button href="../edit/" class="edit-link" target="_parent" appearance="outlined">
|
||||
<wa-icon slot="prefix" name="pencil"></wa-icon>
|
||||
<wa-icon slot="start" name="pencil"></wa-icon>
|
||||
Edit theme
|
||||
</wa-button>
|
||||
</p>
|
||||
|
||||
@@ -42,7 +42,7 @@ unlisted: true
|
||||
<wa-tab-panel name="css">
|
||||
<p>
|
||||
<wa-button variant="brand" :href="code.css.blob" :download="cssFilename">
|
||||
<wa-icon name="arrow-down-to-line" variant="solid" slot="prefix"></wa-icon>
|
||||
<wa-icon name="arrow-down-to-line" variant="solid" slot="start"></wa-icon>
|
||||
Download <code v-text="cssFilename"></code>
|
||||
</wa-button>
|
||||
</p>
|
||||
@@ -153,7 +153,7 @@ unlisted: true
|
||||
</icons-card>
|
||||
|
||||
<wa-input label="Font Awesome Pro Kit Code" v-model="theme.icon.kit" placeholder="e.g. f0nta7e50e">
|
||||
<info-tip slot="suffix"><template #content>You need a Font Awesome Pro license to use certain families and styles.</template></info-tip>
|
||||
<info-tip slot="end"><template #content>You need a Font Awesome Pro license to use certain families and styles.</template></info-tip>
|
||||
<a href="https://fontawesome.com/kits" target="_blank" slot="hint" class="wa-caption-m wa-cluster wa-gap-2xs">
|
||||
<span>Find your kit code here</span>
|
||||
<wa-icon name="arrow-up-right-from-square" variant="regular" style="font-size: 0.75em"></wa-icon>
|
||||
|
||||
@@ -36,7 +36,7 @@ noTheme: true
|
||||
<div class="title">
|
||||
<h1 class="hero-title">What you know you can't explain, but you feel it.</h1>
|
||||
<wa-button variant="brand" class="hero-cta">
|
||||
<wa-icon slot="prefix" name="arrow-down"></wa-icon>
|
||||
<wa-icon slot="start" name="arrow-down"></wa-icon>
|
||||
Free Your Mind
|
||||
</wa-button>
|
||||
</div>
|
||||
@@ -56,11 +56,11 @@ noTheme: true
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="prefix" name="plus" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="plus" variant="regular"></wa-icon>
|
||||
Add to Cart
|
||||
</wa-button>
|
||||
<wa-button size="small" appearance="outline">
|
||||
<wa-icon slot="prefix" name="bookmark" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="bookmark" variant="regular"></wa-icon>
|
||||
Save
|
||||
</wa-button>
|
||||
</div>
|
||||
@@ -79,11 +79,11 @@ noTheme: true
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="prefix" name="plus" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="plus" variant="regular"></wa-icon>
|
||||
Add to Cart
|
||||
</wa-button>
|
||||
<wa-button size="small" appearance="outline">
|
||||
<wa-icon slot="prefix" name="bookmark" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="bookmark" variant="regular"></wa-icon>
|
||||
Save
|
||||
</wa-button>
|
||||
</div>
|
||||
@@ -101,11 +101,11 @@ noTheme: true
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<wa-button size="small">
|
||||
<wa-icon slot="prefix" name="plus" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="plus" variant="regular"></wa-icon>
|
||||
Add to Cart
|
||||
</wa-button>
|
||||
<wa-button size="small" appearance="outline">
|
||||
<wa-icon slot="prefix" name="bookmark" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="bookmark" variant="regular"></wa-icon>
|
||||
Save
|
||||
</wa-button>
|
||||
</div>
|
||||
|
||||
@@ -108,11 +108,11 @@ For example, a button's default slot is used to populate its label.
|
||||
<wa-button>Click me</wa-button>
|
||||
```
|
||||
|
||||
Some components also have _named_ slots. A named slot can be populated by adding a child element with the appropriate `slot` attribute. Notice how the icon below has the `slot="prefix"` attribute? This tells the component to place the icon into its `prefix` slot.
|
||||
Some components also have _named_ slots. A named slot can be populated by adding a child element with the appropriate `slot` attribute. Notice how the icon below has the `slot="start"` attribute? This tells the component to place the icon into its `start` slot.
|
||||
|
||||
```html
|
||||
<wa-button>
|
||||
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon>
|
||||
<wa-icon slot="start" name="gear" variant="solid"></wa-icon>
|
||||
Settings
|
||||
</wa-button>
|
||||
```
|
||||
|
||||
@@ -37,7 +37,7 @@ Flanks work especially well for asides, inputs with adjacent buttons, and rich d
|
||||
```html {.example}
|
||||
<div class="wa-flank:end wa-gap-xs">
|
||||
<wa-input>
|
||||
<wa-icon slot="prefix" name="magnifying-glass"></wa-icon>
|
||||
<wa-icon slot="start" name="magnifying-glass"></wa-icon>
|
||||
</wa-input>
|
||||
<wa-button>Search</wa-button>
|
||||
</div>
|
||||
|
||||
@@ -77,16 +77,16 @@ Organized content in bulleted or numbered format with proper nesting support.
|
||||
<ol>
|
||||
<li>List item 1</li>
|
||||
<li>List item 2
|
||||
<ul>
|
||||
<ol>
|
||||
<li>Subitem a</li>
|
||||
<li>Subitem b</li>
|
||||
</ul>
|
||||
</ol>
|
||||
</li>
|
||||
<li>List item 3</li>
|
||||
</ol>
|
||||
```
|
||||
|
||||
### Definition Lists
|
||||
### Description Lists
|
||||
|
||||
Term and definition pairs for glossaries and descriptions.
|
||||
|
||||
@@ -141,19 +141,19 @@ Various text formatting elements for emphasis and semantic meaning.
|
||||
```html {.example}
|
||||
<div class="two-columns">
|
||||
<p><strong>Bold</strong></p>
|
||||
<p><em>Italics</em></p>
|
||||
<p><em>Italic</em></p>
|
||||
<p><u>Underline</u></p>
|
||||
<p><s>Strike-through</s></p>
|
||||
<p><del>Deleted</del></p>
|
||||
<p><ins>Inserted</ins></p>
|
||||
<p><s>Strike-through</s></p>
|
||||
<p><small>Small</small></p>
|
||||
<p><span>Text <sub>Sub</sub></span></p>
|
||||
<p><span>Text <sup>Sup</sup></span></p>
|
||||
<p><span>Subscript <sub>Sub</sub></span></p>
|
||||
<p><span>Superscript <sup>Sup</sup></span></p>
|
||||
<p><abbr title="Abbreviation">Abbr.</abbr></p>
|
||||
<p><kbd>Keyboard</kbd></p>
|
||||
<p><mark>Highlighted</mark></p>
|
||||
<p><a href="#">Link text</a></p>
|
||||
<p><code>Inline code</code></p>
|
||||
<p><kbd>Keyboard</kbd></p>
|
||||
</div>
|
||||
```
|
||||
|
||||
@@ -161,11 +161,13 @@ Various text formatting elements for emphasis and semantic meaning.
|
||||
|
||||
Formatted code snippets with proper syntax styling.
|
||||
|
||||
```
|
||||
```html {.example}
|
||||
<pre>
|
||||
// do a thing
|
||||
export function thing() {
|
||||
return true;
|
||||
}
|
||||
</pre>
|
||||
```
|
||||
|
||||
### Images
|
||||
@@ -454,3 +456,12 @@ Multi-line text input fields for longer content.
|
||||
```html {.example}
|
||||
<label>Textarea <textarea placeholder="Type something"></textarea></label>
|
||||
```
|
||||
|
||||
### Fieldsets
|
||||
|
||||
```html {.example}
|
||||
<fieldset>
|
||||
<legend>Legend</legend>
|
||||
Nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Tincidunt id aliquet risus feugiat in ante. Ac turpis egestas integer eget aliquet nibh praesent tristique magna.
|
||||
</fieldset>
|
||||
```
|
||||
@@ -36,10 +36,10 @@ Stacks are well suited for forms, text, and ensuring consistent spacing between
|
||||
```html {.example}
|
||||
<div class="wa-stack">
|
||||
<wa-input label="Email">
|
||||
<wa-icon slot="prefix" name="envelope" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="envelope" variant="regular"></wa-icon>
|
||||
</wa-input>
|
||||
<wa-input label="Password" type="password">
|
||||
<wa-icon slot="prefix" name="lock" variant="regular"></wa-icon>
|
||||
<wa-icon slot="start" name="lock" variant="regular"></wa-icon>
|
||||
</wa-input>
|
||||
<wa-checkbox>Remember me on this device</wa-checkbox>
|
||||
<wa-button>Log In</wa-button>
|
||||
|
||||
@@ -222,7 +222,7 @@ layout: page
|
||||
text-align: left;
|
||||
white-space: wrap;
|
||||
}
|
||||
wa-button.tile::part(suffix) {
|
||||
wa-button.tile::part(end) {
|
||||
display: none;
|
||||
}
|
||||
wa-button.tile {
|
||||
@@ -266,7 +266,7 @@ layout: page
|
||||
<div class="hero-cta">
|
||||
<span><em>Psst!</em> You can pre-order Web Awesome Pro at a low, guaranteed-for-life price — but not for long. Get in while the gettin’s good.</span>
|
||||
<wa-button class="wa-dark" size="small" href="https://www.kickstarter.com/projects/fontawesome/web-awesome">
|
||||
<wa-icon slot="prefix" name="person-running"></wa-icon>
|
||||
<wa-icon slot="start" name="person-running"></wa-icon>
|
||||
Pre-order WA Pro
|
||||
</wa-button>
|
||||
</div>
|
||||
|
||||
@@ -149,13 +149,14 @@ export async function build(options = {}) {
|
||||
if (process.env.ROOT_DIR) {
|
||||
process.chdir(process.env.ROOT_DIR);
|
||||
}
|
||||
execSync(`tsc --project ./tsconfig.prod.json --outdir "${getCdnDir()}"`);
|
||||
execSync(`tsc --project ./tsconfig.prod.json --outdir "${getCdnDir()}"`, { stdio: 'inherit' });
|
||||
process.chdir(cwd);
|
||||
} catch (error) {
|
||||
process.chdir(cwd);
|
||||
if (!isDeveloping) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return Promise.reject(error.stdout);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,25 +45,25 @@
|
||||
outline-offset: var(--wa-focus-ring-offset);
|
||||
}
|
||||
|
||||
.prefix,
|
||||
.suffix {
|
||||
.start,
|
||||
.end {
|
||||
display: none;
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.prefix,
|
||||
.suffix {
|
||||
.start,
|
||||
.end {
|
||||
display: inline-flex;
|
||||
color: var(--wa-color-text-quiet);
|
||||
}
|
||||
|
||||
::slotted([slot='prefix']) {
|
||||
::slotted([slot='start']) {
|
||||
margin-inline-end: var(--wa-space-s);
|
||||
}
|
||||
|
||||
::slotted([slot='suffix']) {
|
||||
::slotted([slot='end']) {
|
||||
margin-inline-start: var(--wa-space-s);
|
||||
}
|
||||
|
||||
|
||||
@@ -122,11 +122,11 @@ describe('<wa-breadcrumb-item>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided an element in the slot "prefix" to support prefix icons', () => {
|
||||
describe('when provided an element in the slot "start" to support start icons', () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item>
|
||||
<span class="prefix-example" slot="prefix">/</span>
|
||||
<span class="start-example" slot="start">/</span>
|
||||
Home
|
||||
</wa-breadcrumb-item>
|
||||
`);
|
||||
@@ -136,22 +136,22 @@ describe('<wa-breadcrumb-item>', () => {
|
||||
it('should accept as an assigned child in the shadow root', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item>
|
||||
<span class="prefix-example" slot="prefix">/</span>
|
||||
<span class="start-example" slot="start">/</span>
|
||||
Home
|
||||
</wa-breadcrumb-item>
|
||||
`);
|
||||
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=prefix]')!;
|
||||
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=start]')!;
|
||||
const childNodes = slot.assignedNodes({ flatten: true });
|
||||
|
||||
expect(childNodes.length).to.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided an element in the slot "suffix" to support suffix icons', () => {
|
||||
describe('when provided an element in the slot "end" to support end icons', () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item>
|
||||
<span class="prefix-example" slot="suffix">/</span>
|
||||
<span class="end-example" slot="end">/</span>
|
||||
Security
|
||||
</wa-breadcrumb-item>
|
||||
`);
|
||||
@@ -162,40 +162,16 @@ describe('<wa-breadcrumb-item>', () => {
|
||||
it('should accept as an assigned child in the shadow root', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item>
|
||||
<span class="prefix-example" slot="suffix">/</span>
|
||||
<span class="end-example" slot="end">/</span>
|
||||
Security
|
||||
</wa-breadcrumb-item>
|
||||
`);
|
||||
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=suffix]')!;
|
||||
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=end]')!;
|
||||
const childNodes = slot.assignedNodes({ flatten: true });
|
||||
|
||||
expect(childNodes.length).to.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when rendering a wa-dropdown in the default slot', () => {
|
||||
it('should not render a link or button tag, but a div wrapper', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" size="small" circle>
|
||||
<wa-icon label="More options" name="ellipsis"></wa-icon>
|
||||
</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item type="checkbox" checked>Web Design</wa-menu-item>
|
||||
<wa-menu-item type="checkbox">Web Development</wa-menu-item>
|
||||
<wa-menu-item type="checkbox">Marketing</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
</wa-breadcrumb-item>
|
||||
`);
|
||||
|
||||
await expect(el).to.be.accessible();
|
||||
expect(el.shadowRoot!.querySelector('a')).to.be.null;
|
||||
expect(el.shadowRoot!.querySelector('button')).to.be.null;
|
||||
expect(el.shadowRoot!.querySelector('.label-dropdown')).not.to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,20 +6,20 @@ 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
|
||||
*
|
||||
* @slot - The breadcrumb item's label.
|
||||
* @slot prefix - An optional prefix, usually an icon.
|
||||
* @slot suffix - An optional suffix, usually an icon.
|
||||
* @slot start - An element, such as `<wa-icon>`, placed before the label.
|
||||
* @slot end - An element, such as `<wa-icon>`, placed after the label.
|
||||
* @slot separator - The separator to use for the breadcrumb item. This will only change the separator for this item. If
|
||||
* you want to change it for all items in the group, set the separator on `<wa-breadcrumb>` instead.
|
||||
*
|
||||
* @csspart label - The breadcrumb item's label.
|
||||
* @csspart prefix - The container that wraps the prefix.
|
||||
* @csspart suffix - The container that wraps the suffix.
|
||||
* @csspart start - The container that wraps the `start` slot.
|
||||
* @csspart end - The container that wraps the `end` slot.
|
||||
* @csspart separator - The container that wraps the separator.
|
||||
*/
|
||||
@customElement('wa-breadcrumb-item')
|
||||
@@ -71,8 +71,8 @@ export default class WaBreadcrumbItem extends WebAwesomeElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<span part="prefix" class="prefix">
|
||||
<slot name="prefix"></slot>
|
||||
<span part="start" class="start">
|
||||
<slot name="start"></slot>
|
||||
</span>
|
||||
|
||||
${this.renderType === 'link'
|
||||
@@ -103,8 +103,8 @@ export default class WaBreadcrumbItem extends WebAwesomeElement {
|
||||
`
|
||||
: ''}
|
||||
|
||||
<span part="suffix" class="suffix">
|
||||
<slot name="suffix"></slot>
|
||||
<span part="end" class="end">
|
||||
<slot name="end"></slot>
|
||||
</span>
|
||||
|
||||
<span part="separator" class="separator" aria-hidden="true">
|
||||
|
||||
@@ -95,12 +95,12 @@ describe('<wa-breadcrumb>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a standard list of el-breadcrumb-item children and an element in the slot "prefix" to support prefix icons', () => {
|
||||
describe('when provided a standard list of el-breadcrumb-item children and an element in the slot "start" to support start icons', () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumb>(html`
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>
|
||||
<span class="prefix-example" slot="prefix">/</span>
|
||||
<span class="start-example" slot="start">/</span>
|
||||
Home
|
||||
</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>First</wa-breadcrumb-item>
|
||||
@@ -112,7 +112,7 @@ describe('<wa-breadcrumb>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a standard list of el-breadcrumb-item children and an element in the slot "suffix" to support suffix icons', () => {
|
||||
describe('when provided a standard list of el-breadcrumb-item children and an element in the slot "end" to support end icons', () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumb>(html`
|
||||
<wa-breadcrumb>
|
||||
@@ -120,7 +120,7 @@ describe('<wa-breadcrumb>', () => {
|
||||
<wa-breadcrumb-item>Second</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Third</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>
|
||||
<span class="prefix-example" slot="suffix">/</span>
|
||||
<span class="end-example" slot="end">/</span>
|
||||
Security
|
||||
</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
|
||||
@@ -107,8 +107,8 @@
|
||||
* Label
|
||||
*/
|
||||
|
||||
.prefix,
|
||||
.suffix {
|
||||
.start,
|
||||
.end {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -137,7 +137,7 @@ wa-icon[part~='caret'] {
|
||||
height: 0.875em;
|
||||
}
|
||||
|
||||
.button:has(&) .suffix {
|
||||
.button:has(&) .end {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -150,9 +150,9 @@ wa-icon[part~='caret'] {
|
||||
position: relative;
|
||||
cursor: wait;
|
||||
|
||||
.prefix,
|
||||
.start,
|
||||
.label,
|
||||
.suffix,
|
||||
.end,
|
||||
.caret {
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -191,11 +191,11 @@ button ::slotted(wa-badge) {
|
||||
* Button spacing
|
||||
*/
|
||||
|
||||
slot[name='prefix']::slotted(*) {
|
||||
slot[name='start']::slotted(*) {
|
||||
margin-inline-end: var(--wa-form-control-padding-inline);
|
||||
}
|
||||
|
||||
slot[name='suffix']::slotted(*),
|
||||
slot[name='end']::slotted(*),
|
||||
.button:not(.visually-hidden-label) [part~='caret'] {
|
||||
margin-inline-start: var(--wa-form-control-padding-inline);
|
||||
}
|
||||
|
||||
@@ -29,13 +29,13 @@ import styles from './button.css';
|
||||
* @event wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
|
||||
*
|
||||
* @slot - The button's label.
|
||||
* @slot prefix - A presentational prefix icon or similar element.
|
||||
* @slot suffix - A presentational suffix icon or similar element.
|
||||
* @slot start - An element, such as `<wa-icon>`, placed before the label.
|
||||
* @slot end - An element, such as `<wa-icon>`, placed after the label.
|
||||
*
|
||||
* @csspart base - The component's base wrapper.
|
||||
* @csspart prefix - The container that wraps the prefix.
|
||||
* @csspart start - The container that wraps the `start` slot.
|
||||
* @csspart label - The button's label.
|
||||
* @csspart suffix - The container that wraps the suffix.
|
||||
* @csspart end - The container that wraps the `end` slot.
|
||||
* @csspart caret - The button's caret icon, a `<wa-icon>` element.
|
||||
* @csspart spinner - The spinner that shows when the button is in the loading state.
|
||||
*
|
||||
@@ -60,7 +60,7 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
assumeInteractionOn = ['click'];
|
||||
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');
|
||||
private readonly hasSlotController = new HasSlotController(this, '[default]', 'start', 'end');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
@query('.button') button: HTMLButtonElement | HTMLLinkElement;
|
||||
@@ -263,8 +263,8 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
loading: this.loading,
|
||||
rtl: this.localize.dir() === 'rtl',
|
||||
'has-label': this.hasSlotController.test('[default]'),
|
||||
'has-prefix': this.hasSlotController.test('prefix'),
|
||||
'has-suffix': this.hasSlotController.test('suffix'),
|
||||
'has-start': this.hasSlotController.test('start'),
|
||||
'has-end': this.hasSlotController.test('end'),
|
||||
'is-icon-button': this.isIconButton,
|
||||
})}
|
||||
?disabled=${ifDefined(isLink ? undefined : this.disabled)}
|
||||
@@ -282,9 +282,9 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
@invalid=${this.isButton() ? this.handleInvalid : null}
|
||||
@click=${this.handleClick}
|
||||
>
|
||||
<slot name="prefix" part="prefix" class="prefix"></slot>
|
||||
<slot name="start" part="start" class="start"></slot>
|
||||
<slot part="label" class="label" @slotchange=${this.handleLabelSlotChange}></slot>
|
||||
<slot name="suffix" part="suffix" class="suffix"></slot>
|
||||
<slot name="end" part="end" class="end"></slot>
|
||||
${
|
||||
this.caret
|
||||
? html`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -132,7 +132,9 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
this.hasInteracted = true;
|
||||
this.checked = !this.checked;
|
||||
this.indeterminate = false;
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
|
||||
@watch('defaultChecked')
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
.color-picker {
|
||||
background-color: var(--background-color);
|
||||
border-radius: var(--border-radius);
|
||||
border-style: var(--border-style);
|
||||
border-width: var(--border-width);
|
||||
border-color: var(--border-color);
|
||||
box-shadow: var(--wa-shadow-l);
|
||||
color: var(--color);
|
||||
font: inherit;
|
||||
user-select: none;
|
||||
|
||||
@@ -300,6 +300,7 @@ describe('<wa-color-picker>', () => {
|
||||
await sendKeys({ type: 'fc0' }); // type in a color
|
||||
input.blur(); // commit changes by blurring the field
|
||||
await el.updateComplete;
|
||||
await aTimeout(1);
|
||||
|
||||
expect(changeHandler).to.have.been.calledOnce;
|
||||
expect(inputHandler).to.have.been.calledOnce;
|
||||
@@ -329,13 +330,6 @@ describe('<wa-color-picker>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should render in a dropdown', async () => {
|
||||
const el = await fixture<WaColorPicker>(html` <wa-color-picker></wa-color-picker> `);
|
||||
const dropdown = el.shadowRoot!.querySelector('wa-dropdown');
|
||||
|
||||
expect(dropdown).to.exist;
|
||||
});
|
||||
|
||||
it('should show opacity slider when opacity is enabled', async () => {
|
||||
const el = await fixture<WaColorPicker>(html` <wa-color-picker opacity></wa-color-picker> `);
|
||||
const opacitySlider = el.shadowRoot!.querySelector('[part*="opacity-slider"]')!;
|
||||
@@ -368,7 +362,7 @@ describe('<wa-color-picker>', () => {
|
||||
<button type="button">Click me</button>
|
||||
</div>
|
||||
`);
|
||||
const colorPicker = el.querySelector('wa-color-picker')!;
|
||||
const colorPicker = el.querySelector<WaColorPicker>('wa-color-picker')!;
|
||||
const trigger = colorPicker.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
|
||||
const button = el.querySelector('button')!;
|
||||
const focusHandler = sinon.spy();
|
||||
@@ -456,7 +450,7 @@ describe('<wa-color-picker>', () => {
|
||||
</form>
|
||||
`);
|
||||
const button = form.querySelector('wa-button')!;
|
||||
const colorPicker = form.querySelector('wa-color-picker')!;
|
||||
const colorPicker = form.querySelector<WaColorPicker>('wa-color-picker')!;
|
||||
colorPicker.value = '#000000';
|
||||
|
||||
await colorPicker.updateComplete;
|
||||
|
||||
@@ -6,7 +6,9 @@ import { classMap } from 'lit/directives/class-map.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { WaInvalidEvent } from '../../events/invalid.js';
|
||||
import { animateWithClass } from '../../internal/animate.js';
|
||||
import { drag } from '../../internal/drag.js';
|
||||
import { waitForEvent } from '../../internal/event.js';
|
||||
import { clamp } from '../../internal/math.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { RequiredValidator } from '../../internal/validators/required-validator.js';
|
||||
@@ -18,11 +20,11 @@ import visuallyHidden from '../../styles/utilities/visually-hidden.css';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import '../button-group/button-group.js';
|
||||
import '../button/button.js';
|
||||
import '../dropdown/dropdown.js';
|
||||
import type WaDropdown from '../dropdown/dropdown.js';
|
||||
import '../icon/icon.js';
|
||||
import '../input/input.js';
|
||||
import type WaInput from '../input/input.js';
|
||||
import '../popup/popup.js';
|
||||
import type WaPopup from '../popup/popup.js';
|
||||
import styles from './color-picker.css';
|
||||
|
||||
interface EyeDropperConstructor {
|
||||
@@ -43,8 +45,8 @@ declare const EyeDropper: EyeDropperConstructor;
|
||||
*
|
||||
* @dependency wa-button
|
||||
* @dependency wa-button-group
|
||||
* @dependency wa-dropdown
|
||||
* @dependency wa-input
|
||||
* @dependency wa-popup
|
||||
* @dependency wa-visually-hidden
|
||||
*
|
||||
* @slot label - The color picker's form label. Alternatively, you can use the `label` attribute.
|
||||
@@ -72,15 +74,15 @@ declare const EyeDropper: EyeDropperConstructor;
|
||||
* @csspart input - The text input.
|
||||
* @csspart eye-dropper-button - The eye dropper button.
|
||||
* @csspart eye-dropper-button__base - The eye dropper button's exported `button` part.
|
||||
* @csspart eye-dropper-button__prefix - The eye dropper button's exported `prefix` part.
|
||||
* @csspart eye-dropper-button__start - The eye dropper button's exported `start` part.
|
||||
* @csspart eye-dropper-button__label - The eye dropper button's exported `label` part.
|
||||
* @csspart eye-dropper-button__suffix - The eye dropper button's exported `suffix` part.
|
||||
* @csspart eye-dropper-button__end - The eye dropper button's exported `end` part.
|
||||
* @csspart eye-dropper-button__caret - The eye dropper button's exported `caret` part.
|
||||
* @csspart format-button - The format button.
|
||||
* @csspart format-button__base - The format button's exported `button` part.
|
||||
* @csspart format-button__prefix - The format button's exported `prefix` part.
|
||||
* @csspart format-button__start - The format button's exported `start` part.
|
||||
* @csspart format-button__label - The format button's exported `label` part.
|
||||
* @csspart format-button__suffix - The format button's exported `suffix` part.
|
||||
* @csspart format-button__end - The format button's exported `end` part.
|
||||
* @csspart format-button__caret - The format button's exported `caret` part.
|
||||
*
|
||||
* @cssproperty --background-color - The color picker's background color.
|
||||
@@ -125,7 +127,7 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
// or is the new behavior okay?
|
||||
get validationTarget() {
|
||||
// This puts the popup on the element only if the color picker is expanded.
|
||||
if (this.dropdown?.open) {
|
||||
if (this.popup?.active) {
|
||||
return this.input;
|
||||
}
|
||||
|
||||
@@ -134,7 +136,7 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
return this.trigger;
|
||||
}
|
||||
|
||||
@query('.color-dropdown') dropdown: WaDropdown;
|
||||
@query('.color-popup') popup: WaPopup;
|
||||
@query('[part~="preview"]') previewButton: HTMLButtonElement;
|
||||
@query('[part~="trigger"]') trigger: HTMLButtonElement;
|
||||
|
||||
@@ -210,6 +212,12 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
/** Disables the color picker. */
|
||||
@property({ type: Boolean }) disabled = false;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the popup is open. You can toggle this attribute to show and hide the popup, or you
|
||||
* can use the `show()` and `hide()` methods and this attribute will reflect the popup's open state.
|
||||
*/
|
||||
@property({ type: Boolean, reflect: true }) open = false;
|
||||
|
||||
/** Shows the opacity slider. Enabling this will cause the formatted value to be HEXA, RGBA, or HSLA. */
|
||||
@property({ type: Boolean }) opacity = false;
|
||||
|
||||
@@ -267,8 +275,11 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
const nextIndex = (formats.indexOf(this.format) + 1) % formats.length;
|
||||
this.format = formats[nextIndex] as 'hex' | 'rgb' | 'hsl' | 'hsv';
|
||||
this.setColor(this.value || '');
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
|
||||
private handleAlphaDrag(event: PointerEvent) {
|
||||
@@ -288,13 +299,18 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
if (this.value !== currentValue) {
|
||||
currentValue = this.value;
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
},
|
||||
onStop: () => {
|
||||
if (this.value !== initialValue) {
|
||||
initialValue = this.value;
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
},
|
||||
initialEvent: event,
|
||||
@@ -318,13 +334,17 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
if (this.value !== currentValue) {
|
||||
currentValue = this.value;
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
});
|
||||
}
|
||||
},
|
||||
onStop: () => {
|
||||
if (this.value !== initialValue) {
|
||||
initialValue = this.value;
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
},
|
||||
initialEvent: event,
|
||||
@@ -351,14 +371,18 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
if (this.value !== currentValue) {
|
||||
currentValue = this.value;
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
},
|
||||
onStop: () => {
|
||||
this.isDraggingGridHandle = false;
|
||||
if (this.value !== initialValue) {
|
||||
initialValue = this.value;
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
},
|
||||
initialEvent: event,
|
||||
@@ -394,8 +418,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,8 +454,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,8 +490,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,8 +512,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,8 +535,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
this.input.value = this.value;
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => this.input.select());
|
||||
@@ -688,8 +722,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
this.setColor(colorSelectionResult.sRGBHex);
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -704,8 +740,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
this.setColor(color);
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -790,8 +828,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
elementToBlur.blur();
|
||||
}
|
||||
|
||||
if (this.dropdown?.open) {
|
||||
this.dropdown.hide();
|
||||
if (this.popup?.active) {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -839,10 +877,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
/** Checks for validity and shows the browser's validation message if the control is invalid. */
|
||||
reportValidity() {
|
||||
// This won't get called when a form is submitted. This is only for manual calls.
|
||||
if (!this.validity.valid && !this.dropdown.open) {
|
||||
// Show the dropdown so the browser can focus on it
|
||||
if (!this.validity.valid && !this.open) {
|
||||
// Show the popup so the browser can focus on it
|
||||
this.addEventListener('wa-after-show', this.reportValidityAfterShow, { once: true });
|
||||
this.dropdown.show();
|
||||
this.show();
|
||||
|
||||
if (!this.disabled) {
|
||||
// By standards we have to emit a `wa-invalid` event here synchronously.
|
||||
@@ -867,6 +905,158 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
this.hasEyeDropper = 'EyeDropper' in window;
|
||||
}
|
||||
|
||||
private handleKeyDown = (event: KeyboardEvent) => {
|
||||
// Close when escape is pressed inside an open popup. We need to listen on the panel itself and stop propagation
|
||||
// in case any ancestors are also listening for this key.
|
||||
if (this.open && event.key === 'Escape') {
|
||||
event.stopPropagation();
|
||||
this.hide();
|
||||
this.focus();
|
||||
}
|
||||
};
|
||||
|
||||
private handleDocumentKeyDown = (event: KeyboardEvent) => {
|
||||
// Close when escape or tab is pressed
|
||||
if (event.key === 'Escape' && this.open) {
|
||||
event.stopPropagation();
|
||||
this.focus();
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle tabbing
|
||||
if (event.key === 'Tab') {
|
||||
// Tabbing outside of the containing element closes the panel
|
||||
//
|
||||
// If the popup is used within a shadow DOM, we need to obtain the activeElement within that shadowRoot,
|
||||
// otherwise `document.activeElement` will only return the name of the parent shadow DOM element.
|
||||
setTimeout(() => {
|
||||
const activeElement =
|
||||
this.getRootNode() instanceof ShadowRoot
|
||||
? document.activeElement?.shadowRoot?.activeElement
|
||||
: document.activeElement;
|
||||
|
||||
if (!this || activeElement?.closest(this.tagName.toLowerCase()) !== this) {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private handleDocumentMouseDown = (event: MouseEvent) => {
|
||||
// Close when clicking outside of the popup panel and trigger
|
||||
const path = event.composedPath();
|
||||
|
||||
// Check if click is inside the popup panel or the trigger element specifically
|
||||
const isInsideRelevantArea = path.some(
|
||||
element => element instanceof Element && (element.closest('.color-picker') || element === this.trigger),
|
||||
);
|
||||
|
||||
if (this && !isInsideRelevantArea) {
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
handleTriggerClick() {
|
||||
if (this.open) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
async handleTriggerKeyDown(event: KeyboardEvent) {
|
||||
// When spacebar/enter is pressed, show the panel but don't focus on the menu. This let's the user press the same
|
||||
// key again to hide the menu in case they don't want to make a selection.
|
||||
if ([' ', 'Enter'].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
this.handleTriggerClick();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handleTriggerKeyUp(event: KeyboardEvent) {
|
||||
// Prevent space from triggering a click event in Firefox
|
||||
if (event.key === ' ') {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
updateAccessibleTrigger() {
|
||||
const accessibleTrigger = this.trigger;
|
||||
|
||||
if (accessibleTrigger) {
|
||||
accessibleTrigger.setAttribute('aria-haspopup', 'true');
|
||||
accessibleTrigger.setAttribute('aria-expanded', this.open ? 'true' : 'false');
|
||||
}
|
||||
}
|
||||
|
||||
/** Shows the color picker panel. */
|
||||
async show() {
|
||||
if (this.open) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.open = true;
|
||||
return waitForEvent(this, 'wa-after-show');
|
||||
}
|
||||
|
||||
/** Hides the color picker panel */
|
||||
async hide() {
|
||||
if (!this.open) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.open = false;
|
||||
return waitForEvent(this, 'wa-after-hide');
|
||||
}
|
||||
|
||||
addOpenListeners() {
|
||||
this.base.addEventListener('keydown', this.handleKeyDown);
|
||||
document.addEventListener('keydown', this.handleDocumentKeyDown);
|
||||
document.addEventListener('mousedown', this.handleDocumentMouseDown);
|
||||
}
|
||||
|
||||
removeOpenListeners() {
|
||||
if (this.base) {
|
||||
this.base.removeEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
document.removeEventListener('keydown', this.handleDocumentKeyDown);
|
||||
document.removeEventListener('mousedown', this.handleDocumentMouseDown);
|
||||
}
|
||||
|
||||
@watch('open', { waitUntilFirstUpdate: true })
|
||||
async handleOpenChange() {
|
||||
if (this.disabled) {
|
||||
this.open = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateAccessibleTrigger();
|
||||
|
||||
if (this.open) {
|
||||
// Show
|
||||
this.dispatchEvent(new CustomEvent('wa-show'));
|
||||
|
||||
this.addOpenListeners();
|
||||
await this.updateComplete;
|
||||
this.base.hidden = false;
|
||||
this.popup.active = true;
|
||||
await animateWithClass(this.popup.popup, 'show-with-scale');
|
||||
this.dispatchEvent(new CustomEvent('wa-after-show'));
|
||||
} else {
|
||||
// Hide
|
||||
this.dispatchEvent(new CustomEvent('wa-hide'));
|
||||
|
||||
this.removeOpenListeners();
|
||||
await animateWithClass(this.popup.popup, 'hide-with-scale');
|
||||
this.base.hidden = true;
|
||||
this.popup.active = false;
|
||||
this.dispatchEvent(new CustomEvent('wa-after-hide'));
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasLabelSlot = !this.hasUpdated ? this.withLabel : this.withLabel || this.hasSlotController.test('label');
|
||||
const hasHintSlot = !this.hasUpdated ? this.withHint : this.withHint || this.hasSlotController.test('hint');
|
||||
@@ -1021,9 +1211,9 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
aria-label=${this.localize.term('toggleColorFormat')}
|
||||
exportparts="
|
||||
base:format-button__base,
|
||||
prefix:format-button__prefix,
|
||||
start:format-button__start,
|
||||
label:format-button__label,
|
||||
suffix:format-button__suffix,
|
||||
end:format-button__end,
|
||||
caret:format-button__caret
|
||||
"
|
||||
@click=${this.handleFormatToggle}
|
||||
@@ -1042,9 +1232,9 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
appearance="outlined"
|
||||
exportparts="
|
||||
base:eye-dropper-button__base,
|
||||
prefix:eye-dropper-button__prefix,
|
||||
start:eye-dropper-button__start,
|
||||
label:eye-dropper-button__label,
|
||||
suffix:eye-dropper-button__suffix,
|
||||
end:eye-dropper-button__end,
|
||||
caret:eye-dropper-button__caret
|
||||
"
|
||||
@click=${this.handleEyeDropper}
|
||||
@@ -1095,82 +1285,64 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Render as a dropdown
|
||||
// Render with popup
|
||||
return html`
|
||||
<wa-dropdown
|
||||
class="color-dropdown"
|
||||
<div
|
||||
class=${classMap({
|
||||
container: true,
|
||||
'form-control': true,
|
||||
'form-control-has-label': hasLabel,
|
||||
})}
|
||||
part="trigger-container form-control"
|
||||
>
|
||||
<div part="form-control-label" class="label" id="form-control-label">
|
||||
<slot name="label">${this.label}</slot>
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="trigger"
|
||||
part="trigger form-control-input"
|
||||
class=${classMap({
|
||||
trigger: true,
|
||||
'trigger-empty': this.isEmpty,
|
||||
'transparent-bg': true,
|
||||
'form-control-input': true,
|
||||
})}
|
||||
style=${styleMap({
|
||||
color: this.getHexString(this.hue, this.saturation, this.brightness, this.alpha),
|
||||
})}
|
||||
type="button"
|
||||
aria-labelledby="form-control-label"
|
||||
aria-describedby="hint"
|
||||
.disabled=${this.disabled}
|
||||
@click=${this.handleTriggerClick}
|
||||
@keydown=${this.handleTriggerKeyDown}
|
||||
@keyup=${this.handleTriggerKeyUp}
|
||||
></button>
|
||||
|
||||
<slot
|
||||
name="hint"
|
||||
part="hint"
|
||||
class=${classMap({
|
||||
'has-slotted': hasHint,
|
||||
})}
|
||||
>${this.hint}</slot
|
||||
>
|
||||
</div>
|
||||
|
||||
<wa-popup
|
||||
class="color-popup"
|
||||
anchor="trigger"
|
||||
placement="bottom-start"
|
||||
distance="0"
|
||||
skidding="0"
|
||||
sync="width"
|
||||
aria-disabled=${this.disabled ? 'true' : 'false'}
|
||||
.containingElement=${this}
|
||||
?disabled=${this.disabled}
|
||||
@wa-after-show=${this.handleAfterShow}
|
||||
@wa-after-hide=${this.handleAfterHide}
|
||||
>
|
||||
<div
|
||||
class=${classMap({
|
||||
container: true,
|
||||
'form-control': true,
|
||||
'form-control-has-label': hasLabel,
|
||||
})}
|
||||
part="trigger-container form-control"
|
||||
slot="trigger"
|
||||
@click=${(e: Event) => {
|
||||
const composedPath = e.composedPath();
|
||||
const triggerButton = this.triggerButton;
|
||||
const triggerLabel = this.triggerLabel;
|
||||
const buttonOrLabelClicked = composedPath.find(el => el === triggerButton || el === triggerLabel);
|
||||
|
||||
if (buttonOrLabelClicked) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop clicks from bubbling on anything except the button and the label. This is a hacky work around i may come to regret, but this "fixes" the issue of `<wa-dropdown>` expecting all children in the "trigger slot" to open the trigger. [Konnor]
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.dropdown.open) {
|
||||
this.dropdown.hide();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div part="form-control-label" class="label" id="form-control-label">
|
||||
<slot name="label">${this.label}</slot>
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="trigger"
|
||||
part="trigger form-control-input"
|
||||
class=${classMap({
|
||||
trigger: true,
|
||||
'trigger-empty': this.isEmpty,
|
||||
'transparent-bg': true,
|
||||
'form-control-input': true,
|
||||
})}
|
||||
style=${styleMap({
|
||||
color: this.getHexString(this.hue, this.saturation, this.brightness, this.alpha),
|
||||
})}
|
||||
type="button"
|
||||
aria-labelledby="form-control-label"
|
||||
aria-describedby="hint"
|
||||
.disabled=${this.disabled}
|
||||
></button>
|
||||
|
||||
<slot
|
||||
name="hint"
|
||||
part="hint"
|
||||
class=${classMap({
|
||||
'has-slotted': hasHint,
|
||||
})}
|
||||
>${this.hint}</slot
|
||||
>
|
||||
</div>
|
||||
${colorPicker}
|
||||
</wa-dropdown>
|
||||
</wa-popup>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'wa-color-picker': WaColorPicker;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
--border-radius: var(--wa-panel-border-radius);
|
||||
--box-shadow: var(--wa-shadow-l);
|
||||
--width: 31rem;
|
||||
--spacing: var(--wa-space-xl);
|
||||
--spacing: var(--wa-space-l);
|
||||
--show-duration: 200ms;
|
||||
--hide-duration: 200ms;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
--background-color: var(--wa-color-surface-raised);
|
||||
--box-shadow: var(--wa-shadow-l);
|
||||
--size: 25rem;
|
||||
--spacing: var(--wa-space-xl);
|
||||
--spacing: var(--wa-space-l);
|
||||
--show-duration: 200ms;
|
||||
--hide-duration: 200ms;
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
:host {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
padding: 0.5em 1em;
|
||||
border-radius: var(--wa-border-radius-s);
|
||||
isolation: isolate;
|
||||
color: var(--wa-color-text-normal);
|
||||
line-height: var(--wa-line-height-condensed);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
100ms background-color ease,
|
||||
100ms color ease;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
:host(:hover:not(:state(disabled))) {
|
||||
background-color: var(--wa-color-neutral-fill-normal);
|
||||
}
|
||||
}
|
||||
|
||||
:host(:focus-visible) {
|
||||
z-index: 1;
|
||||
outline: var(--wa-focus-ring);
|
||||
background-color: var(--wa-color-neutral-fill-normal);
|
||||
}
|
||||
|
||||
:host(:state(disabled)) {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Danger variant */
|
||||
:host([variant='danger']),
|
||||
:host([variant='danger']) #details {
|
||||
color: var(--wa-color-danger-on-quiet);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
:host([variant='danger']:hover) {
|
||||
background-color: var(--wa-color-danger-fill-normal);
|
||||
color: var(--wa-color-danger-on-normal);
|
||||
}
|
||||
}
|
||||
|
||||
:host([variant='danger']:focus-visible) {
|
||||
background-color: var(--wa-color-danger-fill-normal);
|
||||
color: var(--wa-color-danger-on-normal);
|
||||
}
|
||||
|
||||
:host([checkbox-adjacent]) {
|
||||
padding-inline-start: 2em;
|
||||
}
|
||||
|
||||
/* Only add padding when item actually has a submenu */
|
||||
:host([submenu-adjacent]:not(:state(has-submenu))) #details {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
:host(:state(has-submenu)[submenu-adjacent]) #details {
|
||||
padding-inline-end: 1.75em;
|
||||
}
|
||||
|
||||
#check {
|
||||
visibility: hidden;
|
||||
margin-inline-start: -1.5em;
|
||||
margin-inline-end: 0.5em;
|
||||
font-size: var(--wa-font-size-smaller);
|
||||
}
|
||||
|
||||
:host(:state(checked)) #check {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#icon ::slotted(*) {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
margin-inline-end: 0.75em !important;
|
||||
font-size: var(--wa-font-size-smaller);
|
||||
}
|
||||
|
||||
#label {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#details {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
color: var(--wa-color-text-quiet);
|
||||
font-size: var(--wa-font-size-smaller) !important;
|
||||
}
|
||||
|
||||
#details ::slotted(*) {
|
||||
margin-inline-start: 2em !important;
|
||||
}
|
||||
|
||||
/* Submenu indicator icon */
|
||||
#submenu-indicator {
|
||||
position: absolute;
|
||||
inset-inline-end: 1em;
|
||||
color: var(--wa-color-neutral-on-quiet);
|
||||
font-size: var(--wa-font-size-smaller);
|
||||
}
|
||||
|
||||
/* Flip chevron icon when RTL */
|
||||
:host(:dir(rtl)) #submenu-indicator {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
/* Submenu styles */
|
||||
#submenu {
|
||||
display: flex;
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
flex-direction: column;
|
||||
width: max-content;
|
||||
margin: 0;
|
||||
padding: 0.25em;
|
||||
border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border);
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
background-color: var(--wa-color-surface-raised);
|
||||
box-shadow: var(--wa-shadow-m);
|
||||
color: var(--wa-color-text-normal);
|
||||
text-align: start;
|
||||
user-select: none;
|
||||
|
||||
/* Override default popover styles */
|
||||
&[popover] {
|
||||
margin: 0;
|
||||
inset: auto;
|
||||
padding: 0.25em;
|
||||
overflow: visible;
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
}
|
||||
|
||||
&.show {
|
||||
animation: submenu-show var(--show-duration, 50ms) ease;
|
||||
}
|
||||
|
||||
&.hide {
|
||||
animation: submenu-show var(--show-duration, 50ms) ease reverse;
|
||||
}
|
||||
|
||||
/* Submenu placement transform origins */
|
||||
&[data-placement^='top'] {
|
||||
transform-origin: bottom;
|
||||
}
|
||||
|
||||
&[data-placement^='bottom'] {
|
||||
transform-origin: top;
|
||||
}
|
||||
|
||||
&[data-placement^='left'] {
|
||||
transform-origin: right;
|
||||
}
|
||||
|
||||
&[data-placement^='right'] {
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
&[data-placement='left-start'] {
|
||||
transform-origin: right top;
|
||||
}
|
||||
|
||||
&[data-placement='left-end'] {
|
||||
transform-origin: right bottom;
|
||||
}
|
||||
|
||||
&[data-placement='right-start'] {
|
||||
transform-origin: left top;
|
||||
}
|
||||
|
||||
&[data-placement='right-end'] {
|
||||
transform-origin: left bottom;
|
||||
}
|
||||
|
||||
/* Safe triangle styling */
|
||||
&::before {
|
||||
display: none;
|
||||
z-index: 9;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: transparent;
|
||||
content: '';
|
||||
clip-path: polygon(
|
||||
var(--safe-triangle-cursor-x, 0) var(--safe-triangle-cursor-y, 0),
|
||||
var(--safe-triangle-submenu-start-x, 0) var(--safe-triangle-submenu-start-y, 0),
|
||||
var(--safe-triangle-submenu-end-x, 0) var(--safe-triangle-submenu-end-y, 0)
|
||||
);
|
||||
pointer-events: auto; /* Enable mouse events on the triangle */
|
||||
}
|
||||
|
||||
&[data-visible]::before {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
::slotted(wa-dropdown-item) {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
::slotted(wa-divider) {
|
||||
--spacing: 0.25em;
|
||||
}
|
||||
|
||||
@keyframes submenu-show {
|
||||
from {
|
||||
scale: 0.9;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
scale: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
|
||||
describe('<wa-dropdown-item>', () => {
|
||||
it('should render a component', async () => {
|
||||
const el = await fixture(html` <wa-dropdown-item></wa-dropdown-item> `);
|
||||
|
||||
expect(el).to.exist;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,302 @@
|
||||
import type { PropertyValues } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { animateWithClass } from '../../internal/animate.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import styles from './dropdown-item.css';
|
||||
|
||||
/**
|
||||
* @summary Represents an individual item within a dropdown menu, supporting standard items, checkboxes, and submenus.
|
||||
* @documentation https://backers.webawesome.com/docs/components/dropdown-item
|
||||
* @status experimental
|
||||
* @since 3.0
|
||||
*
|
||||
* @dependency wa-icon
|
||||
*
|
||||
* @event blur - Emitted when the dropdown item loses focus.
|
||||
* @event focus - Emitted when the dropdown item gains focus.
|
||||
*
|
||||
* @slot - The dropdown item's label.
|
||||
* @slot icon - An optional icon to display before the label.
|
||||
* @slot details - Additional content or details to display after the label.
|
||||
* @slot submenu - Submenu items, typically `<wa-dropdown-item>` elements, to create a nested menu.
|
||||
*
|
||||
* @csspart checkmark - The checkmark icon (a `<wa-icon>` element) when the item is a checkbox.
|
||||
* @csspart icon - The container for the icon slot.
|
||||
* @csspart label - The container for the label slot.
|
||||
* @csspart details - The container for the details slot.
|
||||
* @csspart submenu-icon - The submenu indicator icon (a `<wa-icon>` element).
|
||||
* @csspart submenu - The submenu container.
|
||||
*/
|
||||
@customElement('wa-dropdown-item')
|
||||
export default class WaDropdownItem extends WebAwesomeElement {
|
||||
static css = styles;
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, '[default]', 'start', 'end');
|
||||
|
||||
@query('#submenu') submenuElement: HTMLDivElement;
|
||||
|
||||
/** @internal The controller will set this property to true when the item is active. */
|
||||
@property({ type: Boolean }) active = false;
|
||||
|
||||
/** The type of menu item to render. */
|
||||
@property({ reflect: true }) variant: 'danger' | 'default' = 'default';
|
||||
|
||||
/**
|
||||
* @internal The dropdown item's size.
|
||||
*/
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
|
||||
/**
|
||||
* @internal The controller will set this property to true when at least one checkbox exists in the dropdown. This
|
||||
* allows non-checkbox items to draw additional space to align properly with checkbox items.
|
||||
*/
|
||||
@property({ attribute: 'checkbox-adjacent', type: Boolean, reflect: true }) checkboxAdjacent = false;
|
||||
|
||||
/**
|
||||
* @internal The controller will set this property to true when at least one item with a submenu exists in the
|
||||
* dropdown. This allows non-submenu items to draw additional space to align properly with items that have submenus.
|
||||
*/
|
||||
@property({ attribute: 'submenu-adjacent', type: Boolean, reflect: true }) submenuAdjacent = false;
|
||||
|
||||
/**
|
||||
* An optional value for the menu item. This is useful for determining which item was selected when listening to the
|
||||
* dropdown's `wa-select` event.
|
||||
*/
|
||||
@property() value: string;
|
||||
|
||||
/** Set to `checkbox` to make the item a checkbox. */
|
||||
@property({ reflect: true }) type: 'normal' | 'checkbox' = 'normal';
|
||||
|
||||
/** Set to true to check the dropdown item. Only valid when `type` is `checkbox`. */
|
||||
@property({ type: Boolean }) checked = false;
|
||||
|
||||
/** Disables the dropdown item. */
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
/** Whether the submenu is currently open. */
|
||||
@property({ type: Boolean, reflect: true }) submenuOpen = false;
|
||||
|
||||
/** @internal Store whether this item has a submenu */
|
||||
@state() hasSubmenu = false;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.addEventListener('mouseenter', this.handleMouseEnter.bind(this));
|
||||
this.shadowRoot!.addEventListener('slotchange', this.handleSlotChange);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.closeSubmenu();
|
||||
this.removeEventListener('mouseenter', this.handleMouseEnter);
|
||||
this.shadowRoot!.removeEventListener('slotchange', this.handleSlotChange);
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.setAttribute('tabindex', '-1');
|
||||
this.hasSubmenu = this.hasSlotController.test('submenu');
|
||||
this.updateHasSubmenuState();
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has('active')) {
|
||||
this.setAttribute('tabindex', this.active ? '0' : '-1');
|
||||
this.customStates.set('active', this.active);
|
||||
}
|
||||
|
||||
if (changedProperties.has('checked')) {
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
this.customStates.set('checked', this.checked);
|
||||
}
|
||||
|
||||
if (changedProperties.has('disabled')) {
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
this.customStates.set('disabled', this.disabled);
|
||||
}
|
||||
|
||||
if (changedProperties.has('type')) {
|
||||
if (this.type === 'checkbox') {
|
||||
this.setAttribute('role', 'menuitemcheckbox');
|
||||
} else {
|
||||
this.setAttribute('role', 'menuitem');
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProperties.has('submenuOpen')) {
|
||||
this.customStates.set('submenu-open', this.submenuOpen);
|
||||
if (this.submenuOpen) {
|
||||
this.openSubmenu();
|
||||
} else {
|
||||
this.closeSubmenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleSlotChange = () => {
|
||||
this.hasSubmenu = this.hasSlotController.test('submenu');
|
||||
this.updateHasSubmenuState();
|
||||
|
||||
if (this.hasSubmenu) {
|
||||
this.setAttribute('aria-haspopup', 'menu');
|
||||
this.setAttribute('aria-expanded', this.submenuOpen ? 'true' : 'false');
|
||||
} else {
|
||||
this.removeAttribute('aria-haspopup');
|
||||
this.removeAttribute('aria-expanded');
|
||||
}
|
||||
};
|
||||
|
||||
/** Update the has-submenu custom state */
|
||||
private updateHasSubmenuState() {
|
||||
this.customStates.set('has-submenu', this.hasSubmenu);
|
||||
}
|
||||
|
||||
/** Opens the submenu. */
|
||||
async openSubmenu() {
|
||||
if (!this.hasSubmenu || !this.submenuElement) return;
|
||||
|
||||
// Notify parent dropdown to handle positioning
|
||||
this.notifyParentOfOpening();
|
||||
|
||||
// Use Popover API to show the submenu
|
||||
this.submenuElement.showPopover();
|
||||
this.submenuElement.hidden = false;
|
||||
this.submenuElement.setAttribute('data-visible', '');
|
||||
this.submenuOpen = true;
|
||||
this.setAttribute('aria-expanded', 'true');
|
||||
|
||||
// Animate the submenu
|
||||
await animateWithClass(this.submenuElement, 'show');
|
||||
|
||||
// Set focus to the first submenu item
|
||||
setTimeout(() => {
|
||||
const items = this.getSubmenuItems();
|
||||
if (items.length > 0) {
|
||||
items.forEach((item, index) => (item.active = index === 0));
|
||||
items[0].focus();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/** Notifies the parent dropdown that this item is opening its submenu */
|
||||
private notifyParentOfOpening() {
|
||||
// First notify the parent that we're about to open
|
||||
const event = new CustomEvent('submenu-opening', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { item: this },
|
||||
});
|
||||
this.dispatchEvent(event);
|
||||
|
||||
// Find sibling items that have open submenus and close them
|
||||
const parent = this.parentElement;
|
||||
if (parent) {
|
||||
const siblings = [...parent.children].filter(
|
||||
el =>
|
||||
el !== this &&
|
||||
el.localName === 'wa-dropdown-item' &&
|
||||
el.getAttribute('slot') === this.getAttribute('slot') &&
|
||||
(el as WaDropdownItem).submenuOpen,
|
||||
) as WaDropdownItem[];
|
||||
|
||||
// Close each sibling submenu with animation
|
||||
siblings.forEach(sibling => {
|
||||
sibling.submenuOpen = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Closes the submenu. */
|
||||
async closeSubmenu() {
|
||||
if (!this.hasSubmenu || !this.submenuElement) return;
|
||||
|
||||
this.submenuOpen = false;
|
||||
this.setAttribute('aria-expanded', 'false');
|
||||
|
||||
if (!this.submenuElement.hidden) {
|
||||
await animateWithClass(this.submenuElement, 'hide');
|
||||
this.submenuElement.hidden = true;
|
||||
this.submenuElement.removeAttribute('data-visible');
|
||||
this.submenuElement.hidePopover();
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets all dropdown items in the submenu. */
|
||||
private getSubmenuItems(): WaDropdownItem[] {
|
||||
// Only get direct children with slot="submenu", not nested ones
|
||||
return [...this.children].filter(
|
||||
el =>
|
||||
el.localName === 'wa-dropdown-item' && el.getAttribute('slot') === 'submenu' && !el.hasAttribute('disabled'),
|
||||
) as WaDropdownItem[];
|
||||
}
|
||||
|
||||
/** Handles mouse enter to open the submenu */
|
||||
private handleMouseEnter() {
|
||||
if (this.hasSubmenu && !this.disabled) {
|
||||
this.notifyParentOfOpening();
|
||||
this.submenuOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this.type === 'checkbox'
|
||||
? html`
|
||||
<wa-icon
|
||||
id="check"
|
||||
part="checkmark"
|
||||
exportparts="svg:checkmark__svg"
|
||||
library="system"
|
||||
name="check"
|
||||
></wa-icon>
|
||||
`
|
||||
: ''}
|
||||
|
||||
<span id="icon" part="icon">
|
||||
<slot name="icon"></slot>
|
||||
</span>
|
||||
|
||||
<span id="label" part="label">
|
||||
<slot></slot>
|
||||
</span>
|
||||
|
||||
<span id="details" part="details">
|
||||
<slot name="details"></slot>
|
||||
</span>
|
||||
|
||||
${this.hasSubmenu
|
||||
? html`
|
||||
<wa-icon
|
||||
id="submenu-indicator"
|
||||
part="submenu-icon"
|
||||
exportparts="svg:submenu-icon__svg"
|
||||
library="system"
|
||||
name="chevron-right"
|
||||
></wa-icon>
|
||||
`
|
||||
: ''}
|
||||
${this.hasSubmenu
|
||||
? html`
|
||||
<div
|
||||
id="submenu"
|
||||
part="submenu"
|
||||
popover="manual"
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
aria-orientation="vertical"
|
||||
hidden
|
||||
>
|
||||
<slot name="submenu"></slot>
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'wa-dropdown-item': WaDropdownItem;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +1,93 @@
|
||||
:host {
|
||||
--box-shadow: var(--wa-shadow-m);
|
||||
|
||||
display: inline-block;
|
||||
--show-duration: 50ms;
|
||||
--hide-duration: 50ms;
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.dropdown::part(popup) {
|
||||
z-index: 900;
|
||||
#menu {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
flex-direction: column;
|
||||
width: max-content;
|
||||
margin: 0;
|
||||
padding: 0.25em;
|
||||
border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border);
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
background-color: var(--wa-color-surface-raised);
|
||||
box-shadow: var(--wa-shadow-m);
|
||||
color: var(--wa-color-text-normal);
|
||||
text-align: start;
|
||||
user-select: none;
|
||||
|
||||
&.show {
|
||||
animation: show var(--show-duration) ease;
|
||||
}
|
||||
|
||||
&.hide {
|
||||
animation: show var(--hide-duration) ease reverse;
|
||||
}
|
||||
|
||||
::slotted(h1),
|
||||
::slotted(h2),
|
||||
::slotted(h3),
|
||||
::slotted(h4),
|
||||
::slotted(h5),
|
||||
::slotted(h6) {
|
||||
display: block !important;
|
||||
margin: 0.25em 0 !important;
|
||||
padding: 0.25em 0.75em !important;
|
||||
color: var(--wa-color-text-quiet) !important;
|
||||
font-family: var(--wa-font-family-body) !important;
|
||||
font-weight: var(--wa-font-weight-semibold) !important;
|
||||
font-size: var(--wa-font-size-smaller) !important;
|
||||
}
|
||||
|
||||
::slotted(wa-divider) {
|
||||
--spacing: 0.25em; /* Component-specific, left as-is */
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown[data-current-placement^='top']::part(popup) {
|
||||
wa-popup[data-current-placement^='top'] #menu {
|
||||
transform-origin: bottom;
|
||||
}
|
||||
|
||||
.dropdown[data-current-placement^='bottom']::part(popup) {
|
||||
wa-popup[data-current-placement^='bottom'] #menu {
|
||||
transform-origin: top;
|
||||
}
|
||||
|
||||
.dropdown[data-current-placement^='left']::part(popup) {
|
||||
wa-popup[data-current-placement^='left'] #menu {
|
||||
transform-origin: right;
|
||||
}
|
||||
|
||||
.dropdown[data-current-placement^='right']::part(popup) {
|
||||
wa-popup[data-current-placement^='right'] #menu {
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
#trigger {
|
||||
display: block; /* for boundingClientRect */
|
||||
wa-popup[data-current-placement='left-start'] #menu {
|
||||
transform-origin: right top;
|
||||
}
|
||||
|
||||
.panel {
|
||||
font: inherit;
|
||||
box-shadow: var(--box-shadow);
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
pointer-events: none;
|
||||
wa-popup[data-current-placement='left-end'] #menu {
|
||||
transform-origin: right bottom;
|
||||
}
|
||||
|
||||
.dropdown-open .panel {
|
||||
display: block;
|
||||
pointer-events: all;
|
||||
wa-popup[data-current-placement='right-start'] #menu {
|
||||
transform-origin: left top;
|
||||
}
|
||||
|
||||
/* Sizes */
|
||||
:host([size='small']) ::slotted(wa-menu) {
|
||||
font-size: var(--wa-font-size-s);
|
||||
wa-popup[data-current-placement='right-end'] #menu {
|
||||
transform-origin: left bottom;
|
||||
}
|
||||
|
||||
:host([size='medium']) ::slotted(wa-menu) {
|
||||
font-size: var(--wa-font-size-m);
|
||||
}
|
||||
|
||||
:host([size='large']) ::slotted(wa-menu) {
|
||||
font-size: var(--wa-font-size-l);
|
||||
}
|
||||
|
||||
/* When users slot a menu, make sure it conforms to the popup's auto-size */
|
||||
::slotted(wa-menu) {
|
||||
max-width: var(--auto-size-available-width) !important;
|
||||
max-height: var(--auto-size-available-height) !important;
|
||||
@keyframes show {
|
||||
from {
|
||||
scale: 0.9;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
scale: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,405 +1,9 @@
|
||||
import { expect, waitUntil } from '@open-wc/testing';
|
||||
import { sendKeys, sendMouse } from '@web/test-runner-commands';
|
||||
import { html } from 'lit';
|
||||
import sinon from 'sinon';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import type WaDropdown from './dropdown.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
|
||||
describe('<wa-dropdown>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should be visible with the open attribute', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown open>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~="panel"]')!;
|
||||
it('should render a component', async () => {
|
||||
const el = await fixture(html` <wa-dropdown></wa-dropdown> `);
|
||||
|
||||
expect(panel.hidden).to.be.false;
|
||||
});
|
||||
|
||||
it('should not be visible without the open attribute', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~="panel"]')!;
|
||||
|
||||
expect(panel.hidden).to.be.true;
|
||||
});
|
||||
|
||||
it('should emit wa-show and wa-after-show when calling show()', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~="panel"]')!;
|
||||
const showHandler = sinon.spy();
|
||||
const afterShowHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('wa-show', showHandler);
|
||||
el.addEventListener('wa-after-show', afterShowHandler);
|
||||
el.show();
|
||||
|
||||
await waitUntil(() => showHandler.calledOnce);
|
||||
await waitUntil(() => afterShowHandler.calledOnce);
|
||||
|
||||
expect(showHandler).to.have.been.calledOnce;
|
||||
expect(afterShowHandler).to.have.been.calledOnce;
|
||||
expect(panel.hidden).to.be.false;
|
||||
});
|
||||
|
||||
it('should emit wa-hide and wa-after-hide when calling hide()', async () => {
|
||||
// @TODO: Fix this [Konnor]
|
||||
if (fixture.type === 'ssr-client-hydrated') {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown open>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~="panel"]')!;
|
||||
const hideHandler = sinon.spy();
|
||||
const afterHideHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('wa-hide', hideHandler);
|
||||
el.addEventListener('wa-after-hide', afterHideHandler);
|
||||
el.hide();
|
||||
|
||||
await waitUntil(() => hideHandler.calledOnce);
|
||||
await waitUntil(() => afterHideHandler.calledOnce);
|
||||
|
||||
expect(hideHandler).to.have.been.calledOnce;
|
||||
expect(afterHideHandler).to.have.been.calledOnce;
|
||||
expect(panel.hidden).to.be.true;
|
||||
});
|
||||
|
||||
it('should emit wa-show and wa-after-show when setting open = true', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~="panel"]')!;
|
||||
const showHandler = sinon.spy();
|
||||
const afterShowHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('wa-show', showHandler);
|
||||
el.addEventListener('wa-after-show', afterShowHandler);
|
||||
el.open = true;
|
||||
|
||||
await waitUntil(() => showHandler.calledOnce);
|
||||
await waitUntil(() => afterShowHandler.calledOnce);
|
||||
|
||||
expect(showHandler).to.have.been.calledOnce;
|
||||
expect(afterShowHandler).to.have.been.calledOnce;
|
||||
expect(panel.hidden).to.be.false;
|
||||
});
|
||||
|
||||
it('should emit wa-hide and wa-after-hide when setting open = false', async () => {
|
||||
// @TODO: Fix this [Konnor]
|
||||
if (fixture.type === 'ssr-client-hydrated') {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown open>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const panel = el.shadowRoot!.querySelector<HTMLElement>('[part~="panel"]')!;
|
||||
const hideHandler = sinon.spy();
|
||||
const afterHideHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('wa-hide', hideHandler);
|
||||
el.addEventListener('wa-after-hide', afterHideHandler);
|
||||
el.open = false;
|
||||
|
||||
await waitUntil(() => hideHandler.calledOnce);
|
||||
await waitUntil(() => afterHideHandler.calledOnce);
|
||||
|
||||
expect(hideHandler).to.have.been.calledOnce;
|
||||
expect(afterHideHandler).to.have.been.calledOnce;
|
||||
expect(panel.hidden).to.be.true;
|
||||
});
|
||||
|
||||
it('should still open on arrow navigation when no menu items', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu> </wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const trigger = el.querySelector('wa-button')!;
|
||||
|
||||
trigger.focus();
|
||||
await sendKeys({ press: 'ArrowDown' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.open).to.be.true;
|
||||
});
|
||||
|
||||
it('should open on arrow down navigation', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const trigger = el.querySelector('wa-button')!;
|
||||
const firstMenuItem = el.querySelectorAll('wa-menu-item')[0];
|
||||
|
||||
trigger.focus();
|
||||
await sendKeys({ press: 'ArrowDown' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.open).to.be.true;
|
||||
expect(document.activeElement).to.equal(firstMenuItem);
|
||||
});
|
||||
|
||||
it('should open on arrow up navigation', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const trigger = el.querySelector('wa-button')!;
|
||||
const secondMenuItem = el.querySelectorAll('wa-menu-item')[1];
|
||||
|
||||
trigger.focus();
|
||||
await sendKeys({ press: 'ArrowUp' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.open).to.be.true;
|
||||
expect(document.activeElement).to.equal(secondMenuItem);
|
||||
});
|
||||
|
||||
it('should navigate to first focusable item on arrow navigation', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-label>Top Label</wa-menu-label>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const trigger = el.querySelector('wa-button')!;
|
||||
const item = el.querySelector('wa-menu-item')!;
|
||||
|
||||
await clickOnElement(trigger);
|
||||
await trigger.updateComplete;
|
||||
await sendKeys({ press: 'ArrowDown' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(document.activeElement).to.equal(item);
|
||||
});
|
||||
|
||||
it('should close on escape key', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown open>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const trigger = el.querySelector('wa-button')!;
|
||||
|
||||
trigger.focus();
|
||||
await sendKeys({ press: 'Escape' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.open).to.be.false;
|
||||
});
|
||||
|
||||
it('should not open on arrow navigation when no menu exists', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<div>Some custom content</div>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const trigger = el.querySelector('wa-button')!;
|
||||
|
||||
trigger.focus();
|
||||
await sendKeys({ press: 'ArrowDown' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.open).to.be.false;
|
||||
});
|
||||
|
||||
it('should open on enter key', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const trigger = el.querySelector('wa-button')!;
|
||||
|
||||
trigger.focus();
|
||||
await el.updateComplete;
|
||||
await sendKeys({ press: 'Enter' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.open).to.be.true;
|
||||
});
|
||||
|
||||
it('should focus on menu items when clicking the trigger and arrowing through options', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const trigger = el.querySelector('wa-button')!;
|
||||
const secondMenuItem = el.querySelectorAll('wa-menu-item')[1];
|
||||
|
||||
await clickOnElement(trigger);
|
||||
await trigger.updateComplete;
|
||||
await sendKeys({ press: 'ArrowDown' });
|
||||
await el.updateComplete;
|
||||
await sendKeys({ press: 'ArrowDown' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(document.activeElement).to.equal(secondMenuItem);
|
||||
});
|
||||
|
||||
it('should open on enter key when no menu exists', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<div>Some custom content</div>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const trigger = el.querySelector('wa-button')!;
|
||||
|
||||
trigger.focus();
|
||||
await el.updateComplete;
|
||||
await sendKeys({ press: 'Enter' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.open).to.be.true;
|
||||
});
|
||||
|
||||
it('should hide when clicked outside container and initially open', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown open>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
|
||||
await sendMouse({ type: 'click', position: [0, 0] });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.open).to.be.false;
|
||||
});
|
||||
|
||||
it('should hide when clicked outside container', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const trigger = el.querySelector('wa-button')!;
|
||||
|
||||
trigger.click();
|
||||
await el.updateComplete;
|
||||
await sendMouse({ type: 'click', position: [0, 0] });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.open).to.be.false;
|
||||
});
|
||||
|
||||
it('should close and stop propagating the keydown event when Escape is pressed and the dropdown is open ', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown open>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
<wa-menu>
|
||||
<wa-menu-item>Dropdown Item 1</wa-menu-item>
|
||||
<wa-menu-item>Dropdown Item 2</wa-menu-item>
|
||||
<wa-menu-item>Dropdown Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-dropdown>
|
||||
`);
|
||||
const firstMenuItem = el.querySelector('wa-menu-item')!;
|
||||
const hideHandler = sinon.spy();
|
||||
|
||||
document.body.addEventListener('keydown', hideHandler);
|
||||
firstMenuItem.focus();
|
||||
await sendKeys({ press: 'Escape' });
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.open).to.be.false;
|
||||
|
||||
if ('CloseWatcher' in window) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @TODO: Fix this [Konnor]
|
||||
if (fixture.type === 'ssr-client-hydrated') {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(hideHandler).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
}
|
||||
expect(el).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -129,8 +129,8 @@ textarea {
|
||||
}
|
||||
}
|
||||
|
||||
.prefix,
|
||||
.suffix {
|
||||
.start,
|
||||
.end {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
@@ -141,11 +141,11 @@ textarea {
|
||||
}
|
||||
}
|
||||
|
||||
.prefix::slotted(*) {
|
||||
.start::slotted(*) {
|
||||
margin-inline-end: var(--wa-form-control-padding-inline);
|
||||
}
|
||||
|
||||
.suffix::slotted(*) {
|
||||
.end::slotted(*) {
|
||||
margin-inline-start: var(--wa-form-control-padding-inline);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ import styles from './input.css';
|
||||
* @dependency wa-icon
|
||||
*
|
||||
* @slot label - The input's label. Alternatively, you can use the `label` attribute.
|
||||
* @slot prefix - Used to prepend a presentational icon or similar element to the input.
|
||||
* @slot suffix - Used to append a presentational icon or similar element to the input.
|
||||
* @slot start - An element, such as `<wa-icon>`, placed at the start of the input control.
|
||||
* @slot end - An element, such as `<wa-icon>`, placed at the end of the input control.
|
||||
* @slot clear-icon - An icon to use in lieu of the default clear icon.
|
||||
* @slot show-password-icon - An icon to use in lieu of the default show password icon.
|
||||
* @slot hide-password-icon - An icon to use in lieu of the default hide password icon.
|
||||
@@ -43,10 +43,10 @@ import styles from './input.css';
|
||||
* @csspart hint - The hint's wrapper.
|
||||
* @csspart input - The wrapper being rendered as an input
|
||||
* @csspart base - The internal `<input>` control.
|
||||
* @csspart prefix - The container that wraps the prefix.
|
||||
* @csspart start - The container that wraps the `start` slot.
|
||||
* @csspart clear-button - The clear button.
|
||||
* @csspart password-toggle-button - The password toggle button.
|
||||
* @csspart suffix - The container that wraps the suffix.
|
||||
* @csspart end - The container that wraps the `end` slot.
|
||||
*
|
||||
* @cssproperty --background-color - The input's background color.
|
||||
* @cssproperty --border-color - The color of the input's borders.
|
||||
@@ -223,8 +223,9 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
@property({ attribute: 'with-hint', type: Boolean }) withHint = false;
|
||||
|
||||
private handleChange(event: Event) {
|
||||
this.relayNativeEvent(event, { bubbles: true, composed: true });
|
||||
this.value = this.input.value;
|
||||
|
||||
this.relayNativeEvent(event, { bubbles: true, composed: true });
|
||||
}
|
||||
|
||||
private handleClearClick(event: MouseEvent) {
|
||||
@@ -232,9 +233,12 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
if (this.value !== '') {
|
||||
this.value = '';
|
||||
this.dispatchEvent(new WaClearEvent());
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new WaClearEvent());
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
|
||||
this.input.focus();
|
||||
@@ -356,7 +360,7 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
</label>
|
||||
|
||||
<div part="input" class="text-field">
|
||||
<slot name="prefix" part="prefix" class="prefix"></slot>
|
||||
<slot name="start" part="start" class="start"></slot>
|
||||
|
||||
<input
|
||||
part="base"
|
||||
@@ -430,7 +434,7 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
`
|
||||
: ''}
|
||||
|
||||
<slot name="suffix" part="suffix" class="suffix"></slot>
|
||||
<slot name="end" part="end" class="end"></slot>
|
||||
</div>
|
||||
|
||||
<slot
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
:host {
|
||||
--background-color-hover: var(--wa-color-neutral-fill-normal);
|
||||
--text-color-hover: var(--wa-color-neutral-on-normal);
|
||||
--submenu-offset: -0.125rem;
|
||||
|
||||
display: block;
|
||||
color: var(--wa-color-text-normal);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
font: inherit;
|
||||
padding: 0.5em 0.25em;
|
||||
line-height: var(--wa-line-height-condensed);
|
||||
transition: fill var(--wa-transition-normal) var(--wa-transition-easing);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:host([inert]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([disabled]) {
|
||||
outline: none;
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
:host([loading]) {
|
||||
outline: none;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
:host([loading]) *:not(wa-spinner) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
:host([loading]) wa-spinner {
|
||||
--indicator-color: currentColor;
|
||||
--track-width: round(0.0625em, 1px);
|
||||
position: absolute;
|
||||
font-size: var(--wa-font-size-smaller);
|
||||
top: calc(50% - 0.5em);
|
||||
left: 0.6em;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.label {
|
||||
flex: 1 1 auto;
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.prefix {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.prefix::slotted(*) {
|
||||
margin-inline-end: 0.5em;
|
||||
}
|
||||
|
||||
.suffix {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.suffix::slotted(*) {
|
||||
margin-inline-start: 0.5em;
|
||||
}
|
||||
|
||||
/* Safe triangle */
|
||||
:host(:state(submenu-expanded))::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
z-index: 899;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
clip-path: polygon(
|
||||
var(--safe-triangle-cursor-x, 0) var(--safe-triangle-cursor-y, 0),
|
||||
var(--safe-triangle-submenu-start-x, 0) var(--safe-triangle-submenu-start-y, 0),
|
||||
var(--safe-triangle-submenu-end-x, 0) var(--safe-triangle-submenu-end-y, 0)
|
||||
);
|
||||
}
|
||||
|
||||
:host(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:host(:hover:not([aria-disabled='true'], :focus-visible)),
|
||||
:host(:state(submenu-expanded)) {
|
||||
background-color: var(--background-color-hover);
|
||||
color: var(--text-color-hover);
|
||||
}
|
||||
|
||||
:host(:focus-visible) {
|
||||
outline: var(--wa-focus-ring);
|
||||
outline-offset: calc(-1 * var(--wa-focus-ring-width));
|
||||
background: var(--background-color-hover);
|
||||
color: var(--text-color-hover);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.check,
|
||||
.chevron {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--wa-font-size-smaller);
|
||||
width: 2em;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
:host([checked]) .check,
|
||||
:host(:state(has-submenu)) .chevron {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Add elevation and z-index to submenus */
|
||||
wa-popup::part(popup) {
|
||||
box-shadow: var(--wa-shadow-m);
|
||||
z-index: 900;
|
||||
margin-left: var(--submenu-offset);
|
||||
}
|
||||
|
||||
wa-popup:dir(rtl)::part(popup) {
|
||||
margin-left: calc(-1 * var(--submenu-offset));
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
:host(:hover:not([aria-disabled='true'])),
|
||||
:host(:focus-visible) {
|
||||
outline: dashed 1px SelectedItem;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
::slotted(wa-menu) {
|
||||
max-width: var(--auto-size-available-width) !important;
|
||||
max-height: var(--auto-size-available-height) !important;
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
import { aTimeout, expect, waitUntil } from '@open-wc/testing';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import { html } from 'lit';
|
||||
import sinon from 'sinon';
|
||||
import type { WaSelectEvent } from '../../events/select.js';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import type WaMenuItem from './menu-item.js';
|
||||
|
||||
describe('<wa-menu-item>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaMenuItem>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
<wa-divider></wa-divider>
|
||||
<wa-menu-item type="checkbox" checked>Checked</wa-menu-item>
|
||||
<wa-menu-item type="checkbox">Unchecked</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
it('should pass accessibility tests when using a submenu', async () => {
|
||||
const el = await fixture<WaMenuItem>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item>
|
||||
Submenu
|
||||
<wa-menu slot="submenu">
|
||||
<wa-menu-item>Submenu Item 1</wa-menu-item>
|
||||
<wa-menu-item>Submenu Item 2</wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
it('should have the correct default properties', async () => {
|
||||
const el = await fixture<WaMenuItem>(html` <wa-menu-item>Test</wa-menu-item> `);
|
||||
|
||||
expect(el.value).to.equal('');
|
||||
expect(el.disabled).to.be.false;
|
||||
expect(el.loading).to.equal(false);
|
||||
expect(el.getAttribute('aria-disabled')).to.equal('false');
|
||||
});
|
||||
|
||||
it('should render the correct aria attributes when disabled', async () => {
|
||||
const el = await fixture<WaMenuItem>(html` <wa-menu-item disabled>Test</wa-menu-item> `);
|
||||
expect(el.getAttribute('aria-disabled')).to.equal('true');
|
||||
});
|
||||
|
||||
describe('when loading', () => {
|
||||
it('should have a spinner present', async () => {
|
||||
const el = await fixture<WaMenuItem>(html` <wa-menu-item loading>Menu Item Label</wa-menu-item> `);
|
||||
expect(el.shadowRoot!.querySelector('wa-spinner')).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
it('defaultLabel should return a text label', async () => {
|
||||
const el = await fixture<WaMenuItem>(html` <wa-menu-item>Test</wa-menu-item> `);
|
||||
expect(el.defaultLabel).to.equal('Test');
|
||||
expect(el.label).to.equal('Test');
|
||||
});
|
||||
|
||||
it('label attribute should override default label', async () => {
|
||||
const el = await fixture<WaMenuItem>(html` <wa-menu-item label="Manual label">Text content</wa-menu-item> `);
|
||||
expect(el.defaultLabel).to.equal('Text content');
|
||||
expect(el.label).to.equal('Manual label');
|
||||
});
|
||||
|
||||
it('should emit the slotchange event when the label changes', async () => {
|
||||
const el = await fixture<WaMenuItem>(html` <wa-menu-item>Text</wa-menu-item> `);
|
||||
const slotChangeHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('slotchange', slotChangeHandler);
|
||||
el.textContent = 'New Text';
|
||||
await waitUntil(() => slotChangeHandler.calledOnce);
|
||||
|
||||
expect(slotChangeHandler).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should render a hidden menu item when the inert attribute is used', async () => {
|
||||
const menu = await fixture<WaMenuItem>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item inert>Item 1</wa-menu-item>
|
||||
<wa-menu-item>Item 2</wa-menu-item>
|
||||
<wa-menu-item>Item 3</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
const item1 = menu.querySelector('wa-menu-item')!;
|
||||
|
||||
expect(getComputedStyle(item1).display).to.equal('none');
|
||||
});
|
||||
|
||||
it('should not render a wa-popup if the slot="submenu" attribute is missing, but the slot should exist in the component and be hidden.', async () => {
|
||||
const menu = await fixture<WaMenuItem>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item>
|
||||
Item 1
|
||||
<wa-menu>
|
||||
<wa-menu-item> Nested Item 1 </wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
|
||||
const menuItem: HTMLElement = menu.querySelector('wa-menu-item')!;
|
||||
expect(menuItem.shadowRoot!.querySelector('wa-popup')).to.be.null;
|
||||
const submenuSlot: HTMLElement = menuItem.shadowRoot!.querySelector('slot[name="submenu"]')!;
|
||||
expect(submenuSlot.hidden).to.be.true;
|
||||
});
|
||||
|
||||
it('should render a wa-popup if the slot="submenu" attribute is present', async () => {
|
||||
const menu = await fixture<WaMenuItem>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item id="test">
|
||||
Item 1
|
||||
<wa-menu slot="submenu">
|
||||
<wa-menu-item> Nested Item 1 </wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
|
||||
const menuItem = menu.querySelector('wa-menu-item')!;
|
||||
expect(menuItem.shadowRoot!.querySelector('wa-popup')).to.be.not.null;
|
||||
const submenuSlot: HTMLElement = menuItem.shadowRoot!.querySelector('slot[name="submenu"]')!;
|
||||
expect(submenuSlot.hidden).to.be.false;
|
||||
});
|
||||
|
||||
it('should focus on first menuitem of submenu if ArrowRight is pressed on parent menuitem', async () => {
|
||||
const menu = await fixture<WaMenuItem>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item id="item-1">
|
||||
Submenu
|
||||
<wa-menu slot="submenu">
|
||||
<wa-menu-item value="submenu-item-1"> Nested Item 1 </wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
|
||||
const selectHandler = sinon.spy((event: WaSelectEvent) => {
|
||||
const item = event.detail.item;
|
||||
expect(item.value).to.equal('submenu-item-1');
|
||||
});
|
||||
menu.addEventListener('wa-select', selectHandler);
|
||||
|
||||
const submenu = menu.querySelector<WaMenuItem>('wa-menu-item')!;
|
||||
// Sometimes Chrome fails if we dont click before triggering focus.
|
||||
await clickOnElement(submenu);
|
||||
submenu.focus();
|
||||
await menu.updateComplete;
|
||||
await sendKeys({ press: 'ArrowRight' });
|
||||
await menu.updateComplete;
|
||||
await sendKeys({ press: 'Enter' });
|
||||
await menu.updateComplete;
|
||||
// Once for each menu element.
|
||||
expect(selectHandler).to.have.been.calledTwice;
|
||||
});
|
||||
|
||||
it('should focus on outer menu if ArrowRight is pressed on nested menuitem', async () => {
|
||||
const menu = await fixture<WaMenuItem>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item id="outer" value="outer-item-1">
|
||||
Submenu
|
||||
<wa-menu slot="submenu">
|
||||
<wa-menu-item value="inner-item-1"> Nested Item 1 </wa-menu-item>
|
||||
</wa-menu>
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
|
||||
const focusHandler = sinon.spy((event: FocusEvent) => {
|
||||
const target = event.target as WaMenuItem;
|
||||
const relatedTarget = event.relatedTarget as WaMenuItem;
|
||||
expect(target.value).to.equal('outer-item-1');
|
||||
expect(relatedTarget.value).to.equal('inner-item-1');
|
||||
});
|
||||
|
||||
const outerItem = menu.querySelector<WaMenuItem>('#outer')!;
|
||||
// Silly fix for CI + Chrome to focus properly.
|
||||
await clickOnElement(outerItem);
|
||||
|
||||
outerItem.focus();
|
||||
await menu.updateComplete;
|
||||
await sendKeys({ press: 'ArrowRight' });
|
||||
|
||||
outerItem.addEventListener('focus', focusHandler);
|
||||
await menu.updateComplete;
|
||||
await sendKeys({ press: 'ArrowLeft' });
|
||||
await menu.updateComplete;
|
||||
expect(focusHandler).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,238 +0,0 @@
|
||||
import type { PropertyValues } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import getText from '../../internal/get-text.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import '../icon/icon.js';
|
||||
import '../popup/popup.js';
|
||||
import '../spinner/spinner.js';
|
||||
import styles from './menu-item.css';
|
||||
import { SubmenuController } from './submenu-controller.js';
|
||||
|
||||
/**
|
||||
* @summary Menu items provide options for the user to pick from in a menu.
|
||||
* @documentation https://backers.webawesome.com/docs/components/menu-item
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @dependency wa-icon
|
||||
* @dependency wa-popup
|
||||
*
|
||||
* @slot - The menu item's label.
|
||||
* @slot prefix - Used to prepend an icon or similar element to the menu item.
|
||||
* @slot suffix - Used to append an icon or similar element to the menu item.
|
||||
* @slot submenu - Used to denote a nested menu.
|
||||
* @slot checked-icon - The icon used to indicate that this menu item is checked. Usually a `<wa-icon>`.
|
||||
* @slot submenu-icon - The icon used to indicate that this menu item has a submenu. Usually a `<wa-icon>`.
|
||||
*
|
||||
* @csspart checked-icon - The checked icon, which is only visible when the menu item is checked.
|
||||
* @csspart prefix - The prefix container.
|
||||
* @csspart label - The menu item label.
|
||||
* @csspart suffix - The suffix container.
|
||||
* @csspart spinner - The spinner that shows when the menu item is in the loading state.
|
||||
* @csspart spinner__base - The spinner's base part.
|
||||
* @csspart submenu-icon - The submenu icon, visible only when the menu item has a submenu (not yet implemented).
|
||||
*
|
||||
* @cssproperty --background-color-hover - The menu item's background color on hover.
|
||||
* @cssproperty --text-color-hover - The label color on hover.
|
||||
* @cssproperty [--submenu-offset=-2px] - The distance submenus shift to overlap the parent menu.
|
||||
*
|
||||
* @cssstate has-submenu - Applied when the menu item has a submenu.
|
||||
* @cssstate submenu-expanded - Applied when the menu item has a submenu and it is expanded.
|
||||
*/
|
||||
@customElement('wa-menu-item')
|
||||
export default class WaMenuItem extends WebAwesomeElement {
|
||||
static css = styles;
|
||||
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
|
||||
@query('.menu-item') menuItem: HTMLElement;
|
||||
|
||||
/** The type of menu item to render. To use `checked`, this value must be set to `checkbox`. */
|
||||
@property() type: 'normal' | 'checkbox' = 'normal';
|
||||
|
||||
/** Draws the item in a checked state. */
|
||||
@property({ type: Boolean, reflect: true }) checked = false;
|
||||
|
||||
/** A unique value to store in the menu item. This can be used as a way to identify menu items when selected. */
|
||||
@property() value = '';
|
||||
|
||||
/** Draws the menu item in a loading state. */
|
||||
@property({ type: Boolean, reflect: true }) loading = false;
|
||||
|
||||
/** Draws the menu item in a disabled state, preventing selection. */
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
_label: string = '';
|
||||
/**
|
||||
* The option’s plain text label.
|
||||
* Usually automatically generated, but can be useful to provide manually for cases involving complex content.
|
||||
*/
|
||||
@property()
|
||||
set label(value) {
|
||||
const oldValue = this._label;
|
||||
this._label = value || '';
|
||||
|
||||
if (this._label !== oldValue) {
|
||||
this.requestUpdate('label', oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
if (this._label) {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
if (!this.defaultLabel) {
|
||||
this.updateDefaultLabel();
|
||||
}
|
||||
|
||||
return this.defaultLabel;
|
||||
}
|
||||
|
||||
/** The default label, generated from the element contents. Will be equal to `label` in most cases. */
|
||||
@state() defaultLabel = '';
|
||||
|
||||
/**
|
||||
* Used for SSR purposes. If true, will render a ">" caret icon for showing that it has a submenu, but will be non-interactive.
|
||||
*/
|
||||
@property({ attribute: 'with-submenu', type: Boolean }) withSubmenu = false;
|
||||
|
||||
private submenuController: SubmenuController = new SubmenuController(this);
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.addEventListener('click', this.handleHostClick);
|
||||
this.addEventListener('mouseover', this.handleMouseOver);
|
||||
this.updateDefaultLabel();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener('click', this.handleHostClick);
|
||||
this.removeEventListener('mouseover', this.handleMouseOver);
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues<this>): void {
|
||||
// Kick it so that it renders the "submenu" properly.
|
||||
if (this.isSubmenu()) {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
super.firstUpdated(changedProperties);
|
||||
}
|
||||
|
||||
private handleDefaultSlotChange() {
|
||||
let labelChanged = this.updateDefaultLabel();
|
||||
|
||||
// When the label changes, emit a slotchange event so parent controls see it
|
||||
if (labelChanged) {
|
||||
/** @internal - prevent the CEM from recording this event */
|
||||
this.dispatchEvent(new Event('slotchange', { bubbles: true, composed: false, cancelable: false }));
|
||||
}
|
||||
|
||||
this.customStates.set('has-submenu', this.isSubmenu());
|
||||
}
|
||||
|
||||
private handleHostClick = (event: MouseEvent) => {
|
||||
// Prevent the click event from being emitted when the button is disabled or loading
|
||||
if (this.disabled) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
};
|
||||
|
||||
private handleMouseOver = (event: MouseEvent) => {
|
||||
this.focus();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has('checked')) {
|
||||
// For proper accessibility, users have to use type="checkbox" to use the checked attribute
|
||||
if (this.checked && this.type !== 'checkbox') {
|
||||
this.checked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Only checkbox types can receive the aria-checked attribute
|
||||
if (this.type === 'checkbox') {
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
} else {
|
||||
this.removeAttribute('aria-checked');
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProperties.has('disabled')) {
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
}
|
||||
|
||||
if (changedProperties.has('type')) {
|
||||
if (this.type === 'checkbox') {
|
||||
this.setAttribute('role', 'menuitemcheckbox');
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
} else {
|
||||
this.setAttribute('role', 'menuitem');
|
||||
this.removeAttribute('aria-checked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateDefaultLabel() {
|
||||
let oldValue = this.defaultLabel;
|
||||
this.defaultLabel = getText(this).trim();
|
||||
let changed = this.defaultLabel !== oldValue;
|
||||
|
||||
if (!this._label && changed) {
|
||||
// Uses default label, and it has changed
|
||||
this.requestUpdate('label', oldValue);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/** Does this element have a submenu? */
|
||||
private isSubmenu() {
|
||||
return this.hasUpdated ? this.querySelector(`:scope > [slot="submenu"]`) !== null : this.withSubmenu;
|
||||
}
|
||||
|
||||
render() {
|
||||
const isRtl = this.hasUpdated ? this.localize.dir() === 'rtl' : this.dir === 'rtl';
|
||||
const isSubmenuExpanded = this.submenuController.isExpanded();
|
||||
this.customStates.set('submenu-expanded', isSubmenuExpanded);
|
||||
|
||||
this.internals.ariaHasPopup = this.isSubmenu() + '';
|
||||
this.internals.ariaExpanded = isSubmenuExpanded + '';
|
||||
|
||||
return html`
|
||||
<slot name="checked-icon" part="checked-icon" class="check">
|
||||
<wa-icon name="check" library="system" variant="solid" aria-hidden="true"></wa-icon>
|
||||
</slot>
|
||||
|
||||
<slot name="prefix" part="prefix" class="prefix"></slot>
|
||||
|
||||
<slot part="label" class="label" @slotchange=${this.handleDefaultSlotChange}></slot>
|
||||
|
||||
<slot name="suffix" part="suffix" class="suffix"></slot>
|
||||
|
||||
<slot name="submenu-icon" part="submenu-icon" class="chevron">
|
||||
<wa-icon
|
||||
name=${isRtl ? 'chevron-left' : 'chevron-right'}
|
||||
library="system"
|
||||
variant="solid"
|
||||
aria-hidden="true"
|
||||
></wa-icon>
|
||||
</slot>
|
||||
|
||||
${this.submenuController.renderSubmenu()} ${this.loading ? html`<wa-spinner part="spinner"></wa-spinner>` : ''}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'wa-menu-item': WaMenuItem;
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
||||
import { html } from 'lit';
|
||||
import { createRef, ref, type Ref } from 'lit/directives/ref.js';
|
||||
import type WaPopup from '../popup/popup.js';
|
||||
import type WaMenuItem from './menu-item.js';
|
||||
|
||||
/** A reactive controller to manage the registration of event listeners for submenus. */
|
||||
export class SubmenuController implements ReactiveController {
|
||||
private host: ReactiveControllerHost & WaMenuItem;
|
||||
private popupRef: Ref<WaPopup> = createRef();
|
||||
private enableSubmenuTimer = -1;
|
||||
private isConnected = false;
|
||||
private isPopupConnected = false;
|
||||
private skidding = 0;
|
||||
private readonly submenuOpenDelay = 100;
|
||||
|
||||
constructor(host: ReactiveControllerHost & WaMenuItem) {
|
||||
(this.host = host).addController(this);
|
||||
}
|
||||
|
||||
private hasSubmenu() {
|
||||
return this.host.querySelector(`:scope > [slot="submenu"]`) !== null;
|
||||
}
|
||||
|
||||
hostConnected() {
|
||||
if (this.hasSubmenu() && !this.host.disabled) {
|
||||
this.addListeners();
|
||||
}
|
||||
}
|
||||
|
||||
hostDisconnected() {
|
||||
this.removeListeners();
|
||||
}
|
||||
|
||||
hostUpdated() {
|
||||
if (this.hasSubmenu() && !this.host.disabled) {
|
||||
this.addListeners();
|
||||
this.updateSkidding();
|
||||
} else {
|
||||
this.removeListeners();
|
||||
}
|
||||
}
|
||||
|
||||
private addListeners() {
|
||||
if (!this.isConnected) {
|
||||
this.host.addEventListener('mousemove', this.handleMouseMove);
|
||||
this.host.addEventListener('mouseover', this.handleMouseOver);
|
||||
this.host.addEventListener('keydown', this.handleKeyDown);
|
||||
this.host.addEventListener('click', this.handleClick);
|
||||
this.host.addEventListener('focusout', this.handleFocusOut);
|
||||
this.isConnected = true;
|
||||
}
|
||||
|
||||
// The popup does not seem to get wired when the host is
|
||||
// connected, so manage its listeners separately.
|
||||
if (!this.isPopupConnected) {
|
||||
if (this.popupRef.value) {
|
||||
this.popupRef.value.addEventListener('mouseover', this.handlePopupMouseover);
|
||||
this.popupRef.value.addEventListener('wa-reposition', this.handlePopupReposition);
|
||||
this.isPopupConnected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeListeners() {
|
||||
if (this.isConnected) {
|
||||
this.host.removeEventListener('mousemove', this.handleMouseMove);
|
||||
this.host.removeEventListener('mouseover', this.handleMouseOver);
|
||||
this.host.removeEventListener('keydown', this.handleKeyDown);
|
||||
this.host.removeEventListener('click', this.handleClick);
|
||||
this.host.removeEventListener('focusout', this.handleFocusOut);
|
||||
this.isConnected = false;
|
||||
}
|
||||
if (this.isPopupConnected) {
|
||||
if (this.popupRef.value) {
|
||||
this.popupRef.value.removeEventListener('mouseover', this.handlePopupMouseover);
|
||||
this.popupRef.value.removeEventListener('wa-reposition', this.handlePopupReposition);
|
||||
this.isPopupConnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the safe triangle cursor position
|
||||
private handleMouseMove = (event: MouseEvent) => {
|
||||
this.host.style.setProperty('--safe-triangle-cursor-x', `${event.clientX}px`);
|
||||
this.host.style.setProperty('--safe-triangle-cursor-y', `${event.clientY}px`);
|
||||
};
|
||||
|
||||
private handleMouseOver = () => {
|
||||
if (this.hasSubmenu()) {
|
||||
this.enableSubmenu();
|
||||
}
|
||||
};
|
||||
|
||||
private handleSubmenuEntry(event: KeyboardEvent) {
|
||||
// Pass focus to the first menu-item in the submenu.
|
||||
const submenuSlot: HTMLSlotElement | null = this.host.renderRoot.querySelector("slot[name='submenu']");
|
||||
|
||||
// Missing slot
|
||||
if (!submenuSlot) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Menus
|
||||
let menuItems: NodeListOf<Element> | null = null;
|
||||
for (const elt of submenuSlot.assignedElements()) {
|
||||
menuItems = elt.querySelectorAll("wa-menu-item, [role^='menuitem']");
|
||||
if (menuItems.length !== 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!menuItems || menuItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
menuItems[0].setAttribute('tabindex', '0');
|
||||
for (let i = 1; i !== menuItems.length; ++i) {
|
||||
menuItems[i].setAttribute('tabindex', '-1');
|
||||
}
|
||||
|
||||
// Open the submenu (if not open), and set focus to first menuitem.
|
||||
if (this.popupRef.value) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (this.popupRef.value.active) {
|
||||
if (menuItems[0] instanceof HTMLElement) {
|
||||
menuItems[0].focus();
|
||||
}
|
||||
} else {
|
||||
this.enableSubmenu(false);
|
||||
this.host.updateComplete.then(() => {
|
||||
if (menuItems[0] instanceof HTMLElement) {
|
||||
menuItems[0].focus();
|
||||
}
|
||||
});
|
||||
this.host.requestUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Focus on the first menu-item of a submenu.
|
||||
private handleKeyDown = (event: KeyboardEvent) => {
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
case 'Tab':
|
||||
this.disableSubmenu();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
// Either focus is currently on the host element or a child
|
||||
if (event.target !== this.host) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.host.focus();
|
||||
this.disableSubmenu();
|
||||
}
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
this.handleSubmenuEntry(event);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
private handleClick = (event: MouseEvent) => {
|
||||
// Clicking on the item which heads the menu does nothing, otherwise hide submenu and propagate
|
||||
if (event.target === this.host) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else if (
|
||||
event.target instanceof Element &&
|
||||
(event.target.tagName === 'wa-menu-item' || event.target.role?.startsWith('menuitem'))
|
||||
) {
|
||||
this.disableSubmenu();
|
||||
}
|
||||
};
|
||||
|
||||
// Close this submenu on focus outside of the parent or any descendants.
|
||||
private handleFocusOut = (event: FocusEvent) => {
|
||||
if (event.relatedTarget && event.relatedTarget instanceof Element && this.host.contains(event.relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
this.disableSubmenu();
|
||||
};
|
||||
|
||||
// Prevent the parent menu-item from getting focus on mouse movement on the submenu
|
||||
private handlePopupMouseover = (event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
// Set the safe triangle values for the submenu when the position changes
|
||||
private handlePopupReposition = () => {
|
||||
const submenuSlot: HTMLSlotElement | null = this.host.renderRoot.querySelector("slot[name='submenu']");
|
||||
const menu = submenuSlot?.assignedElements({ flatten: true }).filter(el => el.localName === 'wa-menu')[0];
|
||||
const isRtl = getComputedStyle(this.host).direction === 'rtl';
|
||||
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { left, top, width, height } = menu.getBoundingClientRect();
|
||||
|
||||
this.host.style.setProperty('--safe-triangle-submenu-start-x', `${isRtl ? left + width : left}px`);
|
||||
this.host.style.setProperty('--safe-triangle-submenu-start-y', `${top}px`);
|
||||
this.host.style.setProperty('--safe-triangle-submenu-end-x', `${isRtl ? left + width : left}px`);
|
||||
this.host.style.setProperty('--safe-triangle-submenu-end-y', `${top + height}px`);
|
||||
};
|
||||
|
||||
private setSubmenuState(state: boolean) {
|
||||
if (this.popupRef.value) {
|
||||
if (this.popupRef.value.active !== state) {
|
||||
this.popupRef.value.active = state;
|
||||
this.host.requestUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shows the submenu. Supports disabling the opening delay, e.g. for keyboard events that want to set the focus to the
|
||||
// newly opened menu.
|
||||
private enableSubmenu(delay = true) {
|
||||
if (delay) {
|
||||
window.clearTimeout(this.enableSubmenuTimer);
|
||||
this.enableSubmenuTimer = window.setTimeout(() => {
|
||||
this.setSubmenuState(true);
|
||||
}, this.submenuOpenDelay);
|
||||
} else {
|
||||
this.setSubmenuState(true);
|
||||
}
|
||||
}
|
||||
|
||||
private disableSubmenu() {
|
||||
window.clearTimeout(this.enableSubmenuTimer);
|
||||
this.setSubmenuState(false);
|
||||
}
|
||||
|
||||
// Calculate the space the top of a menu takes-up, for aligning the popup menu-item with the activating element.
|
||||
private updateSkidding(): void {
|
||||
// .computedStyleMap() not always available.
|
||||
if (!this.host.parentElement?.computedStyleMap) {
|
||||
return;
|
||||
}
|
||||
const styleMap: StylePropertyMapReadOnly = this.host.parentElement.computedStyleMap();
|
||||
const attrs: string[] = ['padding-top', 'border-top-width', 'margin-top'];
|
||||
|
||||
const skidding = attrs.reduce((accumulator, attr) => {
|
||||
const styleValue: CSSStyleValue = styleMap.get(attr) ?? new CSSUnitValue(0, 'px');
|
||||
const unitValue = styleValue instanceof CSSUnitValue ? styleValue : new CSSUnitValue(0, 'px');
|
||||
const pxValue = unitValue.to('px');
|
||||
return accumulator - pxValue.value;
|
||||
}, 0);
|
||||
|
||||
this.skidding = skidding;
|
||||
}
|
||||
|
||||
isExpanded(): boolean {
|
||||
return this.popupRef.value ? this.popupRef.value.active : false;
|
||||
}
|
||||
|
||||
renderSubmenu() {
|
||||
// Always render the slot, but conditionally render the outer <wa-popup>
|
||||
if (!this.host.hasUpdated) {
|
||||
return html` <slot name="submenu" hidden></slot> `;
|
||||
}
|
||||
|
||||
const isRtl = getComputedStyle(this.host).direction === 'rtl';
|
||||
|
||||
return html`
|
||||
<wa-popup
|
||||
${ref(this.popupRef)}
|
||||
placement=${isRtl ? 'left-start' : 'right-start'}
|
||||
.anchor="${this.host}"
|
||||
flip
|
||||
flip-fallback-strategy="best-fit"
|
||||
skidding="${this.skidding}"
|
||||
auto-size="vertical"
|
||||
auto-size-padding="10"
|
||||
>
|
||||
<slot name="submenu"></slot>
|
||||
</wa-popup>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
:host {
|
||||
display: block;
|
||||
color: var(--wa-color-text-quiet);
|
||||
font-size: var(--wa-font-size-smaller);
|
||||
font-weight: var(--wa-font-weight-semibold);
|
||||
padding: 0.5em 2.25em;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { html } from 'lit';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import type WaMenuLabel from './menu-label.js';
|
||||
|
||||
describe('<wa-menu-label>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('passes accessibility test', async () => {
|
||||
const el = await fixture<WaMenuLabel>(html` <wa-menu-label>Test</wa-menu-label> `);
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import styles from './menu-label.css';
|
||||
|
||||
/**
|
||||
* @summary Menu labels are used to describe a group of menu items.
|
||||
* @documentation https://backers.webawesome.com/docs/components/menu-label
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @slot - The menu label's content.
|
||||
*/
|
||||
@customElement('wa-menu-label')
|
||||
export default class WaMenuLabel extends WebAwesomeElement {
|
||||
static css = styles;
|
||||
|
||||
render() {
|
||||
return html`<slot></slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'wa-menu-label': WaMenuLabel;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: start;
|
||||
background-color: var(--wa-color-surface-raised);
|
||||
border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border);
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
padding: 0.5em 0;
|
||||
overflow: auto;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
::slotted(wa-divider) {
|
||||
--spacing: 0.5em;
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import { html } from 'lit';
|
||||
import sinon from 'sinon';
|
||||
import type { WaSelectEvent } from '../../events/select.js';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import type WaMenu from './menu.js';
|
||||
|
||||
describe('<wa-menu>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('emits wa-select with the correct event detail when clicking an item', async () => {
|
||||
const menu = await fixture<WaMenu>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item value="item-1">Item 1</wa-menu-item>
|
||||
<wa-menu-item value="item-2">Item 2</wa-menu-item>
|
||||
<wa-menu-item value="item-3">Item 3</wa-menu-item>
|
||||
<wa-menu-item value="item-4">Item 4</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
const item2 = menu.querySelectorAll('wa-menu-item')[1];
|
||||
const selectHandler = sinon.spy((event: WaSelectEvent) => {
|
||||
const item = event.detail.item;
|
||||
if (item !== item2) {
|
||||
expect.fail('Incorrect event detail emitted with wa-select');
|
||||
}
|
||||
});
|
||||
|
||||
menu.addEventListener('wa-select', selectHandler);
|
||||
await clickOnElement(item2);
|
||||
|
||||
expect(selectHandler).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('can be selected via keyboard', async () => {
|
||||
const menu = await fixture<WaMenu>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item value="item-1">Item 1</wa-menu-item>
|
||||
<wa-menu-item value="item-2">Item 2</wa-menu-item>
|
||||
<wa-menu-item value="item-3">Item 3</wa-menu-item>
|
||||
<wa-menu-item value="item-4">Item 4</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
const [item1, item2] = menu.querySelectorAll('wa-menu-item');
|
||||
const selectHandler = sinon.spy((event: WaSelectEvent) => {
|
||||
const item = event.detail.item;
|
||||
if (item !== item2) {
|
||||
expect.fail('Incorrect item selected');
|
||||
}
|
||||
});
|
||||
|
||||
menu.addEventListener('wa-select', selectHandler);
|
||||
|
||||
item1.focus();
|
||||
await item1.updateComplete;
|
||||
await sendKeys({ press: 'ArrowDown' });
|
||||
await sendKeys({ press: 'Enter' });
|
||||
|
||||
expect(selectHandler).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('does not select disabled items when clicking', async () => {
|
||||
const menu = await fixture<WaMenu>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item value="item-1">Item 1</wa-menu-item>
|
||||
<wa-menu-item value="item-2" disabled>Item 2</wa-menu-item>
|
||||
<wa-menu-item value="item-3">Item 3</wa-menu-item>
|
||||
<wa-menu-item value="item-4">Item 4</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
const item2 = menu.querySelectorAll('wa-menu-item')[1];
|
||||
const selectHandler = sinon.spy();
|
||||
|
||||
menu.addEventListener('wa-select', selectHandler);
|
||||
|
||||
await clickOnElement(item2);
|
||||
|
||||
expect(selectHandler).to.not.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('does not select disabled items when pressing enter', async () => {
|
||||
const menu = await fixture<WaMenu>(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item value="item-1">Item 1</wa-menu-item>
|
||||
<wa-menu-item value="item-2" disabled>Item 2</wa-menu-item>
|
||||
<wa-menu-item value="item-3">Item 3</wa-menu-item>
|
||||
<wa-menu-item value="item-4">Item 4</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
const [item1, item2] = menu.querySelectorAll('wa-menu-item');
|
||||
const selectHandler = sinon.spy();
|
||||
|
||||
menu.addEventListener('wa-select', selectHandler);
|
||||
|
||||
item1.focus();
|
||||
await item1.updateComplete;
|
||||
await sendKeys({ press: 'ArrowDown' });
|
||||
expect(document.activeElement).to.equal(item2);
|
||||
await sendKeys({ press: 'Enter' });
|
||||
await item2.updateComplete;
|
||||
|
||||
expect(selectHandler).to.not.have.been.called;
|
||||
});
|
||||
|
||||
// @see https://github.com/shoelace-style/shoelace/issues/1596
|
||||
it('Should fire "wa-select" when clicking an element within a menu-item', async () => {
|
||||
// eslint-disable-next-line
|
||||
const selectHandler = sinon.spy(() => {});
|
||||
|
||||
const menu: WaMenu = await fixture(html`
|
||||
<wa-menu>
|
||||
<wa-menu-item>
|
||||
<span>Menu item</span>
|
||||
</wa-menu-item>
|
||||
</wa-menu>
|
||||
`);
|
||||
|
||||
menu.addEventListener('wa-select', selectHandler);
|
||||
const span = menu.querySelector('span')!;
|
||||
await clickOnElement(span);
|
||||
|
||||
expect(selectHandler).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,172 +0,0 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { WaSelectEvent } from '../../events/select.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import sizeStyles from '../../styles/utilities/size.css';
|
||||
import '../menu-item/menu-item.js';
|
||||
import type WaMenuItem from '../menu-item/menu-item.js';
|
||||
import styles from './menu.css';
|
||||
|
||||
export interface MenuSelectEventDetail {
|
||||
item: WaMenuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Menus provide a list of options for the user to choose from.
|
||||
* @documentation https://backers.webawesome.com/docs/components/menu
|
||||
* @status stable
|
||||
* @since 2.0
|
||||
*
|
||||
* @dependency wa-menu-item
|
||||
*
|
||||
* @slot - The menu's content, including menu items, menu labels, and dividers.
|
||||
*
|
||||
* @event {{ item: WaMenuItem }} wa-select - Emitted when a menu item is selected.
|
||||
*/
|
||||
@customElement('wa-menu')
|
||||
export default class WaMenu extends WebAwesomeElement {
|
||||
static css = [sizeStyles, styles];
|
||||
|
||||
/** The component's size. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
|
||||
@query('slot') defaultSlot: HTMLSlotElement;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.setAttribute('role', 'menu');
|
||||
}
|
||||
|
||||
private handleClick(event: MouseEvent) {
|
||||
const menuItemTypes = ['menuitem', 'menuitemcheckbox'];
|
||||
|
||||
const target = event.composedPath().find((el: Element) => menuItemTypes.includes(el?.getAttribute?.('role') || ''));
|
||||
|
||||
if (!target) return;
|
||||
|
||||
// This isn't true. But we use it for TypeScript checks below.
|
||||
const item = target as WaMenuItem;
|
||||
|
||||
if (item.type === 'checkbox') {
|
||||
item.checked = !item.checked;
|
||||
}
|
||||
|
||||
this.dispatchEvent(new WaSelectEvent({ item }));
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent) {
|
||||
// Make a selection when pressing enter or space
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
const item = this.getCurrentItem();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// Simulate a click to support @click handlers on menu items that also work with the keyboard
|
||||
item?.click();
|
||||
}
|
||||
|
||||
// Move the selection when pressing down or up
|
||||
else if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
|
||||
const items = this.getAllItems();
|
||||
const activeItem = this.getCurrentItem();
|
||||
let index = activeItem ? items.indexOf(activeItem) : 0;
|
||||
|
||||
if (items.length > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
index++;
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
index--;
|
||||
} else if (event.key === 'Home') {
|
||||
index = 0;
|
||||
} else if (event.key === 'End') {
|
||||
index = items.length - 1;
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
index = items.length - 1;
|
||||
}
|
||||
if (index > items.length - 1) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
this.setCurrentItem(items[index]);
|
||||
items[index].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleMouseDown(event: MouseEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
if (this.isMenuItem(target)) {
|
||||
this.setCurrentItem(target as WaMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
private handleSlotChange() {
|
||||
const items = this.getAllItems();
|
||||
|
||||
// Reset the roving tab index when the slotted items change
|
||||
if (items.length > 0) {
|
||||
this.setCurrentItem(items[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private isMenuItem(item: HTMLElement) {
|
||||
return (
|
||||
item.tagName.toLowerCase() === 'wa-menu-item' ||
|
||||
['menuitem', 'menuitemcheckbox', 'menuitemradio'].includes(item.getAttribute('role') ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal Gets all slotted menu items, ignoring dividers, headers, and other elements. */
|
||||
getAllItems() {
|
||||
return [...this.defaultSlot.assignedElements({ flatten: true })].filter((el: HTMLElement) => {
|
||||
if (el.inert || !this.isMenuItem(el)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}) as WaMenuItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Gets the current menu item, which is the menu item that has `tabindex="0"` within the roving tab index.
|
||||
* The menu item may or may not have focus, but for keyboard interaction purposes it's considered the "active" item.
|
||||
*/
|
||||
getCurrentItem() {
|
||||
return this.getAllItems().find(i => i.getAttribute('tabindex') === '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Sets the current menu item to the specified element. This sets `tabindex="0"` on the target element and
|
||||
* `tabindex="-1"` to all other items. This method must be called prior to setting focus on a menu item.
|
||||
*/
|
||||
setCurrentItem(item: WaMenuItem) {
|
||||
const items = this.getAllItems();
|
||||
|
||||
// Update tab indexes
|
||||
items.forEach(i => {
|
||||
i.setAttribute('tabindex', i === item ? '0' : '-1');
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<slot
|
||||
@slotchange=${this.handleSlotChange}
|
||||
@click=${this.handleClick}
|
||||
@keydown=${this.handleKeyDown}
|
||||
@mousedown=${this.handleMouseDown}
|
||||
></slot>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'wa-menu': WaMenu;
|
||||
}
|
||||
}
|
||||
@@ -62,18 +62,18 @@
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.prefix,
|
||||
.suffix {
|
||||
.start,
|
||||
.end {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.prefix::slotted(*) {
|
||||
.start::slotted(*) {
|
||||
margin-inline-end: 0.5em;
|
||||
}
|
||||
|
||||
.suffix::slotted(*) {
|
||||
.end::slotted(*) {
|
||||
margin-inline-start: 0.5em;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -16,8 +16,8 @@ import styles from './option.css';
|
||||
* @dependency wa-icon
|
||||
*
|
||||
* @slot - The option's label.
|
||||
* @slot prefix - Used to prepend an icon or similar element to the menu item.
|
||||
* @slot suffix - Used to append an icon or similar element to the menu item.
|
||||
* @slot start - An element, such as `<wa-icon>`, placed before the label.
|
||||
* @slot end - An element, such as `<wa-icon>`, placed after the label.
|
||||
*
|
||||
* @cssproperty --background-color-current - The current option's background color.
|
||||
* @cssproperty --background-color-hover - The options's background color on hover.
|
||||
@@ -26,8 +26,8 @@ import styles from './option.css';
|
||||
*
|
||||
* @csspart checked-icon - The checked icon, a `<wa-icon>` element.
|
||||
* @csspart label - The option's label.
|
||||
* @csspart prefix - The container that wraps the prefix.
|
||||
* @csspart suffix - The container that wraps the suffix.
|
||||
* @csspart start - The container that wraps the `start` slot.
|
||||
* @csspart end - The container that wraps the `end` slot.
|
||||
*
|
||||
* @cssstate current - The user has keyed into the option, but hasn't selected it yet (shows a highlight)
|
||||
* @cssstate selected - The option is selected and has aria-selected="true"
|
||||
@@ -192,9 +192,9 @@ export default class WaOption extends WebAwesomeElement {
|
||||
variant="solid"
|
||||
aria-hidden="true"
|
||||
></wa-icon>
|
||||
<slot part="prefix" name="prefix" class="prefix"></slot>
|
||||
<slot part="start" name="start" class="start"></slot>
|
||||
<slot part="label" class="label" @slotchange=${this.handleDefaultSlotChange}></slot>
|
||||
<slot part="suffix" name="suffix" class="suffix"></slot>
|
||||
<slot part="end" name="end" class="end"></slot>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,8 +171,10 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -274,8 +276,10 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
@@ -101,7 +101,9 @@ export default class WaRating extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
this.setValue(this.getValueFromMousePosition(event));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
|
||||
private setValue(newValue: number) {
|
||||
@@ -145,7 +147,9 @@ export default class WaRating extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +182,9 @@ export default class WaRating extends WebAwesomeElement {
|
||||
private handleTouchEnd(event: TouchEvent) {
|
||||
this.isHovering = false;
|
||||
this.setValue(this.hoverValue);
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
|
||||
// Prevent click on mobile devices
|
||||
event.preventDefault();
|
||||
|
||||
@@ -158,25 +158,25 @@ label:has(select),
|
||||
}
|
||||
}
|
||||
|
||||
/* Prefix and Suffix */
|
||||
/* Start and End */
|
||||
|
||||
.prefix,
|
||||
.suffix {
|
||||
.start,
|
||||
.end {
|
||||
flex: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--wa-color-neutral-on-quiet);
|
||||
}
|
||||
|
||||
.suffix::slotted(*) {
|
||||
.end::slotted(*) {
|
||||
margin-inline-start: var(--wa-form-control-padding-inline);
|
||||
}
|
||||
|
||||
.prefix::slotted(*) {
|
||||
.start::slotted(*) {
|
||||
margin-inline-end: var(--wa-form-control-padding-inline);
|
||||
}
|
||||
|
||||
:host([multiple]) .prefix::slotted(*) {
|
||||
:host([multiple]) .start::slotted(*) {
|
||||
margin-inline: var(--wa-form-control-padding-inline);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ import styles from './select.css';
|
||||
*
|
||||
* @slot - The listbox options. Must be `<wa-option>` elements. You can use `<wa-divider>` to group items visually.
|
||||
* @slot label - The input's label. Alternatively, you can use the `label` attribute.
|
||||
* @slot prefix - Used to prepend a presentational icon or similar element to the combobox.
|
||||
* @slot suffix - Used to append a presentational icon or similar element to the combobox.
|
||||
* @slot start - An element, such as `<wa-icon>`, placed at the start of the combobox.
|
||||
* @slot end - An element, such as `<wa-icon>`, placed at the end of the combobox.
|
||||
* @slot clear-icon - An icon to use in lieu of the default clear icon.
|
||||
* @slot expand-icon - The icon to show when the control is expanded and collapsed. Rotates on open and close.
|
||||
* @slot hint - Text that describes how to use the input. Alternatively, you can use the `hint` attribute.
|
||||
@@ -62,9 +62,9 @@ import styles from './select.css';
|
||||
* @csspart form-control-label - The label's wrapper.
|
||||
* @csspart form-control-input - The select's wrapper.
|
||||
* @csspart hint - The hint's wrapper.
|
||||
* @csspart combobox - The container the wraps the prefix, suffix, combobox, clear icon, and expand button.
|
||||
* @csspart prefix - The container that wraps the prefix slot.
|
||||
* @csspart suffix - The container that wraps the suffix slot.
|
||||
* @csspart combobox - The container the wraps the start, end, value, clear icon, and expand button.
|
||||
* @csspart start - The container that wraps the `start` slot.
|
||||
* @csspart end - The container that wraps the `end` slot.
|
||||
* @csspart display-input - The element that displays the selected option's label, an `<input>` element.
|
||||
* @csspart listbox - The listbox container where options are slotted.
|
||||
* @csspart tags - The container that houses option tags when `multiselect` is used.
|
||||
@@ -381,7 +381,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
// Emit after updating
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
|
||||
@@ -511,7 +511,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
// Emit after update
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new WaClearEvent());
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
@@ -542,7 +542,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
if (this.value !== oldValue) {
|
||||
// Emit after updating
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
@@ -600,7 +600,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
// Emit after updating
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
@@ -900,7 +900,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
@keydown=${this.handleComboboxKeyDown}
|
||||
@mousedown=${this.handleComboboxMouseDown}
|
||||
>
|
||||
<slot part="prefix" name="prefix" class="prefix"></slot>
|
||||
<slot part="start" name="start" class="start"></slot>
|
||||
|
||||
<input
|
||||
part="display-input"
|
||||
@@ -962,7 +962,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
`
|
||||
: ''}
|
||||
|
||||
<slot name="suffix" part="suffix" class="suffix"></slot>
|
||||
<slot name="end" part="end" class="end"></slot>
|
||||
|
||||
<slot name="expand-icon" part="expand-icon" class="expand-icon">
|
||||
<wa-icon library="system" name="chevron-down" variant="solid"></wa-icon>
|
||||
|
||||
@@ -228,7 +228,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
|
||||
},
|
||||
stop: () => {
|
||||
if (this.minValue !== this.valueWhenDraggingStarted) {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
this.hasInteracted = true;
|
||||
}
|
||||
this.hideRangeTooltips();
|
||||
@@ -251,7 +253,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
|
||||
},
|
||||
stop: () => {
|
||||
if (this.maxValue !== this.valueWhenDraggingStarted) {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
this.hasInteracted = true;
|
||||
}
|
||||
this.hideRangeTooltips();
|
||||
@@ -321,7 +325,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
|
||||
if (this.activeThumb) {
|
||||
const currentValue = this.activeThumb === 'min' ? this.minValue : this.maxValue;
|
||||
if (currentValue !== this.valueWhenDraggingStarted) {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
this.hasInteracted = true;
|
||||
}
|
||||
}
|
||||
@@ -346,7 +352,10 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
|
||||
},
|
||||
stop: () => {
|
||||
if (this.value !== this.valueWhenDraggingStarted) {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
|
||||
this.hasInteracted = true;
|
||||
}
|
||||
this.hideTooltip();
|
||||
@@ -602,8 +611,10 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
// Dispatch events
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
this.hasInteracted = true;
|
||||
}
|
||||
|
||||
@@ -625,7 +636,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
// Dispatch input events when the value changes by dragging
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,8 +671,10 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
// Dispatch input events
|
||||
if (oldValue !== (thumb === 'min' ? this.minValue : this.maxValue)) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.updateFormValue();
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,22 +117,29 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement {
|
||||
private handleClick() {
|
||||
this.hasInteracted = true;
|
||||
this.checked = !this.checked;
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault();
|
||||
this.checked = false;
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowRight') {
|
||||
event.preventDefault();
|
||||
this.checked = true;
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
||||
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,13 +206,14 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
|
||||
this.valueHasChanged = true;
|
||||
this.value = this.input.value;
|
||||
this.setTextareaDimensions();
|
||||
this.relayNativeEvent(event, { bubbles: true, composed: true });
|
||||
this.checkValidity();
|
||||
this.relayNativeEvent(event, { bubbles: true, composed: true });
|
||||
}
|
||||
|
||||
private handleInput() {
|
||||
private handleInput(event: InputEvent) {
|
||||
this.valueHasChanged = true;
|
||||
this.value = this.input.value;
|
||||
this.relayNativeEvent(event, { bubbles: true, composed: true });
|
||||
}
|
||||
|
||||
private setTextareaDimensions() {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type WaMenuItem from '../components/menu-item/menu-item.js';
|
||||
|
||||
export class WaSelectEvent extends Event {
|
||||
readonly detail;
|
||||
|
||||
@@ -10,7 +8,7 @@ export class WaSelectEvent extends Event {
|
||||
}
|
||||
|
||||
interface WaSelectEventDetail {
|
||||
item: WaMenuItem;
|
||||
item: Element;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -14,6 +14,9 @@ export function animateWithClass(el: Element, className: string) {
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
|
||||
if (el.classList.contains(className)) {
|
||||
return;
|
||||
}
|
||||
el.classList.remove(className);
|
||||
el.classList.add(className);
|
||||
|
||||
|
||||
@@ -16,23 +16,6 @@
|
||||
--wa-color-brand-10: var(--wa-color-blue-10);
|
||||
--wa-color-brand-05: var(--wa-color-blue-05);
|
||||
--wa-color-brand: var(--wa-color-blue);
|
||||
|
||||
--wa-color-brand-lt-50: var(--wa-color-blue-lt-50);
|
||||
--wa-color-brand-gte-50: var(--wa-color-blue-gte-50);
|
||||
|
||||
--wa-color-brand-lt-60: var(--wa-color-blue-lt-60);
|
||||
--wa-color-brand-gte-60: var(--wa-color-blue-gte-60);
|
||||
|
||||
--wa-color-brand-lt-70: var(--wa-color-blue-lt-70);
|
||||
--wa-color-brand-gte-70: var(--wa-color-blue-gte-70);
|
||||
|
||||
--wa-color-brand-max-50: var(--wa-color-blue-max-50);
|
||||
--wa-color-brand-min-50: var(--wa-color-blue-min-50);
|
||||
--wa-color-brand-max-60: var(--wa-color-blue-max-60);
|
||||
--wa-color-brand-min-60: var(--wa-color-blue-min-60);
|
||||
--wa-color-brand-max-70: var(--wa-color-blue-max-70);
|
||||
--wa-color-brand-min-70: var(--wa-color-blue-min-70);
|
||||
|
||||
--wa-color-brand-on: var(--wa-color-blue-on);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,23 +16,6 @@
|
||||
--wa-color-brand-10: var(--wa-color-cyan-10);
|
||||
--wa-color-brand-05: var(--wa-color-cyan-05);
|
||||
--wa-color-brand: var(--wa-color-cyan);
|
||||
|
||||
--wa-color-brand-lt-50: var(--wa-color-cyan-lt-50);
|
||||
--wa-color-brand-gte-50: var(--wa-color-cyan-gte-50);
|
||||
|
||||
--wa-color-brand-lt-60: var(--wa-color-cyan-lt-60);
|
||||
--wa-color-brand-gte-60: var(--wa-color-cyan-gte-60);
|
||||
|
||||
--wa-color-brand-lt-70: var(--wa-color-cyan-lt-70);
|
||||
--wa-color-brand-gte-70: var(--wa-color-cyan-gte-70);
|
||||
|
||||
--wa-color-brand-max-50: var(--wa-color-cyan-max-50);
|
||||
--wa-color-brand-min-50: var(--wa-color-cyan-min-50);
|
||||
--wa-color-brand-max-60: var(--wa-color-cyan-max-60);
|
||||
--wa-color-brand-min-60: var(--wa-color-cyan-min-60);
|
||||
--wa-color-brand-max-70: var(--wa-color-cyan-max-70);
|
||||
--wa-color-brand-min-70: var(--wa-color-cyan-min-70);
|
||||
|
||||
--wa-color-brand-on: var(--wa-color-cyan-on);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,23 +16,6 @@
|
||||
--wa-color-brand-10: var(--wa-color-gray-10);
|
||||
--wa-color-brand-05: var(--wa-color-gray-05);
|
||||
--wa-color-brand: var(--wa-color-gray);
|
||||
|
||||
--wa-color-brand-lt-50: var(--wa-color-gray-lt-50);
|
||||
--wa-color-brand-gte-50: var(--wa-color-gray-gte-50);
|
||||
|
||||
--wa-color-brand-lt-60: var(--wa-color-gray-lt-60);
|
||||
--wa-color-brand-gte-60: var(--wa-color-gray-gte-60);
|
||||
|
||||
--wa-color-brand-lt-70: var(--wa-color-gray-lt-70);
|
||||
--wa-color-brand-gte-70: var(--wa-color-gray-gte-70);
|
||||
|
||||
--wa-color-brand-max-50: var(--wa-color-gray-max-50);
|
||||
--wa-color-brand-min-50: var(--wa-color-gray-min-50);
|
||||
--wa-color-brand-max-60: var(--wa-color-gray-max-60);
|
||||
--wa-color-brand-min-60: var(--wa-color-gray-min-60);
|
||||
--wa-color-brand-max-70: var(--wa-color-gray-max-70);
|
||||
--wa-color-brand-min-70: var(--wa-color-gray-min-70);
|
||||
|
||||
--wa-color-brand-on: var(--wa-color-gray-on);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,23 +16,6 @@
|
||||
--wa-color-brand-10: var(--wa-color-green-10);
|
||||
--wa-color-brand-05: var(--wa-color-green-05);
|
||||
--wa-color-brand: var(--wa-color-green);
|
||||
|
||||
--wa-color-brand-lt-50: var(--wa-color-green-lt-50);
|
||||
--wa-color-brand-gte-50: var(--wa-color-green-gte-50);
|
||||
|
||||
--wa-color-brand-lt-60: var(--wa-color-green-lt-60);
|
||||
--wa-color-brand-gte-60: var(--wa-color-green-gte-60);
|
||||
|
||||
--wa-color-brand-lt-70: var(--wa-color-green-lt-70);
|
||||
--wa-color-brand-gte-70: var(--wa-color-green-gte-70);
|
||||
|
||||
--wa-color-brand-max-50: var(--wa-color-green-max-50);
|
||||
--wa-color-brand-min-50: var(--wa-color-green-min-50);
|
||||
--wa-color-brand-max-60: var(--wa-color-green-max-60);
|
||||
--wa-color-brand-min-60: var(--wa-color-green-min-60);
|
||||
--wa-color-brand-max-70: var(--wa-color-green-max-70);
|
||||
--wa-color-brand-min-70: var(--wa-color-green-min-70);
|
||||
|
||||
--wa-color-brand-on: var(--wa-color-green-on);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,23 +16,6 @@
|
||||
--wa-color-brand-10: var(--wa-color-indigo-10);
|
||||
--wa-color-brand-05: var(--wa-color-indigo-05);
|
||||
--wa-color-brand: var(--wa-color-indigo);
|
||||
|
||||
--wa-color-brand-lt-50: var(--wa-color-indigo-lt-50);
|
||||
--wa-color-brand-gte-50: var(--wa-color-indigo-gte-50);
|
||||
|
||||
--wa-color-brand-lt-60: var(--wa-color-indigo-lt-60);
|
||||
--wa-color-brand-gte-60: var(--wa-color-indigo-gte-60);
|
||||
|
||||
--wa-color-brand-lt-70: var(--wa-color-indigo-lt-70);
|
||||
--wa-color-brand-gte-70: var(--wa-color-indigo-gte-70);
|
||||
|
||||
--wa-color-brand-max-50: var(--wa-color-indigo-max-50);
|
||||
--wa-color-brand-min-50: var(--wa-color-indigo-min-50);
|
||||
--wa-color-brand-max-60: var(--wa-color-indigo-max-60);
|
||||
--wa-color-brand-min-60: var(--wa-color-indigo-min-60);
|
||||
--wa-color-brand-max-70: var(--wa-color-indigo-max-70);
|
||||
--wa-color-brand-min-70: var(--wa-color-indigo-min-70);
|
||||
|
||||
--wa-color-brand-on: var(--wa-color-indigo-on);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user