Compare commits

..

11 Commits

Author SHA1 Message Date
Cory LaViska
b2a9c33ee6 remove redundant examples; closes #1041 2025-06-23 10:24:20 -04:00
Konnor Rogers
d20945d78b increase breakpoint on wa-page (#1060) 2025-06-17 11:45:38 -04:00
Konnor Rogers
07327be95e input / change always bubble now (#1057)
* make sure relayNativeEvent is synchronous

* prettier

* changelog entry

* fix event
2025-06-13 13:19:47 -04:00
Cory LaViska
b99d7771dc move pro themes to pro (#1054) 2025-06-11 12:15:52 -04:00
Lindsay M
10cc9bdc68 Change prefix and suffix to start and end (#1047)
* change `prefix` > `start` and `suffix` > `end` (breadcrumb item)

* fix formatting

* change `prefix` > `start` and `suffix` > `end` (button)

* change `prefix` > `start` and `suffix` > `end` (input)

* change `prefix` > `start` and `suffix` > `end` (select)

* replace leftover `prefix` instances with `start`

* replace leftover `suffix` instances with `end`

* update changelog

* remove `slot="start|end"` examples from unsupported components
2025-06-11 11:36:50 -04:00
Lindsay M
bc598dad92 Consolidate theme files (#1053)
* consolidate theme files

* move dimension to its own layer

* touch up

* fix `.wa-invert`

* fix selectors and missing fonts

* fix selectors (for real this time)
2025-06-11 11:24:40 -04:00
Lindsay M
ff61ac002f Remove clamped color tokens (#1050) 2025-06-10 15:44:33 -04:00
Konnor Rogers
63ec9d5bc1 Try with PAT (#1049)
* Try with PAT

* Try with PAT
2025-06-10 13:59:52 -04:00
Kelsey Jackson
555327b2fc Kj/layout patterns (#1013)
* add button for drawer example

* scaffolding

* building initial page

* loading blank

* committing to pull down changes

* committing to merge

* trying to sync with next

* drawer issue

* added content to drawer

* added checkout page

* added checkout page

* committing to merge

* committing to merge

* made changes to sidebar

* syncing with repo

* syncing with repo

* update sidebar again

---------

Co-authored-by: Cory LaViska <cory@abeautifulsite.net>
2025-06-09 12:17:14 -05:00
Cory LaViska
15c6eaec90 Dropdown rework (#1039)
* use wa-popup in color picker

* remove test

* remove old dropdown, menu, menu item, menu label

* remove old dropdown

* rework dropdown + dropdown item

* update examples

* add hide duration

* update jsdoc

* add event

* add size

* add size

* fix docs

* remove old test; fix types

* add submenu to example

* adapt for <wa-popup>

* remove state

* fix dropdown stuff (#1044)

* fix dropdown stuff

* prettier

* get typescript to give a real error

* remove unnecessary checks

* prettier

* remove old tag

* convert old dropdowns to new syntax

* update example

* update styles

---------

Co-authored-by: Konnor Rogers <konnor5456@gmail.com>
Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-06-09 11:19:34 -04:00
Lindsay M
6f856fbd89 Native styles cleanup (#1043)
* tidy and touch up content styles

* finish touching up inline text styles

* move styles for docs code blocks from `native.css` to appropriate files

* tidy and touch up list and table styles

* tidy and touch up details styles

* tidy up dialog styles

* fix bloated spacing in dialog and drawer

* add fieldset, refactor radio and checkbox styles

* refactor input, textarea, and select styles

* refactor range styles to match new `<wa-slider>`

* readability improvements

* touch up theme overrides

* fix Matter checkbox and radio hover styles
2025-06-09 11:18:37 -04:00
203 changed files with 4470 additions and 8590 deletions

View File

@@ -20,7 +20,7 @@
</head> </head>
<body class="layout-{{ layout | stripExtension }}{{ ' page-wide' if wide }}"> <body class="layout-{{ layout | stripExtension }}{{ ' page-wide' if wide }}">
<!-- use view="desktop" as default to reduce layout jank on desktop site. --> <!-- 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"> <header slot="header" class="wa-split">
{# Logo #} {# Logo #}
<div id="docs-branding"> <div id="docs-branding">
@@ -46,9 +46,9 @@
{# Search #} {# Search #}
<wa-button id="search-trigger" appearance="outlined" size="small" data-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 Search
<kbd slot="suffix" class="wa-desktop-only">/</kbd> <kbd slot="end" class="wa-desktop-only">/</kbd>
</wa-button> </wa-button>
{# Login #} {# Login #}

View File

@@ -1,19 +1,19 @@
{# Color scheme selector #} {# Color scheme selector #}
<wa-select class="color-scheme-selector" appearance="filled" size="small" value="auto" pill title="Press \ to toggle"> <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-light" slot="start" name="sun" variant="regular"></wa-icon>
<wa-icon class="only-dark" slot="prefix" name="moon" variant="regular"></wa-icon> <wa-icon class="only-dark" slot="start" name="moon" variant="regular"></wa-icon>
<wa-option value="light"> <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 Light
</wa-option> </wa-option>
<wa-option value="dark"> <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 Dark
</wa-option> </wa-option>
<wa-divider></wa-divider> <wa-divider></wa-divider>
<wa-option value="auto"> <wa-option value="auto">
<wa-icon class="only-light" slot="prefix" name="sun" variant="regular"></wa-icon> <wa-icon class="only-light" slot="start" name="sun" variant="regular"></wa-icon>
<wa-icon class="only-dark" slot="prefix" name="moon" variant="regular"></wa-icon> <wa-icon class="only-dark" slot="start" name="moon" variant="regular"></wa-icon>
System System
</wa-option> </wa-option>
</wa-select> </wa-select>

View File

@@ -1,6 +1,6 @@
{# Preset theme selector #} {# Preset theme selector #}
<wa-select appearance="filled" size="small" value="default" pill class="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 %} {% for theme in collections.theme | sort %}
<wa-option value="{{ theme.page.fileSlug }}"> <wa-option value="{{ theme.page.fileSlug }}">
{{ theme.data.title }} {{ theme.data.title }}

View File

@@ -24,7 +24,7 @@
aria-haspopup="listbox" aria-haspopup="listbox"
aria-activedescendant aria-activedescendant
> >
<wa-icon slot="prefix" name="search"></wa-icon> <wa-icon slot="start" name="search"></wa-icon>
</wa-input> </wa-input>
</div> </div>
</header> </header>

View File

@@ -121,20 +121,18 @@
<li><a href="/docs/components/dialog/">Dialog</a></li> <li><a href="/docs/components/dialog/">Dialog</a></li>
<li><a href="/docs/components/divider/">Divider</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/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-bytes/">Format Bytes</a></li>
<li><a href="/docs/components/format-date/">Format Date</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/format-number/">Format Number</a></li>
<li><a href="/docs/components/icon/">Icon</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/include/">Include</a></li>
<li><a href="/docs/components/input/">Input</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/mutation-observer/">Mutation Observer</a></li>
<li><a href="/docs/components/popover/">Popover</a></li> <li><a href="/docs/components/popover/">Popover</a></li>
<li><a href="/docs/components/popup/">Popup</a></li> <li><a href="/docs/components/popup/">Popup</a></li>

View File

@@ -47,7 +47,7 @@
</div> </div>
<span class="wa-caption-m">Shipping and taxes calculated at checkout.</span> <span class="wa-caption-m">Shipping and taxes calculated at checkout.</span>
<wa-button tabindex="-1" variant="brand"> <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 Checkout
</wa-button> </wa-button>
</div> </div>
@@ -62,10 +62,10 @@
<div class="wa-stack"> <div class="wa-stack">
<h3 class="wa-heading-m">Sign In</h3> <h3 class="wa-heading-m">Sign In</h3>
<wa-input tabindex="-1" label="Email" placeholder="ddjarin@mandalore.gov"> <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>
<wa-input tabindex="-1" label="Password" type="password"> <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-input>
<wa-button tabindex="-1" variant="brand">Sign In</wa-button> <wa-button tabindex="-1" variant="brand">Sign In</wa-button>
<a href="#" tabindex="-1" class="wa-body-s">I forgot my password</a> <a href="#" tabindex="-1" class="wa-body-s">I forgot my password</a>
@@ -244,11 +244,11 @@
</div> </div>
<div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;"> <div slot="footer" class="wa-grid wa-gap-xs" style="--min-column-size: 10ch;">
<wa-button appearance="outlined" tabindex="-1"> <wa-button appearance="outlined" tabindex="-1">
<wa-icon slot="prefix" name="at"></wa-icon> <wa-icon slot="start" name="at"></wa-icon>
Email Email
</wa-button> </wa-button>
<wa-button appearance="outlined" tabindex="-1"> <wa-button appearance="outlined" tabindex="-1">
<wa-icon slot="prefix" name="phone"></wa-icon> <wa-icon slot="start" name="phone"></wa-icon>
Phone Phone
</wa-button> </wa-button>
</div> </div>
@@ -268,11 +268,9 @@
<wa-button id="more-actions-2" slot="trigger" appearance="plain" size="small" tabindex="-1"> <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-icon name="ellipsis-vertical" label="View menu"></wa-icon>
</wa-button> </wa-button>
<wa-menu> <wa-dropdown-item>Copy link</wa-dropdown-item>
<wa-menu-item>Copy link</wa-menu-item> <wa-dropdown-item>Rename</wa-dropdown-item>
<wa-menu-item>Rename</wa-menu-item> <wa-dropdown-item>Move to trash</wa-dropdown-item>
<wa-menu-item>Move to trash</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
<wa-tooltip for="more-actions-2">View menu</wa-tooltip> <wa-tooltip for="more-actions-2">View menu</wa-tooltip>
</div> </div>

View File

@@ -259,19 +259,15 @@
<td> <td>
<wa-dropdown size="small"> <wa-dropdown size="small">
<wa-button slot="trigger" caret>Dropdown</wa-button> <wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu> <wa-dropdown-item>Menu Item 1</wa-dropdown-item>
<wa-menu-item>Menu Item 1</wa-menu-item> <wa-dropdown-item>Menu Item 2</wa-dropdown-item>
<wa-menu-item>Menu Item 2</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</td> </td>
<td> <td>
<wa-dropdown class="wa-size-s"> <wa-dropdown class="wa-size-s">
<wa-button slot="trigger" caret>Dropdown</wa-button> <wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu> <wa-dropdown-item>Menu Item 1</wa-dropdown-item>
<wa-menu-item>Menu Item 1</wa-menu-item> <wa-dropdown-item>Menu Item 2</wa-dropdown-item>
<wa-menu-item>Menu Item 2</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</td> </td>
</tr> </tr>
@@ -280,19 +276,15 @@
<td> <td>
<wa-dropdown size="medium"> <wa-dropdown size="medium">
<wa-button slot="trigger" caret>Dropdown</wa-button> <wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu> <wa-dropdown-item>Menu Item 1</wa-dropdown-item>
<wa-menu-item>Menu Item 1</wa-menu-item> <wa-dropdown-item>Menu Item 2</wa-dropdown-item>
<wa-menu-item>Menu Item 2</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</td> </td>
<td> <td>
<wa-dropdown class="wa-size-m"> <wa-dropdown class="wa-size-m">
<wa-button slot="trigger" caret>Dropdown</wa-button> <wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu> <wa-dropdown-item>Menu Item 1</wa-dropdown-item>
<wa-menu-item>Menu Item 1</wa-menu-item> <wa-dropdown-item>Menu Item 2</wa-dropdown-item>
<wa-menu-item>Menu Item 2</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</td> </td>
</tr> </tr>
@@ -301,19 +293,15 @@
<td> <td>
<wa-dropdown size="large"> <wa-dropdown size="large">
<wa-button slot="trigger" caret>Dropdown</wa-button> <wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu> <wa-dropdown-item>Menu Item 1</wa-dropdown-item>
<wa-menu-item>Menu Item 1</wa-menu-item> <wa-dropdown-item>Menu Item 2</wa-dropdown-item>
<wa-menu-item>Menu Item 2</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</td> </td>
<td> <td>
<wa-dropdown class="wa-size-l"> <wa-dropdown class="wa-size-l">
<wa-button slot="trigger" caret>Dropdown</wa-button> <wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu> <wa-dropdown-item>Menu Item 1</wa-dropdown-item>
<wa-menu-item>Menu Item 1</wa-menu-item> <wa-dropdown-item>Menu Item 2</wa-dropdown-item>
<wa-menu-item>Menu Item 2</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</td> </td>
</tr> </tr>
@@ -322,66 +310,6 @@
</div> </div>
<wa-divider></wa-divider> <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> <h3>Input</h3>
<div class="table-scroll"> <div class="table-scroll">
@@ -799,4 +727,4 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@@ -8,7 +8,7 @@ layout: page-outline
<div id="block-filter"> <div id="block-filter">
<wa-input type="search" placeholder="Search {{ title }}" with-clear autofocus> <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> </wa-input>
</div> </div>

View File

@@ -72,7 +72,7 @@
</div> </div>
<wa-button @click="reset()" appearance="outlined" variant="danger" size="small"> <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 Reset
</wa-button> </wa-button>
</wa-callout> </wa-callout>

View File

@@ -24,12 +24,12 @@ if (location.pathname.endsWith('/custom/') && !location.search) {
<h4 slot="summary" data-no-anchor data-no-outline id="remix"> <h4 slot="summary" data-no-anchor data-no-outline id="remix">
<wa-icon name="arrows-rotate"></wa-icon> <wa-icon name="arrows-rotate"></wa-icon>
Remix this theme 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> <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> </h4>
<wa-select name="palette" label="Color palette" with-clear v-model="theme.palette"> <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"> <wa-option v-for="(palette, paletteId) in palettes" :label="palette.title" :value="paletteId === baseTheme.palette ? '' : paletteId">
<palette-card :palette="paletteId" size="small"> <palette-card :palette="paletteId" size="small">
<template #extra> <template #extra>
@@ -43,7 +43,7 @@ if (location.pathname.endsWith('/custom/') && !location.search) {
:values="hues"></color-select> :values="hues"></color-select>
<wa-select name="colors" class="theme-colors-select" label="Color contrast from…" value="" with-clear v-model="theme.colors"> <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"> <template v-for="(themeMeta, themeId) in themes">
<wa-option v-if="themeId !== 'custom'" :label="themeMeta.title" :value="themeId === computed.colors ? '' : themeId"> <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"> <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>
<wa-select name="typography" label="Typography from…" with-clear v-model="theme.typography"> <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"> <wa-option v-for="(themeMeta, themeId) in themes" :label="themeMeta.title" :value="themeId === theme.base ? '' : themeId">
<fonts-card :theme="themeId" size="small"> <fonts-card :theme="themeId" size="small">

View File

@@ -42,7 +42,7 @@
</wa-breadcrumb> </wa-breadcrumb>
</div> </div>
<wa-input id="search" class="wa-desktop-only" placeholder="Search" size="small" style="max-inline-size: 12rem"> <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> </wa-input>
</nav> </nav>
<nav slot="navigation-header"> <nav slot="navigation-header">

View File

@@ -19,7 +19,7 @@
<span class="wa-heading-m">radiogaga</span> <span class="wa-heading-m">radiogaga</span>
</div> </div>
<wa-input id="search-header" placeholder="Search" class="wa-desktop-only" style="max-inline-size: 100%"> <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> </wa-input>
<div class="wa-cluster"> <div class="wa-cluster">
<wa-button appearance="outlined">Log In</wa-button> <wa-button appearance="outlined">Log In</wa-button>
@@ -28,7 +28,7 @@
</header> </header>
<div slot="navigation-header" class="wa-split"> <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-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> </wa-input>
<div class="wa-split"> <div class="wa-split">
<h2 class="wa-heading-s">For You</h2> <h2 class="wa-heading-s">For You</h2>

View File

@@ -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); background-color: var(--wa-color-gray-20);
color: white; color: white;
@@ -8,6 +9,7 @@ pre {
background-color: var(--wa-color-surface-lowered); background-color: var(--wa-color-surface-lowered);
} }
} }
.code-comment, .code-comment,
.code-prolog, .code-prolog,
.code-doctype, .code-doctype,

View File

@@ -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 { wa-copy-button.copy-button {
--background-color: var(--wa-color-gray-20); --background-color: var(--wa-color-gray-20);
--background-color-hover: color-mix(in oklab, var(--background-color), white 5%); --background-color-hover: color-mix(in oklab, var(--background-color), white 5%);

View File

@@ -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 */ /* Main content */
wa-page > main { wa-page > main {
max-width: 80ch; max-width: 80ch;

View File

@@ -185,11 +185,11 @@ html.wa-theme-brutalist .preview-container {
text-align: right; text-align: right;
} }
.product-card wa-button::part(prefix) { .product-card wa-button::part(start) {
padding-inline-start: var(--wa-space-xs); 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); padding-inline-end: var(--wa-space-xs);
} }

View File

@@ -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-badge variant="danger" pill>6</wa-badge>
</wa-button> </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>

View File

@@ -5,17 +5,4 @@ tags: component
parent: breadcrumb parent: breadcrumb
--- ---
```html {.example} 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.
<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).
:::

View File

@@ -36,32 +36,20 @@ For websites, you'll probably want to use links instead. You can make any breadc
</wa-breadcrumb> </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} ```html {.example}
<wa-breadcrumb> <wa-breadcrumb>
<wa-breadcrumb-item> <wa-breadcrumb-item>
<wa-icon slot="prefix" name="house" variant="solid"></wa-icon> <wa-icon slot="start" name="house"></wa-icon>
Home Home
</wa-breadcrumb-item> </wa-breadcrumb-item>
<wa-breadcrumb-item>Articles</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> <wa-breadcrumb-item>
Security <wa-icon slot="end" name="tree-palm"></wa-icon>
<wa-icon slot="suffix" name="shield" variant="solid"></wa-icon> Traveling
</wa-breadcrumb-item> </wa-breadcrumb-item>
</wa-breadcrumb> </wa-breadcrumb>
``` ```
@@ -99,7 +87,7 @@ Use the `separator` slot to change the separator that goes between breadcrumb it
### Custom Colors ### 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} ```html {.example}
<style> <style>
@@ -112,14 +100,14 @@ Breadcrumb labels match the color set on `<wa-breadcrumb-item>`. Prefixes, suffi
.redcrumbs wa-breadcrumb-item::part(separator) { .redcrumbs wa-breadcrumb-item::part(separator) {
color: pink; color: pink;
} }
.redcrumbs wa-breadcrumb-item::part(prefix), .redcrumbs wa-breadcrumb-item::part(start),
.redcrumbs wa-breadcrumb-item::part(suffix) { .redcrumbs wa-breadcrumb-item::part(end) {
color: currentColor; color: currentColor;
} }
</style> </style>
<wa-breadcrumb class="redcrumbs"> <wa-breadcrumb class="redcrumbs">
<wa-breadcrumb-item> <wa-breadcrumb-item>
<wa-icon slot="prefix" name="house" variant="solid"></wa-icon> <wa-icon slot="start" name="house" variant="solid"></wa-icon>
Home Home
</wa-breadcrumb-item> </wa-breadcrumb-item>
<wa-breadcrumb-item>Articles</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-button slot="trigger" size="small" appearance="filled" pill>
<wa-icon label="More options" name="ellipsis" variant="solid"></wa-icon> <wa-icon label="More options" name="ellipsis" variant="solid"></wa-icon>
</wa-button> </wa-button>
<wa-menu> <wa-dropdown-item type="checkbox" checked>Web Design</wa-dropdown-item>
<wa-menu-item type="checkbox" checked>Web Design</wa-menu-item> <wa-dropdown-item type="checkbox">Web Development</wa-dropdown-item>
<wa-menu-item type="checkbox">Web Development</wa-menu-item> <wa-dropdown-item type="checkbox">Marketing</wa-dropdown-item>
<wa-menu-item type="checkbox">Marketing</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</wa-breadcrumb-item> </wa-breadcrumb-item>
<wa-breadcrumb-item>Our Services</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> </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} ```html {.example}
<wa-breadcrumb> <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>Digital Media</wa-breadcrumb-item>
<wa-breadcrumb-item> <wa-breadcrumb-item>
Web Design Web Design
<wa-dropdown slot="suffix"> <wa-dropdown slot="end">
<wa-button slot="trigger" size="small" appearance="filled" pill> <wa-button slot="trigger" size="small" appearance="filled" pill>
<wa-icon label="More options" name="ellipsis" variant="solid"></wa-icon> <wa-icon label="More options" name="ellipsis" variant="solid"></wa-icon>
</wa-button> </wa-button>
<wa-menu> <wa-dropdown-item type="checkbox" checked>Web Design</wa-dropdown-item>
<wa-menu-item type="checkbox" checked>Web Design</wa-menu-item> <wa-dropdown-item type="checkbox">Web Development</wa-dropdown-item>
<wa-menu-item type="checkbox">Web Development</wa-menu-item> <wa-dropdown-item type="checkbox">Marketing</wa-dropdown-item>
<wa-menu-item type="checkbox">Marketing</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</wa-breadcrumb-item> </wa-breadcrumb-item>
</wa-breadcrumb> </wa-breadcrumb>
```

View File

@@ -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. 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. Set the `orientation` attribute to `vertical` to make a vertical button group.
```html {.example} ```html {.example}
<wa-button-group orientation="vertical" label="Options" style="max-width: 120px;"> <wa-button-group orientation="vertical" label="Options" style="max-width: 120px;">
<wa-button> <wa-button>
<wa-icon slot="prefix" name="plus"></wa-icon> <wa-icon slot="start" name="plus"></wa-icon>
New New
</wa-button> </wa-button>
<wa-button> <wa-button>
<wa-icon slot="prefix" name="folder-open"></wa-icon> <wa-icon slot="start" name="folder-open"></wa-icon>
Open Open
</wa-button> </wa-button>
<wa-button> <wa-button>
<wa-icon slot="prefix" name="save"></wa-icon> <wa-icon slot="start" name="save"></wa-icon>
Save Save
</wa-button> </wa-button>
<wa-button> <wa-button>
<wa-icon slot="prefix" name="print"></wa-icon> <wa-icon slot="start" name="print"></wa-icon>
Print Print
</wa-button> </wa-button>
</wa-button-group> </wa-button-group>
@@ -166,11 +166,9 @@ Dropdowns can be placed into button groups.
<wa-button>Button</wa-button> <wa-button>Button</wa-button>
<wa-dropdown> <wa-dropdown>
<wa-button slot="trigger" caret>Dropdown</wa-button> <wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu> <wa-dropdown-item>Item 1</wa-dropdown-item>
<wa-menu-item>Item 1</wa-menu-item> <wa-dropdown-item>Item 2</wa-dropdown-item>
<wa-menu-item>Item 2</wa-menu-item> <wa-dropdown-item>Item 3</wa-dropdown-item>
<wa-menu-item>Item 3</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
<wa-button>Button</wa-button> <wa-button>Button</wa-button>
</wa-button-group> </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-button slot="trigger" variant="brand">
<wa-icon name="chevron-down" label="More options"></wa-icon> <wa-icon name="chevron-down" label="More options"></wa-icon>
</wa-button> </wa-button>
<wa-menu> <wa-dropdown-item>Save</wa-dropdown-item>
<wa-menu-item>Save</wa-menu-item> <wa-dropdown-item>Save as&hellip;</wa-dropdown-item>
<wa-menu-item>Save as&hellip;</wa-menu-item> <wa-dropdown-item>Save all</wa-dropdown-item>
<wa-menu-item>Save all</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</wa-button-group> </wa-button-group>
``` ```
@@ -230,9 +226,15 @@ Create interactive toolbars with button groups.
</wa-button-group> </wa-button-group>
<wa-button-group label="Alignment"> <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-left">
<wa-button id="button-align-center"><wa-icon name="align-center" variant="solid" label="Align Center"></wa-icon></wa-button> <wa-icon name="align-left" variant="solid" label="Align Left"></wa-icon>
<wa-button id="button-align-right"><wa-icon name="align-right" variant="solid" label="Align Right"></wa-icon></wa-button> </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> </wa-button-group>
</div> </div>

View File

@@ -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> <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} ```html {.example}
<wa-button size="small"> <wa-button size="small">
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon> <wa-icon slot="start" name="gear"></wa-icon>
Settings Settings
</wa-button> </wa-button>
<wa-button size="small"> <wa-button size="small">
<wa-icon slot="suffix" name="undo" variant="solid"></wa-icon> <wa-icon slot="end" name="undo"></wa-icon>
Refresh Refresh
</wa-button> </wa-button>
<wa-button size="small"> <wa-button size="small">
<wa-icon slot="prefix" name="link" variant="solid"></wa-icon> <wa-icon slot="start" name="link"></wa-icon>
<wa-icon slot="suffix" name="arrow-up-right-from-square" variant="solid"></wa-icon> <wa-icon slot="end" name="arrow-up-right-from-square"></wa-icon>
Open Open
</wa-button> </wa-button>
<br /><br /> <br /><br />
<wa-button> <wa-button>
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon> <wa-icon slot="start" name="gear"></wa-icon>
Settings Settings
</wa-button> </wa-button>
<wa-button> <wa-button>
<wa-icon slot="suffix" name="undo" variant="solid"></wa-icon> <wa-icon slot="end" name="undo"></wa-icon>
Refresh Refresh
</wa-button> </wa-button>
<wa-button> <wa-button>
<wa-icon slot="prefix" name="link" variant="solid"></wa-icon> <wa-icon slot="start" name="link"></wa-icon>
<wa-icon slot="suffix" name="arrow-up-right-from-square" variant="solid"></wa-icon> <wa-icon slot="end" name="arrow-up-right-from-square"></wa-icon>
Open Open
</wa-button> </wa-button>
<br /><br /> <br /><br />
<wa-button size="large"> <wa-button size="large">
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon> <wa-icon slot="start" name="gear"></wa-icon>
Settings Settings
</wa-button> </wa-button>
<wa-button size="large"> <wa-button size="large">
<wa-icon slot="suffix" name="undo" variant="solid"></wa-icon> <wa-icon slot="end" name="undo"></wa-icon>
Refresh Refresh
</wa-button> </wa-button>
<wa-button size="large"> <wa-button size="large">
<wa-icon slot="prefix" name="link" variant="solid"></wa-icon> <wa-icon slot="start" name="link"></wa-icon>
<wa-icon slot="suffix" name="arrow-up-right-from-square" variant="solid"></wa-icon> <wa-icon slot="end" name="arrow-up-right-from-square"></wa-icon>
Open Open
</wa-button> </wa-button>
``` ```

View File

@@ -6,41 +6,4 @@ parent: carousel
icon: carousel-item icon: carousel-item
--- ---
```html {.example} 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.
<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).
:::

View File

@@ -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. Use dividers in [menus](/docs/components/menu) to visually group menu items.
```html {.example} ```html {.example}
<wa-menu style="max-width: 200px;"> <wa-dropdown style="max-width: 200px;">
<wa-menu-item value="1">Option 1</wa-menu-item> <wa-button slot="trigger" caret>Menu</wa-button>
<wa-menu-item value="2">Option 2</wa-menu-item> <wa-dropdown-item value="1">Option 1</wa-dropdown-item>
<wa-menu-item value="3">Option 3</wa-menu-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-divider></wa-divider>
<wa-menu-item value="4">Option 4</wa-menu-item> <wa-dropdown-item value="4">Option 4</wa-dropdown-item>
<wa-menu-item value="5">Option 5</wa-menu-item> <wa-dropdown-item value="5">Option 5</wa-dropdown-item>
<wa-menu-item value="6">Option 6</wa-menu-item> <wa-dropdown-item value="6">Option 6</wa-dropdown-item>
</wa-menu> </wa-dropdown>
``` ```

View File

@@ -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.

View File

@@ -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 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} ```html {.example}
<wa-dropdown> <wa-dropdown>
<wa-button slot="trigger" caret>Dropdown</wa-button> <wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu>
<wa-menu-item>Dropdown Item 1</wa-menu-item> <wa-dropdown-item>
<wa-menu-item>Dropdown Item 2</wa-menu-item> <wa-icon slot="icon" name="scissors"></wa-icon>
<wa-menu-item>Dropdown Item 3</wa-menu-item> Cut
<wa-divider></wa-divider> </wa-dropdown-item>
<wa-menu-item type="checkbox" checked>Checkbox</wa-menu-item> <wa-dropdown-item>
<wa-menu-item disabled>Disabled</wa-menu-item> <wa-icon slot="icon" name="copy"></wa-icon>
<wa-divider></wa-divider> Copy
<wa-menu-item> </wa-dropdown-item>
Prefix <wa-dropdown-item>
<wa-icon slot="prefix" name="gift" variant="solid"></wa-icon> <wa-icon slot="icon" name="paste"></wa-icon>
</wa-menu-item> Paste
<wa-menu-item> </wa-dropdown-item>
Suffix Icon <wa-divider></wa-divider>
<wa-icon slot="suffix" name="heart" variant="solid"></wa-icon> <wa-dropdown-item>
</wa-menu-item> Show images
</wa-menu> <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> </wa-dropdown>
``` ```
@@ -36,17 +46,16 @@ Dropdowns are designed to work well with [menus](/docs/components/menu) to provi
### Getting the Selected Item ### 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} ```html {.example}
<div class="dropdown-selection"> <div class="dropdown-selection">
<wa-dropdown> <wa-dropdown>
<wa-button slot="trigger" caret>Edit</wa-button> <wa-button slot="trigger" caret>View</wa-button>
<wa-menu> <wa-dropdown-item value="full-screen">Enter full screen</wa-dropdown-item>
<wa-menu-item value="cut">Cut</wa-menu-item> <wa-dropdown-item value="actual">Actual size</wa-dropdown-item>
<wa-menu-item value="copy">Copy</wa-menu-item> <wa-dropdown-item value="zoom-in">Zoom in</wa-dropdown-item>
<wa-menu-item value="paste">Paste</wa-menu-item> <wa-dropdown-item value="zoom-out">Zoom out</wa-dropdown-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</div> </div>
@@ -55,53 +64,191 @@ When dropdowns are used with [menus](/docs/components/menu), you can listen for
const dropdown = container.querySelector('wa-dropdown'); const dropdown = container.querySelector('wa-dropdown');
dropdown.addEventListener('wa-select', event => { dropdown.addEventListener('wa-select', event => {
const selectedItem = event.detail.item; console.log(event.detail.item.value);
console.log(selectedItem.value);
}); });
</script> </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} ```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-dropdown>
<wa-button slot="trigger" caret>Edit</wa-button> <wa-button slot="trigger" caret>View</wa-button>
<wa-menu>
<wa-menu-item value="cut">Cut</wa-menu-item> <wa-dropdown-item type="checkbox" value="canvas" checked>Show canvas</wa-dropdown-item>
<wa-menu-item value="copy">Copy</wa-menu-item> <wa-dropdown-item type="checkbox" value="grid" checked>Show grid</wa-dropdown-item>
<wa-menu-item value="paste">Paste</wa-menu-item> <wa-dropdown-item type="checkbox" value="source">Show source</wa-dropdown-item>
</wa-menu>
<wa-divider></wa-divider>
<wa-dropdown-item value="preferences">Preferences…</wa-dropdown-item>
</wa-dropdown> </wa-dropdown>
</div> </div>
<script> <script>
const container = document.querySelector('.dropdown-selection-alt'); const container = document.querySelector('.dropdown-checkboxes');
const cut = container.querySelector('wa-menu-item[value="cut"]'); const dropdown = container.querySelector('wa-dropdown');
const copy = container.querySelector('wa-menu-item[value="copy"]');
const paste = container.querySelector('wa-menu-item[value="paste"]');
cut.addEventListener('click', () => console.log('cut')); dropdown.addEventListener('wa-select', event => {
copy.addEventListener('click', () => console.log('copy')); if (event.detail.item.type === 'checkbox') {
paste.addEventListener('click', () => console.log('paste')); // Checkbox
console.log(event.detail.item.value, event.detail.item.checked ? 'checked' : 'unchecked');
} else {
// Not a checkbox
console.log(event.detail.item.value);
}
});
</script> </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 ### 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. 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} ```html {.example}
<wa-dropdown placement="top-start"> <wa-dropdown placement="right-start">
<wa-button slot="trigger" caret>Edit</wa-button> <wa-button slot="trigger">
<wa-menu> File formats
<wa-menu-item>Cut</wa-menu-item> <wa-icon slot="end" name="chevron-right"></wa-icon>
<wa-menu-item>Copy</wa-menu-item> </wa-button>
<wa-menu-item>Paste</wa-menu-item>
<wa-divider></wa-divider> <wa-dropdown-item value="pdf">PDF Document</wa-dropdown-item>
<wa-menu-item>Find</wa-menu-item> <wa-dropdown-item value="docx">Word Document</wa-dropdown-item>
<wa-menu-item>Replace</wa-menu-item> <wa-dropdown-item value="xlsx">Excel Spreadsheet</wa-dropdown-item>
</wa-menu> <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> </wa-dropdown>
``` ```
@@ -112,71 +259,111 @@ The distance from the panel to the trigger can be customized using the `distance
```html {.example} ```html {.example}
<wa-dropdown distance="30"> <wa-dropdown distance="30">
<wa-button slot="trigger" caret>Edit</wa-button> <wa-button slot="trigger" caret>Edit</wa-button>
<wa-menu>
<wa-menu-item>Cut</wa-menu-item> <wa-dropdown-item>Cut</wa-dropdown-item>
<wa-menu-item>Copy</wa-menu-item> <wa-dropdown-item>Copy</wa-dropdown-item>
<wa-menu-item>Paste</wa-menu-item> <wa-dropdown-item>Paste</wa-dropdown-item>
<wa-divider></wa-divider>
<wa-menu-item>Find</wa-menu-item> <wa-divider></wa-divider>
<wa-menu-item>Replace</wa-menu-item>
</wa-menu> <wa-dropdown-item>Find</wa-dropdown-item>
<wa-dropdown-item>Replace</wa-dropdown-item>
</wa-dropdown> </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} ```html {.example}
<wa-dropdown skidding="30"> <wa-dropdown offset="30">
<wa-button slot="trigger" caret>Edit</wa-button> <wa-button slot="trigger" caret>Edit</wa-button>
<wa-menu>
<wa-menu-item>Cut</wa-menu-item> <wa-dropdown-item>Cut</wa-dropdown-item>
<wa-menu-item>Copy</wa-menu-item> <wa-dropdown-item>Copy</wa-dropdown-item>
<wa-menu-item>Paste</wa-menu-item> <wa-dropdown-item>Paste</wa-dropdown-item>
<wa-divider></wa-divider>
<wa-menu-item>Find</wa-menu-item> <wa-divider></wa-divider>
<wa-menu-item>Replace</wa-menu-item>
</wa-menu> <wa-dropdown-item>Find</wa-dropdown-item>
<wa-dropdown-item>Replace</wa-dropdown-item>
</wa-dropdown> </wa-dropdown>
``` ```
### Submenus ### 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} ```html {.example}
<wa-dropdown> <div class="dropdown-submenus">
<wa-button slot="trigger" caret>Edit</wa-button> <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-divider></wa-divider>
<wa-menu-item value="cut">Cut</wa-menu-item>
<wa-menu-item value="copy">Copy</wa-menu-item> <wa-dropdown-item>
<wa-menu-item value="paste">Paste</wa-menu-item> Options
<wa-divider></wa-divider> <wa-dropdown-item slot="submenu" type="checkbox" value="compress">Compress files</wa-dropdown-item>
<wa-menu-item> <wa-dropdown-item slot="submenu" type="checkbox" checked value="metadata">Include metadata</wa-dropdown-item>
Find <wa-dropdown-item slot="submenu" type="checkbox" value="password">Password protect</wa-dropdown-item>
<wa-menu slot="submenu"> </wa-dropdown-item>
<wa-menu-item value="find">Find…</wa-menu-item> </wa-dropdown>
<wa-menu-item value="find-previous">Find Next</wa-menu-item> </div>
<wa-menu-item value="find-next">Find Previous</wa-menu-item>
</wa-menu> <script>
</wa-menu-item> const container = document.querySelector('.dropdown-submenus');
<wa-menu-item> const dropdown = container.querySelector('wa-dropdown');
Transformations
<wa-menu slot="submenu"> dropdown.addEventListener('wa-select', event => {
<wa-menu-item value="uppercase">Make uppercase</wa-menu-item> console.log(event.detail.item.value);
<wa-menu-item value="lowercase">Make lowercase</wa-menu-item> });
<wa-menu-item value="capitalize">Capitalize</wa-menu-item> </script>
</wa-menu>
</wa-menu-item>
</wa-menu>
</wa-dropdown>
``` ```
:::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 :::warning
As a UX best practice, avoid using more than one level of submenu when possible. 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>
```

View File

@@ -109,24 +109,24 @@ The `type` attribute controls the type of input the browser renders.
<wa-input type="date" placeholder="Date"></wa-input> <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} ```html {.example}
<wa-input placeholder="Small" size="small"> <wa-input placeholder="Small" size="small">
<wa-icon name="house" variant="solid" slot="prefix"></wa-icon> <wa-icon name="house" slot="start"></wa-icon>
<wa-icon name="comment" variant="solid" slot="suffix"></wa-icon> <wa-icon name="comment" slot="end"></wa-icon>
</wa-input> </wa-input>
<br /> <br />
<wa-input placeholder="Medium" size="medium"> <wa-input placeholder="Medium" size="medium">
<wa-icon name="house" variant="solid" slot="prefix"></wa-icon> <wa-icon name="house" slot="start"></wa-icon>
<wa-icon name="comment" variant="solid" slot="suffix"></wa-icon> <wa-icon name="comment" slot="end"></wa-icon>
</wa-input> </wa-input>
<br /> <br />
<wa-input placeholder="Large" size="large"> <wa-input placeholder="Large" size="large">
<wa-icon name="house" variant="solid" slot="prefix"></wa-icon> <wa-icon name="house" slot="start"></wa-icon>
<wa-icon name="comment" variant="solid" slot="suffix"></wa-icon> <wa-icon name="comment" slot="end"></wa-icon>
</wa-input> </wa-input>
``` ```

View File

@@ -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>
```

View File

@@ -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>
```

View File

@@ -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.
:::

View File

@@ -6,50 +6,4 @@ parent: select
icon: option icon: option
--- ---
```html {.example} 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.
<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>
```

View File

@@ -137,7 +137,7 @@ Use the [`autofocus`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_a
</wa-popover> </wa-popover>
<wa-button id="popover__autofocus"> <wa-button id="popover__autofocus">
<wa-icon name="comment" slot="prefix"></wa-icon> <wa-icon name="comment" slot="start"></wa-icon>
Feedback Feedback
</wa-button> </wa-button>
``` ```

View File

@@ -7,9 +7,7 @@ icon: progress-bar
--- ---
```html {.example} ```html {.example}
<wa-progress-bar value="40"> <wa-progress-bar value="40"></wa-progress-bar>
<wa-icon slot="prefix" name="tasks"></wa-icon>
</wa-progress-bar>
``` ```
## Examples ## Examples

View File

@@ -6,9 +6,7 @@ icon: progress-ring
--- ---
```html {.example} ```html {.example}
<wa-progress-ring value="25"> <wa-progress-ring value="25"></wa-progress-ring>
<wa-icon slot="prefix" name="circle-notch"></wa-icon>
</wa-progress-ring>
``` ```
## Examples ## Examples
@@ -18,9 +16,7 @@ icon: progress-ring
Use the `--size` custom property to set the diameter of the progress ring. Use the `--size` custom property to set the diameter of the progress ring.
```html {.example} ```html {.example}
<wa-progress-ring value="50" style="--size: 200px;"> <wa-progress-ring value="50" style="--size: 200px;"></wa-progress-ring>
<wa-icon slot="prefix" name="expand"></wa-icon>
</wa-progress-ring>
``` ```
### Track and Indicator Width ### 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. Use the `--track-width` and `--indicator-width` custom properties to set the width of the progress ring's track and indicator.
```html {.example} ```html {.example}
<wa-progress-ring value="50" style="--track-width: 6px; --indicator-width: 12px;"> <wa-progress-ring value="50" style="--track-width: 6px; --indicator-width: 12px;"></wa-progress-ring>
<wa-icon slot="prefix" name="arrows-alt"></wa-icon>
</wa-progress-ring>
``` ```
### Colors ### Colors
@@ -45,7 +39,6 @@ To change the color, use the `--track-color` and `--indicator-color` custom prop
--indicator-color: deeppink; --indicator-color: deeppink;
" "
> >
<wa-icon slot="prefix" name="palette"></wa-icon>
</wa-progress-ring> </wa-progress-ring>
``` ```

View File

@@ -13,7 +13,7 @@ QR codes are useful for providing small pieces of information to users who can q
<br /> <br />
<wa-input maxlength="255" with-clear label="Value"> <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> </wa-input>
</div> </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. 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} ```html {.example}
<wa-qr-code value="https://shoelace.style/" fill="deeppink" background="white"> <wa-qr-code value="https://shoelace.style/" fill="deeppink" background="white"></wa-qr-code>
<wa-icon slot="prefix" name="palette"></wa-icon>
</wa-qr-code>
``` ```
### Size ### 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. Use the `size` attribute to change the size of the QR code.
```html {.example} ```html {.example}
<wa-qr-code value="https://shoelace.style/" size="64"> <wa-qr-code value="https://shoelace.style/" size="64"></wa-qr-code>
<wa-icon slot="prefix" name="expand"></wa-icon>
</wa-qr-code>
``` ```
### Radius ### 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. Create a rounded effect with the `radius` attribute.
```html {.example} ```html {.example}
<wa-qr-code value="https://shoelace.style/" radius="0.5"> <wa-qr-code value="https://shoelace.style/" radius="0.5"></wa-qr-code>
<wa-icon slot="prefix" name="circle"></wa-icon>
</wa-qr-code>
``` ```
### Error Correction ### Error Correction
@@ -77,18 +71,10 @@ QR codes can be rendered with various levels of [error correction](https://www.q
```html {.example} ```html {.example}
<div class="qr-error-correction"> <div class="qr-error-correction">
<wa-qr-code value="https://shoelace.style/" error-correction="L"> <wa-qr-code value="https://shoelace.style/" error-correction="L"></wa-qr-code>
<wa-icon slot="prefix" name="shield"></wa-icon> <wa-qr-code value="https://shoelace.style/" error-correction="M"></wa-qr-code>
</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="M"> <wa-qr-code value="https://shoelace.style/" error-correction="H"></wa-qr-code>
<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>
</div> </div>
<style> <style>

View File

@@ -7,71 +7,4 @@ native: radio
icon: radio-group icon: radio-group
--- ---
Radios are designed to be used with [radio groups](/docs/components/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.
```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>
```

View File

@@ -208,54 +208,30 @@ The preferred placement of the select's listbox can be set with the `placement`
</wa-select> </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} ```html {.example}
<wa-select placeholder="Small" size="small" with-clear> <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-1">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option> <wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option> <wa-option value="option-3">Option 3</wa-option>
</wa-select> </wa-select>
<br /> <br />
<wa-select placeholder="Medium" size="medium" with-clear> <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-1">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option> <wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option> <wa-option value="option-3">Option 3</wa-option>
</wa-select> </wa-select>
<br /> <br />
<wa-select placeholder="Large" size="large" with-clear> <wa-select placeholder="Large" size="large" with-clear>
<wa-icon slot="prefix" name="house" variant="solid"></wa-icon> <wa-icon slot="start" name="house" variant="solid"></wa-icon>
<wa-option value="option-1">Option 1</wa-option> <wa-icon slot="end" name="flag-checkered"></wa-icon>
<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-option value="option-1">Option 1</wa-option> <wa-option value="option-1">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option> <wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</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" class="custom-tag"
> >
<wa-option value="email"> <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 Email
</wa-option> </wa-option>
<wa-option value="phone"> <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 Phone
</wa-option> </wa-option>
<wa-option value="chat"> <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 Chat
</wa-option> </wa-option>
</wa-select> </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) => { select.getTag = (option, index) => {
// Use the same icon used in wa-option // 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 // You can return a string, a Lit Template, or an HTMLElement here
return ` return `

View File

@@ -6,20 +6,4 @@ parent: tab-group
icon: tab-panel icon: tab-panel
--- ---
```html {.example} 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.
<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).
:::

View File

@@ -6,6 +6,4 @@ parent: tab-group
icon: tab icon: tab
--- ---
:::info 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.
Additional demonstrations can be found in the [tab group examples](/docs/components/tab-group).
:::

View File

@@ -5,78 +5,4 @@ tags: [navigation, disclosure, apps]
icon: tree icon: tree
--- ---
```html {.example} 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.
<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>
```

View File

@@ -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>

View File

@@ -5,71 +5,74 @@ title: Size tests
Button size should default to `medium`: Button size should default to `medium`:
```html {.example} ```html {.example}
<wa-button size=small>Small</wa-button> <wa-button size="small">Small</wa-button>
<wa-button>Medium</wa-button> <wa-button>Medium</wa-button>
<wa-button size=medium>Medium</wa-button> <wa-button size="medium">Medium</wa-button>
<wa-button size=large>Large</wa-button> <wa-button size="large">Large</wa-button>
``` ```
If no button size is specified, it should default to that of its ancestor: If no button size is specified, it should default to that of its ancestor:
```html {.example} ```html {.example}
<wa-button-group size="small"> <wa-button-group size="small">
<wa-button>Small 1</wa-button> <wa-button>Small 1</wa-button>
<wa-button>Small 2</wa-button> <wa-button>Small 2</wa-button>
<wa-button>Small 3</wa-button> <wa-button>Small 3</wa-button>
</wa-button-group> </wa-button-group>
<br><br> <br /><br />
<wa-button-group> <wa-button-group>
<wa-button>Medium 1</wa-button> <wa-button>Medium 1</wa-button>
<wa-button>Medium 2</wa-button> <wa-button>Medium 2</wa-button>
<wa-button>Medium 3</wa-button> <wa-button>Medium 3</wa-button>
</wa-button-group> </wa-button-group>
<br><br> <br /><br />
<wa-button-group size="large"> <wa-button-group size="large">
<wa-button>Large 1</wa-button> <wa-button>Large 1</wa-button>
<wa-button>Large 2</wa-button> <wa-button>Large 2</wa-button>
<wa-button>Large 3</wa-button> <wa-button>Large 3</wa-button>
</wa-button-group> </wa-button-group>
``` ```
Dropdown: Dropdown:
```html {.example} ```html {.example}
<p>Small dropdown: <p>
<wa-dropdown size="small"> Small dropdown:
<wa-button slot="trigger" caret>Dropdown</wa-button> <wa-dropdown size="small">
<wa-menu> <wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu-item>Dropdown Item 1</wa-menu-item> <wa-dropdown-item>Dropdown Item 1</wa-dropdown-item>
<wa-menu-item>Dropdown Item 2</wa-menu-item> <wa-dropdown-item>Dropdown Item 2</wa-dropdown-item>
<wa-menu-item>Dropdown Item 3</wa-menu-item> <wa-dropdown-item>Dropdown Item 3</wa-dropdown-item>
</wa-menu> </wa-dropdown>
</wa-dropdown> </p>
<p>Small menu:
<wa-dropdown> <p>
<wa-button slot="trigger" caret>Dropdown</wa-button> Small menu:
<wa-menu size="small"> <wa-dropdown>
<wa-menu-item>Dropdown Item 1</wa-menu-item> <wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu-item>Dropdown Item 2</wa-menu-item> <wa-dropdown-item size="small">Dropdown Item 1</wa-dropdown-item>
<wa-menu-item>Dropdown Item 3</wa-menu-item> <wa-dropdown-item size="small">Dropdown Item 2</wa-dropdown-item>
</wa-menu> <wa-dropdown-item size="small">Dropdown Item 3</wa-dropdown-item>
</wa-dropdown> </wa-dropdown>
<p>Small menu item: </p>
<wa-dropdown>
<wa-button slot="trigger" caret>Dropdown</wa-button> <p>
<wa-menu> Small menu item:
<wa-menu-item size="small">Dropdown Item 1</wa-menu-item> <wa-dropdown>
<wa-menu-item size="small">Dropdown Item 2</wa-menu-item> <wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu-item size="small">Dropdown Item 3</wa-menu-item> <wa-dropdown-item size="small">Dropdown Item 1</wa-dropdown-item>
</wa-menu> <wa-dropdown-item size="small">Dropdown Item 2</wa-dropdown-item>
</wa-dropdown> <wa-dropdown-item size="small">Dropdown Item 3</wa-dropdown-item>
<p>No size: </wa-dropdown>
<wa-dropdown> </p>
<wa-button slot="trigger" caret>Dropdown</wa-button>
<wa-menu> <p>
<wa-menu-item>Dropdown Item 1</wa-menu-item> No size:
<wa-menu-item>Dropdown Item 2</wa-menu-item> <wa-dropdown>
<wa-menu-item>Dropdown Item 3</wa-menu-item> <wa-button slot="trigger" caret>Dropdown</wa-button>
</wa-menu> <wa-dropdown-item>Dropdown Item 1</wa-dropdown-item>
</wa-dropdown> <wa-dropdown-item>Dropdown Item 2</wa-dropdown-item>
<wa-dropdown-item>Dropdown Item 3</wa-dropdown-item>
</wa-dropdown>
</p>
``` ```

View File

@@ -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: grid; grid-template-rows: minmax(0, auto) minmax(0, 1fr); height: 100%; gap: 1rem;">
<div style="display: flex; gap: 1.25rem;"> <div style="display: flex; gap: 1.25rem;">
<wa-input name="icon-search" autofocus placeholder="Search Icons" with-clear style="flex: 1 1 auto;"> <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-input>
<wa-select name="icon-variant" value="solid" style="flex: 0 1 auto;"> <wa-select name="icon-variant" value="solid" style="flex: 0 1 auto;">
<wa-option value="solid">Solid</wa-option> <wa-option value="solid">Solid</wa-option>
@@ -2017,7 +2017,7 @@ hasOutline: false
<div class="title"> <div class="title">
<h1 class="hero-title">What you know you can't explain, but you feel it.</h1> <h1 class="hero-title">What you know you can't explain, but you feel it.</h1>
<wa-button variant="brand" class="hero-cta"> <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 Free Your Mind
</wa-button> </wa-button>
</div> </div>
@@ -2037,11 +2037,11 @@ hasOutline: false
</div> </div>
<div slot="footer"> <div slot="footer">
<wa-button size="small"> <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 Add to Cart
</wa-button> </wa-button>
<wa-button size="small" appearance="outline"> <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 Save
</wa-button> </wa-button>
</div> </div>
@@ -2060,11 +2060,11 @@ hasOutline: false
</div> </div>
<div slot="footer"> <div slot="footer">
<wa-button size="small"> <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 Add to Cart
</wa-button> </wa-button>
<wa-button size="small" appearance="outline"> <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 Save
</wa-button> </wa-button>
</div> </div>
@@ -2082,11 +2082,11 @@ hasOutline: false
</div> </div>
<div slot="footer"> <div slot="footer">
<wa-button size="small"> <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 Add to Cart
</wa-button> </wa-button>
<wa-button size="small" appearance="outline"> <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 Save
</wa-button> </wa-button>
</div> </div>
@@ -2236,7 +2236,7 @@ hasOutline: false
</div> </div>
<div class="send"> <div class="send">
<wa-button variant="brand" size="small"> <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 Send
</wa-button> </wa-button>
</div> </div>
@@ -2286,11 +2286,11 @@ hasOutline: false
<div style="display: flex; align-items: end; gap: 1rem;"> <div style="display: flex; align-items: end; gap: 1rem;">
<wa-input type="number" label="How many?"></wa-input> <wa-input type="number" label="How many?"></wa-input>
<wa-button variant="brand"> <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 Add to Basket
</wa-button> </wa-button>
<wa-button variant="neutral"> <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 Save
</wa-button> </wa-button>
</div> </div>
@@ -2320,24 +2320,22 @@ hasOutline: false
<td> <td>
<wa-dropdown> <wa-dropdown>
<wa-button slot="trigger" caret size="small">Action</wa-button> <wa-button slot="trigger" caret size="small">Action</wa-button>
<wa-menu> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="check" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="check" variant="regular"></wa-icon> Resolved
Resolved </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="clock" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="clock" variant="regular"></wa-icon> Pending
Pending </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="arrow-rotate-left" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="arrow-rotate-left" variant="regular"></wa-icon> Re-open
Re-open </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="xmark" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="xmark" variant="regular"></wa-icon> Delete
Delete </wa-dropdown-item>
</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</td> </td>
</tr> </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><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 style="text-align: center;"><wa-tag variant="success" size="small">Resolved</wa-tag></td>
<td> <td>
<wa-dropdown> <wa-dropdown>
<wa-button slot="trigger" caret size="small">Action</wa-button> <wa-button slot="trigger" caret size="small">Action</wa-button>
<wa-menu> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="check" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="check" variant="regular"></wa-icon> Resolved
Resolved </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="clock" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="clock" variant="regular"></wa-icon> Pending
Pending </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="arrow-rotate-left" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="arrow-rotate-left" variant="regular"></wa-icon> Re-open
Re-open </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="xmark" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="xmark" variant="regular"></wa-icon> Delete
Delete </wa-dropdown-item>
</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</td> </td>
</tr> </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><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 style="text-align: center;"><wa-tag variant="warning" size="small">Pending</wa-tag></td>
<td> <td>
<wa-dropdown> <wa-dropdown>
<wa-button slot="trigger" caret size="small">Action</wa-button> <wa-button slot="trigger" caret size="small">Action</wa-button>
<wa-menu> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="check" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="check" variant="regular"></wa-icon> Resolved
Resolved </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="clock" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="clock" variant="regular"></wa-icon> Pending
Pending </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="arrow-rotate-left" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="arrow-rotate-left" variant="regular"></wa-icon> Re-open
Re-open </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="xmark" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="xmark" variant="regular"></wa-icon> Delete
Delete </wa-dropdown-item>
</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</td> </td>
</tr> </tr>
@@ -2408,26 +2402,24 @@ hasOutline: false
<td></td> <td></td>
<td style="text-align: center;"><wa-tag variant="danger" size="small">Bounced</wa-tag></td> <td style="text-align: center;"><wa-tag variant="danger" size="small">Bounced</wa-tag></td>
<td> <td>
<wa-dropdown> <wa-dropdown>
<wa-button slot="trigger" caret size="small">Action</wa-button> <wa-button slot="trigger" caret size="small">Action</wa-button>
<wa-menu> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="check" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="check" variant="regular"></wa-icon> Resolved
Resolved </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="clock" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="clock" variant="regular"></wa-icon> Pending
Pending </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="arrow-rotate-left" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="arrow-rotate-left" variant="regular"></wa-icon> Re-open
Re-open </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="xmark" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="xmark" variant="regular"></wa-icon> Delete
Delete </wa-dropdown-item>
</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</td> </td>
</tr> </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><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 style="text-align: center;"><wa-tag variant="neutral" size="small">Expired</wa-tag></td>
<td> <td>
<wa-dropdown> <wa-dropdown>
<wa-button slot="trigger" caret size="small">Action</wa-button> <wa-button slot="trigger" caret size="small">Action</wa-button>
<wa-menu> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="check" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="check" variant="regular"></wa-icon> Resolved
Resolved </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="clock" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="clock" variant="regular"></wa-icon> Pending
Pending </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="arrow-rotate-left" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="arrow-rotate-left" variant="regular"></wa-icon> Re-open
Re-open </wa-dropdown-item>
</wa-menu-item> <wa-dropdown-item>
<wa-menu-item> <wa-icon slot="start" name="xmark" variant="regular"></wa-icon>
<wa-icon slot="prefix" name="xmark" variant="regular"></wa-icon> Delete
Delete </wa-dropdown-item>
</wa-menu-item>
</wa-menu>
</wa-dropdown> </wa-dropdown>
</td> </td>
</tr> </tr>
@@ -2470,30 +2460,30 @@ hasOutline: false
<h2 style="margin-bottom: var(--wa-space-3xl);">Payment</h2> <h2 style="margin-bottom: var(--wa-space-3xl);">Payment</h2>
<form> <form>
<wa-input type="email" placeholder="ex. tanderson@metacortex.com" label="Email"> <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>
<wa-input placeholder="1234 1234 1234 1234" label="Card Number"> <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> </wa-input>
<div style="display: flex; gap: 1rem;"> <div style="display: flex; gap: 1rem;">
<wa-input placeholder="MM / YY" label="Expiration"> <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>
<wa-input placeholder="CVC" label="CVC"> <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> </wa-input>
</div> </div>
<wa-input placeholder="Thomas Anderson" label="Cardholder Name"> <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> </wa-input>
<div style="display: flex; gap: 1rem;"> <div style="display: flex; gap: 1rem;">
<wa-select label="Country" value="USA"> <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="USA">United States</wa-option>
<wa-option value="CAN">Canada</wa-option> <wa-option value="CAN">Canada</wa-option>
</wa-select> </wa-select>
<wa-input placeholder="12345" label="Zip"> <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> </wa-input>
</div> </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> <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>

View File

@@ -14,6 +14,7 @@ During the alpha period, things might break! We take breaking changes very serio
## Next ## 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 - 🚨 BREAKING: Greatly simplified how native styles work and removed redundant utilities
- Removed `.wa-button`, `.wa-callout` classes - Removed `.wa-button`, `.wa-callout` classes
- Removed `themes/native/*.css` files; use `native.css` to opt into native styles - 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-tab-group no-scroll-controls>` => `<wa-tab-group without-scroll-controls>`
- `<wa-tag removable>` => `<wa-tag with-remove>` - `<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: 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` - 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` - 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 - 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) - 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 - 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: 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-popover>` (#2 of 14 per stretch goals)
- Added a new free component: `<wa-zoomable-frame>` (#3 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] - 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) 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/)

View File

@@ -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 - 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-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 Join the Discussion
</wa-button> </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 - 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-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 Join the Chat
</wa-button> </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. **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-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 Follow on Twitter
</wa-button> </wa-button>

View File

@@ -26,7 +26,7 @@ unlisted: true
{% if theme.fileSlug === 'custom' %} {% if theme.fileSlug === 'custom' %}
<p> <p>
<wa-button href="../edit/" class="edit-link" target="_parent" appearance="outlined"> <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 Edit theme
</wa-button> </wa-button>
</p> </p>

View File

@@ -42,7 +42,7 @@ unlisted: true
<wa-tab-panel name="css"> <wa-tab-panel name="css">
<p> <p>
<wa-button variant="brand" :href="code.css.blob" :download="cssFilename"> <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> Download <code v-text="cssFilename"></code>
</wa-button> </wa-button>
</p> </p>
@@ -153,7 +153,7 @@ unlisted: true
</icons-card> </icons-card>
<wa-input label="Font Awesome Pro Kit Code" v-model="theme.icon.kit" placeholder="e.g. f0nta7e50e"> <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"> <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> <span>Find your kit code here</span>
<wa-icon name="arrow-up-right-from-square" variant="regular" style="font-size: 0.75em"></wa-icon> <wa-icon name="arrow-up-right-from-square" variant="regular" style="font-size: 0.75em"></wa-icon>

View File

@@ -36,7 +36,7 @@ noTheme: true
<div class="title"> <div class="title">
<h1 class="hero-title">What you know you can't explain, but you feel it.</h1> <h1 class="hero-title">What you know you can't explain, but you feel it.</h1>
<wa-button variant="brand" class="hero-cta"> <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 Free Your Mind
</wa-button> </wa-button>
</div> </div>
@@ -56,11 +56,11 @@ noTheme: true
</div> </div>
<div slot="footer"> <div slot="footer">
<wa-button size="small"> <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 Add to Cart
</wa-button> </wa-button>
<wa-button size="small" appearance="outline"> <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 Save
</wa-button> </wa-button>
</div> </div>
@@ -79,11 +79,11 @@ noTheme: true
</div> </div>
<div slot="footer"> <div slot="footer">
<wa-button size="small"> <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 Add to Cart
</wa-button> </wa-button>
<wa-button size="small" appearance="outline"> <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 Save
</wa-button> </wa-button>
</div> </div>
@@ -101,11 +101,11 @@ noTheme: true
</div> </div>
<div slot="footer"> <div slot="footer">
<wa-button size="small"> <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 Add to Cart
</wa-button> </wa-button>
<wa-button size="small" appearance="outline"> <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 Save
</wa-button> </wa-button>
</div> </div>

View File

@@ -108,11 +108,11 @@ For example, a button's default slot is used to populate its label.
<wa-button>Click me</wa-button> <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 ```html
<wa-button> <wa-button>
<wa-icon slot="prefix" name="gear" variant="solid"></wa-icon> <wa-icon slot="start" name="gear" variant="solid"></wa-icon>
Settings Settings
</wa-button> </wa-button>
``` ```

View File

@@ -37,7 +37,7 @@ Flanks work especially well for asides, inputs with adjacent buttons, and rich d
```html {.example} ```html {.example}
<div class="wa-flank:end wa-gap-xs"> <div class="wa-flank:end wa-gap-xs">
<wa-input> <wa-input>
<wa-icon slot="prefix" name="magnifying-glass"></wa-icon> <wa-icon slot="start" name="magnifying-glass"></wa-icon>
</wa-input> </wa-input>
<wa-button>Search</wa-button> <wa-button>Search</wa-button>
</div> </div>

View File

@@ -77,16 +77,16 @@ Organized content in bulleted or numbered format with proper nesting support.
<ol> <ol>
<li>List item 1</li> <li>List item 1</li>
<li>List item 2 <li>List item 2
<ul> <ol>
<li>Subitem a</li> <li>Subitem a</li>
<li>Subitem b</li> <li>Subitem b</li>
</ul> </ol>
</li> </li>
<li>List item 3</li> <li>List item 3</li>
</ol> </ol>
``` ```
### Definition Lists ### Description Lists
Term and definition pairs for glossaries and descriptions. Term and definition pairs for glossaries and descriptions.
@@ -141,19 +141,19 @@ Various text formatting elements for emphasis and semantic meaning.
```html {.example} ```html {.example}
<div class="two-columns"> <div class="two-columns">
<p><strong>Bold</strong></p> <p><strong>Bold</strong></p>
<p><em>Italics</em></p> <p><em>Italic</em></p>
<p><u>Underline</u></p> <p><u>Underline</u></p>
<p><s>Strike-through</s></p>
<p><del>Deleted</del></p> <p><del>Deleted</del></p>
<p><ins>Inserted</ins></p> <p><ins>Inserted</ins></p>
<p><s>Strike-through</s></p>
<p><small>Small</small></p> <p><small>Small</small></p>
<p><span>Text <sub>Sub</sub></span></p> <p><span>Subscript <sub>Sub</sub></span></p>
<p><span>Text <sup>Sup</sup></span></p> <p><span>Superscript <sup>Sup</sup></span></p>
<p><abbr title="Abbreviation">Abbr.</abbr></p> <p><abbr title="Abbreviation">Abbr.</abbr></p>
<p><kbd>Keyboard</kbd></p>
<p><mark>Highlighted</mark></p> <p><mark>Highlighted</mark></p>
<p><a href="#">Link text</a></p> <p><a href="#">Link text</a></p>
<p><code>Inline code</code></p> <p><code>Inline code</code></p>
<p><kbd>Keyboard</kbd></p>
</div> </div>
``` ```
@@ -161,11 +161,13 @@ Various text formatting elements for emphasis and semantic meaning.
Formatted code snippets with proper syntax styling. Formatted code snippets with proper syntax styling.
``` ```html {.example}
<pre>
// do a thing // do a thing
export function thing() { export function thing() {
return true; return true;
} }
</pre>
``` ```
### Images ### Images
@@ -454,3 +456,12 @@ Multi-line text input fields for longer content.
```html {.example} ```html {.example}
<label>Textarea <textarea placeholder="Type something"></textarea></label> <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>
```

View File

@@ -36,10 +36,10 @@ Stacks are well suited for forms, text, and ensuring consistent spacing between
```html {.example} ```html {.example}
<div class="wa-stack"> <div class="wa-stack">
<wa-input label="Email"> <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>
<wa-input label="Password" type="password"> <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-input>
<wa-checkbox>Remember me on this device</wa-checkbox> <wa-checkbox>Remember me on this device</wa-checkbox>
<wa-button>Log In</wa-button> <wa-button>Log In</wa-button>

View File

@@ -222,7 +222,7 @@ layout: page
text-align: left; text-align: left;
white-space: wrap; white-space: wrap;
} }
wa-button.tile::part(suffix) { wa-button.tile::part(end) {
display: none; display: none;
} }
wa-button.tile { wa-button.tile {
@@ -266,7 +266,7 @@ layout: page
<div class="hero-cta"> <div class="hero-cta">
<span><em>Psst!</em> You can pre-order Web Awesome Pro at a low, guaranteed-for-life price &mdash; but not for long. Get in while the gettins good.</span> <span><em>Psst!</em> You can pre-order Web Awesome Pro at a low, guaranteed-for-life price &mdash; but not for long. Get in while the gettins good.</span>
<wa-button class="wa-dark" size="small" href="https://www.kickstarter.com/projects/fontawesome/web-awesome"> <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 Pre-order WA Pro
</wa-button> </wa-button>
</div> </div>

View File

@@ -149,13 +149,14 @@ export async function build(options = {}) {
if (process.env.ROOT_DIR) { if (process.env.ROOT_DIR) {
process.chdir(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); process.chdir(cwd);
} catch (error) { } catch (error) {
process.chdir(cwd); process.chdir(cwd);
if (!isDeveloping) { if (!isDeveloping) {
process.exit(1); process.exit(1);
} }
return Promise.reject(error.stdout); return Promise.reject(error.stdout);
} }

View File

@@ -45,25 +45,25 @@
outline-offset: var(--wa-focus-ring-offset); outline-offset: var(--wa-focus-ring-offset);
} }
.prefix, .start,
.suffix { .end {
display: none; display: none;
flex: 0 0 auto; flex: 0 0 auto;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.prefix, .start,
.suffix { .end {
display: inline-flex; display: inline-flex;
color: var(--wa-color-text-quiet); color: var(--wa-color-text-quiet);
} }
::slotted([slot='prefix']) { ::slotted([slot='start']) {
margin-inline-end: var(--wa-space-s); margin-inline-end: var(--wa-space-s);
} }
::slotted([slot='suffix']) { ::slotted([slot='end']) {
margin-inline-start: var(--wa-space-s); margin-inline-start: var(--wa-space-s);
} }

View File

@@ -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 () => { it('should pass accessibility tests', async () => {
const el = await fixture<WaBreadcrumbItem>(html` const el = await fixture<WaBreadcrumbItem>(html`
<wa-breadcrumb-item> <wa-breadcrumb-item>
<span class="prefix-example" slot="prefix">/</span> <span class="start-example" slot="start">/</span>
Home Home
</wa-breadcrumb-item> </wa-breadcrumb-item>
`); `);
@@ -136,22 +136,22 @@ describe('<wa-breadcrumb-item>', () => {
it('should accept as an assigned child in the shadow root', async () => { it('should accept as an assigned child in the shadow root', async () => {
const el = await fixture<WaBreadcrumbItem>(html` const el = await fixture<WaBreadcrumbItem>(html`
<wa-breadcrumb-item> <wa-breadcrumb-item>
<span class="prefix-example" slot="prefix">/</span> <span class="start-example" slot="start">/</span>
Home Home
</wa-breadcrumb-item> </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 }); const childNodes = slot.assignedNodes({ flatten: true });
expect(childNodes.length).to.eq(1); 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 () => { it('should pass accessibility tests', async () => {
const el = await fixture<WaBreadcrumbItem>(html` const el = await fixture<WaBreadcrumbItem>(html`
<wa-breadcrumb-item> <wa-breadcrumb-item>
<span class="prefix-example" slot="suffix">/</span> <span class="end-example" slot="end">/</span>
Security Security
</wa-breadcrumb-item> </wa-breadcrumb-item>
`); `);
@@ -162,40 +162,16 @@ describe('<wa-breadcrumb-item>', () => {
it('should accept as an assigned child in the shadow root', async () => { it('should accept as an assigned child in the shadow root', async () => {
const el = await fixture<WaBreadcrumbItem>(html` const el = await fixture<WaBreadcrumbItem>(html`
<wa-breadcrumb-item> <wa-breadcrumb-item>
<span class="prefix-example" slot="suffix">/</span> <span class="end-example" slot="end">/</span>
Security Security
</wa-breadcrumb-item> </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 }); const childNodes = slot.assignedNodes({ flatten: true });
expect(childNodes.length).to.eq(1); 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;
});
});
}); });
} }
}); });

View File

@@ -6,20 +6,20 @@ import WebAwesomeElement from '../../internal/webawesome-element.js';
import styles from './breadcrumb-item.css'; 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 * @documentation https://backers.webawesome.com/docs/components/breadcrumb-item
* @status stable * @status stable
* @since 2.0 * @since 2.0
* *
* @slot - The breadcrumb item's label. * @slot - The breadcrumb item's label.
* @slot prefix - An optional prefix, usually an icon. * @slot start - An element, such as `<wa-icon>`, placed before the label.
* @slot suffix - An optional suffix, usually an icon. * @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 * @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. * 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 label - The breadcrumb item's label.
* @csspart prefix - The container that wraps the prefix. * @csspart start - The container that wraps the `start` slot.
* @csspart suffix - The container that wraps the suffix. * @csspart end - The container that wraps the `end` slot.
* @csspart separator - The container that wraps the separator. * @csspart separator - The container that wraps the separator.
*/ */
@customElement('wa-breadcrumb-item') @customElement('wa-breadcrumb-item')
@@ -71,8 +71,8 @@ export default class WaBreadcrumbItem extends WebAwesomeElement {
render() { render() {
return html` return html`
<span part="prefix" class="prefix"> <span part="start" class="start">
<slot name="prefix"></slot> <slot name="start"></slot>
</span> </span>
${this.renderType === 'link' ${this.renderType === 'link'
@@ -103,8 +103,8 @@ export default class WaBreadcrumbItem extends WebAwesomeElement {
` `
: ''} : ''}
<span part="suffix" class="suffix"> <span part="end" class="end">
<slot name="suffix"></slot> <slot name="end"></slot>
</span> </span>
<span part="separator" class="separator" aria-hidden="true"> <span part="separator" class="separator" aria-hidden="true">

View File

@@ -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 () => { it('should pass accessibility tests', async () => {
const el = await fixture<WaBreadcrumb>(html` const el = await fixture<WaBreadcrumb>(html`
<wa-breadcrumb> <wa-breadcrumb>
<wa-breadcrumb-item> <wa-breadcrumb-item>
<span class="prefix-example" slot="prefix">/</span> <span class="start-example" slot="start">/</span>
Home Home
</wa-breadcrumb-item> </wa-breadcrumb-item>
<wa-breadcrumb-item>First</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 () => { it('should pass accessibility tests', async () => {
const el = await fixture<WaBreadcrumb>(html` const el = await fixture<WaBreadcrumb>(html`
<wa-breadcrumb> <wa-breadcrumb>
@@ -120,7 +120,7 @@ describe('<wa-breadcrumb>', () => {
<wa-breadcrumb-item>Second</wa-breadcrumb-item> <wa-breadcrumb-item>Second</wa-breadcrumb-item>
<wa-breadcrumb-item>Third</wa-breadcrumb-item> <wa-breadcrumb-item>Third</wa-breadcrumb-item>
<wa-breadcrumb-item> <wa-breadcrumb-item>
<span class="prefix-example" slot="suffix">/</span> <span class="end-example" slot="end">/</span>
Security Security
</wa-breadcrumb-item> </wa-breadcrumb-item>
</wa-breadcrumb> </wa-breadcrumb>

View File

@@ -107,8 +107,8 @@
* Label * Label
*/ */
.prefix, .start,
.suffix { .end {
flex: 0 0 auto; flex: 0 0 auto;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -137,7 +137,7 @@ wa-icon[part~='caret'] {
height: 0.875em; height: 0.875em;
} }
.button:has(&) .suffix { .button:has(&) .end {
display: none; display: none;
} }
} }
@@ -150,9 +150,9 @@ wa-icon[part~='caret'] {
position: relative; position: relative;
cursor: wait; cursor: wait;
.prefix, .start,
.label, .label,
.suffix, .end,
.caret { .caret {
visibility: hidden; visibility: hidden;
} }
@@ -191,11 +191,11 @@ button ::slotted(wa-badge) {
* Button spacing * Button spacing
*/ */
slot[name='prefix']::slotted(*) { slot[name='start']::slotted(*) {
margin-inline-end: var(--wa-form-control-padding-inline); margin-inline-end: var(--wa-form-control-padding-inline);
} }
slot[name='suffix']::slotted(*), slot[name='end']::slotted(*),
.button:not(.visually-hidden-label) [part~='caret'] { .button:not(.visually-hidden-label) [part~='caret'] {
margin-inline-start: var(--wa-form-control-padding-inline); margin-inline-start: var(--wa-form-control-padding-inline);
} }

View File

@@ -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. * @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 - The button's label.
* @slot prefix - A presentational prefix icon or similar element. * @slot start - An element, such as `<wa-icon>`, placed before the label.
* @slot suffix - A presentational suffix icon or similar element. * @slot end - An element, such as `<wa-icon>`, placed after the label.
* *
* @csspart base - The component's base wrapper. * @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 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 caret - The button's caret icon, a `<wa-icon>` element.
* @csspart spinner - The spinner that shows when the button is in the loading state. * @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']; 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); private readonly localize = new LocalizeController(this);
@query('.button') button: HTMLButtonElement | HTMLLinkElement; @query('.button') button: HTMLButtonElement | HTMLLinkElement;
@@ -263,8 +263,8 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
loading: this.loading, loading: this.loading,
rtl: this.localize.dir() === 'rtl', rtl: this.localize.dir() === 'rtl',
'has-label': this.hasSlotController.test('[default]'), 'has-label': this.hasSlotController.test('[default]'),
'has-prefix': this.hasSlotController.test('prefix'), 'has-start': this.hasSlotController.test('start'),
'has-suffix': this.hasSlotController.test('suffix'), 'has-end': this.hasSlotController.test('end'),
'is-icon-button': this.isIconButton, 'is-icon-button': this.isIconButton,
})} })}
?disabled=${ifDefined(isLink ? undefined : this.disabled)} ?disabled=${ifDefined(isLink ? undefined : this.disabled)}
@@ -282,9 +282,9 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
@invalid=${this.isButton() ? this.handleInvalid : null} @invalid=${this.isButton() ? this.handleInvalid : null}
@click=${this.handleClick} @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 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 this.caret
? html` ? html`

View File

@@ -4,7 +4,7 @@ import WebAwesomeElement from '../../internal/webawesome-element.js';
import styles from './carousel-item.css'; 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 * @since 2.0
* @status experimental * @status experimental

View File

@@ -132,7 +132,9 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
this.hasInteracted = true; this.hasInteracted = true;
this.checked = !this.checked; this.checked = !this.checked;
this.indeterminate = false; 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') @watch('defaultChecked')

View File

@@ -20,6 +20,10 @@
.color-picker { .color-picker {
background-color: var(--background-color); background-color: var(--background-color);
border-radius: var(--border-radius); 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); color: var(--color);
font: inherit; font: inherit;
user-select: none; user-select: none;

View File

@@ -300,6 +300,7 @@ describe('<wa-color-picker>', () => {
await sendKeys({ type: 'fc0' }); // type in a color await sendKeys({ type: 'fc0' }); // type in a color
input.blur(); // commit changes by blurring the field input.blur(); // commit changes by blurring the field
await el.updateComplete; await el.updateComplete;
await aTimeout(1);
expect(changeHandler).to.have.been.calledOnce; expect(changeHandler).to.have.been.calledOnce;
expect(inputHandler).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 () => { it('should show opacity slider when opacity is enabled', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker opacity></wa-color-picker> `); const el = await fixture<WaColorPicker>(html` <wa-color-picker opacity></wa-color-picker> `);
const opacitySlider = el.shadowRoot!.querySelector('[part*="opacity-slider"]')!; const opacitySlider = el.shadowRoot!.querySelector('[part*="opacity-slider"]')!;
@@ -368,7 +362,7 @@ describe('<wa-color-picker>', () => {
<button type="button">Click me</button> <button type="button">Click me</button>
</div> </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 trigger = colorPicker.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const button = el.querySelector('button')!; const button = el.querySelector('button')!;
const focusHandler = sinon.spy(); const focusHandler = sinon.spy();
@@ -456,7 +450,7 @@ describe('<wa-color-picker>', () => {
</form> </form>
`); `);
const button = form.querySelector('wa-button')!; const button = form.querySelector('wa-button')!;
const colorPicker = form.querySelector('wa-color-picker')!; const colorPicker = form.querySelector<WaColorPicker>('wa-color-picker')!;
colorPicker.value = '#000000'; colorPicker.value = '#000000';
await colorPicker.updateComplete; await colorPicker.updateComplete;

View File

@@ -6,7 +6,9 @@ import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js'; import { ifDefined } from 'lit/directives/if-defined.js';
import { styleMap } from 'lit/directives/style-map.js'; import { styleMap } from 'lit/directives/style-map.js';
import { WaInvalidEvent } from '../../events/invalid.js'; import { WaInvalidEvent } from '../../events/invalid.js';
import { animateWithClass } from '../../internal/animate.js';
import { drag } from '../../internal/drag.js'; import { drag } from '../../internal/drag.js';
import { waitForEvent } from '../../internal/event.js';
import { clamp } from '../../internal/math.js'; import { clamp } from '../../internal/math.js';
import { HasSlotController } from '../../internal/slot.js'; import { HasSlotController } from '../../internal/slot.js';
import { RequiredValidator } from '../../internal/validators/required-validator.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 { LocalizeController } from '../../utilities/localize.js';
import '../button-group/button-group.js'; import '../button-group/button-group.js';
import '../button/button.js'; import '../button/button.js';
import '../dropdown/dropdown.js';
import type WaDropdown from '../dropdown/dropdown.js';
import '../icon/icon.js'; import '../icon/icon.js';
import '../input/input.js'; import '../input/input.js';
import type WaInput from '../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'; import styles from './color-picker.css';
interface EyeDropperConstructor { interface EyeDropperConstructor {
@@ -43,8 +45,8 @@ declare const EyeDropper: EyeDropperConstructor;
* *
* @dependency wa-button * @dependency wa-button
* @dependency wa-button-group * @dependency wa-button-group
* @dependency wa-dropdown
* @dependency wa-input * @dependency wa-input
* @dependency wa-popup
* @dependency wa-visually-hidden * @dependency wa-visually-hidden
* *
* @slot label - The color picker's form label. Alternatively, you can use the `label` attribute. * @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 input - The text input.
* @csspart eye-dropper-button - The eye dropper button. * @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__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__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 eye-dropper-button__caret - The eye dropper button's exported `caret` part.
* @csspart format-button - The format button. * @csspart format-button - The format button.
* @csspart format-button__base - The format button's exported `button` part. * @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__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. * @csspart format-button__caret - The format button's exported `caret` part.
* *
* @cssproperty --background-color - The color picker's background color. * @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? // or is the new behavior okay?
get validationTarget() { get validationTarget() {
// This puts the popup on the element only if the color picker is expanded. // 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; return this.input;
} }
@@ -134,7 +136,7 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
return this.trigger; return this.trigger;
} }
@query('.color-dropdown') dropdown: WaDropdown; @query('.color-popup') popup: WaPopup;
@query('[part~="preview"]') previewButton: HTMLButtonElement; @query('[part~="preview"]') previewButton: HTMLButtonElement;
@query('[part~="trigger"]') trigger: HTMLButtonElement; @query('[part~="trigger"]') trigger: HTMLButtonElement;
@@ -210,6 +212,12 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
/** Disables the color picker. */ /** Disables the color picker. */
@property({ type: Boolean }) disabled = false; @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. */ /** Shows the opacity slider. Enabling this will cause the formatted value to be HEXA, RGBA, or HSLA. */
@property({ type: Boolean }) opacity = false; @property({ type: Boolean }) opacity = false;
@@ -267,8 +275,11 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
const nextIndex = (formats.indexOf(this.format) + 1) % formats.length; const nextIndex = (formats.indexOf(this.format) + 1) % formats.length;
this.format = formats[nextIndex] as 'hex' | 'rgb' | 'hsl' | 'hsv'; this.format = formats[nextIndex] as 'hex' | 'rgb' | 'hsl' | 'hsv';
this.setColor(this.value || ''); 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) { private handleAlphaDrag(event: PointerEvent) {
@@ -288,13 +299,18 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
if (this.value !== currentValue) { if (this.value !== currentValue) {
currentValue = this.value; currentValue = this.value;
this.dispatchEvent(new InputEvent('input')); this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
});
} }
}, },
onStop: () => { onStop: () => {
if (this.value !== initialValue) { if (this.value !== initialValue) {
initialValue = this.value; 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, initialEvent: event,
@@ -318,13 +334,17 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
if (this.value !== currentValue) { if (this.value !== currentValue) {
currentValue = this.value; currentValue = this.value;
this.dispatchEvent(new InputEvent('input')); this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input'));
});
} }
}, },
onStop: () => { onStop: () => {
if (this.value !== initialValue) { if (this.value !== initialValue) {
initialValue = this.value; 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, initialEvent: event,
@@ -351,14 +371,18 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
if (this.value !== currentValue) { if (this.value !== currentValue) {
currentValue = this.value; currentValue = this.value;
this.dispatchEvent(new InputEvent('input')); this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
});
} }
}, },
onStop: () => { onStop: () => {
this.isDraggingGridHandle = false; this.isDraggingGridHandle = false;
if (this.value !== initialValue) { if (this.value !== initialValue) {
initialValue = this.value; 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, initialEvent: event,
@@ -394,8 +418,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
} }
if (this.value !== oldValue) { if (this.value !== oldValue) {
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 }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
} }
} }
@@ -428,8 +454,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
} }
if (this.value !== oldValue) { if (this.value !== oldValue) {
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 }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
} }
} }
@@ -462,8 +490,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
} }
if (this.value !== oldValue) { if (this.value !== oldValue) {
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 }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
} }
} }
@@ -482,8 +512,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
} }
if (this.value !== oldValue) { if (this.value !== oldValue) {
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 }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
} }
} }
@@ -503,8 +535,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
this.input.value = this.value; this.input.value = this.value;
if (this.value !== oldValue) { if (this.value !== oldValue) {
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 }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
} }
setTimeout(() => this.input.select()); setTimeout(() => this.input.select());
@@ -688,8 +722,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
this.setColor(colorSelectionResult.sRGBHex); this.setColor(colorSelectionResult.sRGBHex);
if (this.value !== oldValue) { if (this.value !== oldValue) {
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 }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
} }
}) })
.catch(() => { .catch(() => {
@@ -704,8 +740,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
this.setColor(color); this.setColor(color);
if (this.value !== oldValue) { if (this.value !== oldValue) {
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 }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
} }
} }
} }
@@ -790,8 +828,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
elementToBlur.blur(); elementToBlur.blur();
} }
if (this.dropdown?.open) { if (this.popup?.active) {
this.dropdown.hide(); 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. */ /** Checks for validity and shows the browser's validation message if the control is invalid. */
reportValidity() { reportValidity() {
// This won't get called when a form is submitted. This is only for manual calls. // This won't get called when a form is submitted. This is only for manual calls.
if (!this.validity.valid && !this.dropdown.open) { if (!this.validity.valid && !this.open) {
// Show the dropdown so the browser can focus on it // Show the popup so the browser can focus on it
this.addEventListener('wa-after-show', this.reportValidityAfterShow, { once: true }); this.addEventListener('wa-after-show', this.reportValidityAfterShow, { once: true });
this.dropdown.show(); this.show();
if (!this.disabled) { if (!this.disabled) {
// By standards we have to emit a `wa-invalid` event here synchronously. // 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; 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() { render() {
const hasLabelSlot = !this.hasUpdated ? this.withLabel : this.withLabel || this.hasSlotController.test('label'); const hasLabelSlot = !this.hasUpdated ? this.withLabel : this.withLabel || this.hasSlotController.test('label');
const hasHintSlot = !this.hasUpdated ? this.withHint : this.withHint || this.hasSlotController.test('hint'); 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')} aria-label=${this.localize.term('toggleColorFormat')}
exportparts=" exportparts="
base:format-button__base, base:format-button__base,
prefix:format-button__prefix, start:format-button__start,
label:format-button__label, label:format-button__label,
suffix:format-button__suffix, end:format-button__end,
caret:format-button__caret caret:format-button__caret
" "
@click=${this.handleFormatToggle} @click=${this.handleFormatToggle}
@@ -1042,9 +1232,9 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
appearance="outlined" appearance="outlined"
exportparts=" exportparts="
base:eye-dropper-button__base, base:eye-dropper-button__base,
prefix:eye-dropper-button__prefix, start:eye-dropper-button__start,
label:eye-dropper-button__label, label:eye-dropper-button__label,
suffix:eye-dropper-button__suffix, end:eye-dropper-button__end,
caret:eye-dropper-button__caret caret:eye-dropper-button__caret
" "
@click=${this.handleEyeDropper} @click=${this.handleEyeDropper}
@@ -1095,82 +1285,64 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
</div> </div>
`; `;
// Render as a dropdown // Render with popup
return html` return html`
<wa-dropdown <div
class="color-dropdown" 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'} aria-disabled=${this.disabled ? 'true' : 'false'}
.containingElement=${this}
?disabled=${this.disabled}
@wa-after-show=${this.handleAfterShow} @wa-after-show=${this.handleAfterShow}
@wa-after-hide=${this.handleAfterHide} @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} ${colorPicker}
</wa-dropdown> </wa-popup>
`; `;
} }
} }
declare global {
interface HTMLElementTagNameMap {
'wa-color-picker': WaColorPicker;
}
}

View File

@@ -3,7 +3,7 @@
--border-radius: var(--wa-panel-border-radius); --border-radius: var(--wa-panel-border-radius);
--box-shadow: var(--wa-shadow-l); --box-shadow: var(--wa-shadow-l);
--width: 31rem; --width: 31rem;
--spacing: var(--wa-space-xl); --spacing: var(--wa-space-l);
--show-duration: 200ms; --show-duration: 200ms;
--hide-duration: 200ms; --hide-duration: 200ms;

View File

@@ -2,7 +2,7 @@
--background-color: var(--wa-color-surface-raised); --background-color: var(--wa-color-surface-raised);
--box-shadow: var(--wa-shadow-l); --box-shadow: var(--wa-shadow-l);
--size: 25rem; --size: 25rem;
--spacing: var(--wa-space-xl); --spacing: var(--wa-space-l);
--show-duration: 200ms; --show-duration: 200ms;
--hide-duration: 200ms; --hide-duration: 200ms;

View File

@@ -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;
}
}

View File

@@ -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;
});
});

View File

@@ -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;
}
}

View File

@@ -1,60 +1,93 @@
:host { :host {
--box-shadow: var(--wa-shadow-m); --show-duration: 50ms;
--hide-duration: 50ms;
display: inline-block; display: contents;
} }
.dropdown::part(popup) { #menu {
z-index: 900; 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; transform-origin: bottom;
} }
.dropdown[data-current-placement^='bottom']::part(popup) { wa-popup[data-current-placement^='bottom'] #menu {
transform-origin: top; transform-origin: top;
} }
.dropdown[data-current-placement^='left']::part(popup) { wa-popup[data-current-placement^='left'] #menu {
transform-origin: right; transform-origin: right;
} }
.dropdown[data-current-placement^='right']::part(popup) { wa-popup[data-current-placement^='right'] #menu {
transform-origin: left; transform-origin: left;
} }
#trigger { wa-popup[data-current-placement='left-start'] #menu {
display: block; /* for boundingClientRect */ transform-origin: right top;
} }
.panel { wa-popup[data-current-placement='left-end'] #menu {
font: inherit; transform-origin: right bottom;
box-shadow: var(--box-shadow);
border-radius: var(--wa-border-radius-m);
pointer-events: none;
} }
.dropdown-open .panel { wa-popup[data-current-placement='right-start'] #menu {
display: block; transform-origin: left top;
pointer-events: all;
} }
/* Sizes */ wa-popup[data-current-placement='right-end'] #menu {
:host([size='small']) ::slotted(wa-menu) { transform-origin: left bottom;
font-size: var(--wa-font-size-s);
} }
:host([size='medium']) ::slotted(wa-menu) { @keyframes show {
font-size: var(--wa-font-size-m); from {
} scale: 0.9;
opacity: 0;
:host([size='large']) ::slotted(wa-menu) { }
font-size: var(--wa-font-size-l); to {
} scale: 1;
opacity: 1;
/* 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;
} }

View File

@@ -1,405 +1,9 @@
import { expect, waitUntil } from '@open-wc/testing'; import { expect, fixture, html } 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';
describe('<wa-dropdown>', () => { describe('<wa-dropdown>', () => {
for (const fixture of fixtures) { it('should render a component', async () => {
describe(`with "${fixture.type}" rendering`, () => { const el = await fixture(html` <wa-dropdown></wa-dropdown> `);
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"]')!;
expect(panel.hidden).to.be.false; expect(el).to.exist;
}); });
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;
});
});
}
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -129,8 +129,8 @@ textarea {
} }
} }
.prefix, .start,
.suffix { .end {
display: inline-flex; display: inline-flex;
flex: 0 0 auto; flex: 0 0 auto;
align-items: center; align-items: center;
@@ -141,11 +141,11 @@ textarea {
} }
} }
.prefix::slotted(*) { .start::slotted(*) {
margin-inline-end: var(--wa-form-control-padding-inline); margin-inline-end: var(--wa-form-control-padding-inline);
} }
.suffix::slotted(*) { .end::slotted(*) {
margin-inline-start: var(--wa-form-control-padding-inline); margin-inline-start: var(--wa-form-control-padding-inline);
} }

View File

@@ -25,8 +25,8 @@ import styles from './input.css';
* @dependency wa-icon * @dependency wa-icon
* *
* @slot label - The input's label. Alternatively, you can use the `label` attribute. * @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 start - An element, such as `<wa-icon>`, placed at the start of the input control.
* @slot suffix - Used to append a presentational icon or similar element to the input. * @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 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 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. * @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 hint - The hint's wrapper.
* @csspart input - The wrapper being rendered as an input * @csspart input - The wrapper being rendered as an input
* @csspart base - The internal `<input>` control. * @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 clear-button - The clear button.
* @csspart password-toggle-button - The password toggle 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 --background-color - The input's background color.
* @cssproperty --border-color - The color of the input's borders. * @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; @property({ attribute: 'with-hint', type: Boolean }) withHint = false;
private handleChange(event: Event) { private handleChange(event: Event) {
this.relayNativeEvent(event, { bubbles: true, composed: true });
this.value = this.input.value; this.value = this.input.value;
this.relayNativeEvent(event, { bubbles: true, composed: true });
} }
private handleClearClick(event: MouseEvent) { private handleClearClick(event: MouseEvent) {
@@ -232,9 +233,12 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
if (this.value !== '') { if (this.value !== '') {
this.value = ''; this.value = '';
this.dispatchEvent(new WaClearEvent());
this.dispatchEvent(new InputEvent('input')); this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); 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(); this.input.focus();
@@ -356,7 +360,7 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
</label> </label>
<div part="input" class="text-field"> <div part="input" class="text-field">
<slot name="prefix" part="prefix" class="prefix"></slot> <slot name="start" part="start" class="start"></slot>
<input <input
part="base" 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> </div>
<slot <slot

View File

@@ -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;
}

View File

@@ -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;
});
});
}
});

View File

@@ -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 options 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;
}
}

View File

@@ -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>
`;
}
}

View File

@@ -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;
}

View File

@@ -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();
});
});
}
});

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
});
});
}
});

View File

@@ -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;
}
}

View File

@@ -62,18 +62,18 @@
visibility: visible; visibility: visible;
} }
.prefix, .start,
.suffix { .end {
flex: 0 0 auto; flex: 0 0 auto;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.prefix::slotted(*) { .start::slotted(*) {
margin-inline-end: 0.5em; margin-inline-end: 0.5em;
} }
.suffix::slotted(*) { .end::slotted(*) {
margin-inline-start: 0.5em; margin-inline-start: 0.5em;
} }

View File

@@ -8,7 +8,7 @@ import '../icon/icon.js';
import styles from './option.css'; 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 * @documentation https://backers.webawesome.com/docs/components/option
* @status stable * @status stable
* @since 2.0 * @since 2.0
@@ -16,8 +16,8 @@ import styles from './option.css';
* @dependency wa-icon * @dependency wa-icon
* *
* @slot - The option's label. * @slot - The option's label.
* @slot prefix - Used to prepend an icon or similar element to the menu item. * @slot start - An element, such as `<wa-icon>`, placed before the label.
* @slot suffix - Used to append an icon or similar element to the menu item. * @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-current - The current option's background color.
* @cssproperty --background-color-hover - The options's background color on hover. * @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 checked-icon - The checked icon, a `<wa-icon>` element.
* @csspart label - The option's label. * @csspart label - The option's label.
* @csspart prefix - The container that wraps the prefix. * @csspart start - The container that wraps the `start` slot.
* @csspart suffix - The container that wraps the suffix. * @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 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" * @cssstate selected - The option is selected and has aria-selected="true"
@@ -192,9 +192,9 @@ export default class WaOption extends WebAwesomeElement {
variant="solid" variant="solid"
aria-hidden="true" aria-hidden="true"
></wa-icon> ></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="label" class="label" @slotchange=${this.handleDefaultSlotChange}></slot>
<slot part="suffix" name="suffix" class="suffix"></slot> <slot part="end" name="end" class="end"></slot>
`; `;
} }
} }

View File

@@ -171,8 +171,10 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
} }
if (this.value !== oldValue) { if (this.value !== oldValue) {
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 }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
} }
}; };
@@ -274,8 +276,10 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
} }
if (this.value !== oldValue) { if (this.value !== oldValue) {
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 }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
} }
event.preventDefault(); event.preventDefault();

View File

@@ -101,7 +101,9 @@ export default class WaRating extends WebAwesomeElement {
} }
this.setValue(this.getValueFromMousePosition(event)); 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) { private setValue(newValue: number) {
@@ -145,7 +147,9 @@ export default class WaRating extends WebAwesomeElement {
} }
if (this.value !== oldValue) { 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) { private handleTouchEnd(event: TouchEvent) {
this.isHovering = false; this.isHovering = false;
this.setValue(this.hoverValue); 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 // Prevent click on mobile devices
event.preventDefault(); event.preventDefault();

View File

@@ -158,25 +158,25 @@ label:has(select),
} }
} }
/* Prefix and Suffix */ /* Start and End */
.prefix, .start,
.suffix { .end {
flex: 0; flex: 0;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
color: var(--wa-color-neutral-on-quiet); color: var(--wa-color-neutral-on-quiet);
} }
.suffix::slotted(*) { .end::slotted(*) {
margin-inline-start: var(--wa-form-control-padding-inline); margin-inline-start: var(--wa-form-control-padding-inline);
} }
.prefix::slotted(*) { .start::slotted(*) {
margin-inline-end: var(--wa-form-control-padding-inline); 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); margin-inline: var(--wa-form-control-padding-inline);
} }

View File

@@ -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 - 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 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 start - An element, such as `<wa-icon>`, placed at the start of the combobox.
* @slot suffix - Used to append a presentational icon or similar element to 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 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 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. * @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-label - The label's wrapper.
* @csspart form-control-input - The select's wrapper. * @csspart form-control-input - The select's wrapper.
* @csspart hint - The hint's wrapper. * @csspart hint - The hint's wrapper.
* @csspart combobox - The container the wraps the prefix, suffix, combobox, clear icon, and expand button. * @csspart combobox - The container the wraps the start, end, value, clear icon, and expand button.
* @csspart prefix - The container that wraps the prefix slot. * @csspart start - The container that wraps the `start` slot.
* @csspart suffix - The container that wraps the suffix 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 display-input - The element that displays the selected option's label, an `<input>` element.
* @csspart listbox - The listbox container where options are slotted. * @csspart listbox - The listbox container where options are slotted.
* @csspart tags - The container that houses option tags when `multiselect` is used. * @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 // Emit after updating
this.updateComplete.then(() => { 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 })); this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
}); });
@@ -511,7 +511,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Emit after update // Emit after update
this.updateComplete.then(() => { this.updateComplete.then(() => {
this.dispatchEvent(new WaClearEvent()); 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 })); this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
}); });
} }
@@ -542,7 +542,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
if (this.value !== oldValue) { if (this.value !== oldValue) {
// Emit after updating // Emit after updating
this.updateComplete.then(() => { 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 })); this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
}); });
} }
@@ -600,7 +600,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Emit after updating // Emit after updating
this.updateComplete.then(() => { 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 })); this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
}); });
} }
@@ -900,7 +900,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
@keydown=${this.handleComboboxKeyDown} @keydown=${this.handleComboboxKeyDown}
@mousedown=${this.handleComboboxMouseDown} @mousedown=${this.handleComboboxMouseDown}
> >
<slot part="prefix" name="prefix" class="prefix"></slot> <slot part="start" name="start" class="start"></slot>
<input <input
part="display-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"> <slot name="expand-icon" part="expand-icon" class="expand-icon">
<wa-icon library="system" name="chevron-down" variant="solid"></wa-icon> <wa-icon library="system" name="chevron-down" variant="solid"></wa-icon>

View File

@@ -228,7 +228,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
}, },
stop: () => { stop: () => {
if (this.minValue !== this.valueWhenDraggingStarted) { 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.hasInteracted = true;
} }
this.hideRangeTooltips(); this.hideRangeTooltips();
@@ -251,7 +253,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
}, },
stop: () => { stop: () => {
if (this.maxValue !== this.valueWhenDraggingStarted) { 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.hasInteracted = true;
} }
this.hideRangeTooltips(); this.hideRangeTooltips();
@@ -321,7 +325,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
if (this.activeThumb) { if (this.activeThumb) {
const currentValue = this.activeThumb === 'min' ? this.minValue : this.maxValue; const currentValue = this.activeThumb === 'min' ? this.minValue : this.maxValue;
if (currentValue !== this.valueWhenDraggingStarted) { 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; this.hasInteracted = true;
} }
} }
@@ -346,7 +352,10 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
}, },
stop: () => { stop: () => {
if (this.value !== this.valueWhenDraggingStarted) { 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.hasInteracted = true;
} }
this.hideTooltip(); this.hideTooltip();
@@ -602,8 +611,10 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
} }
// Dispatch events // Dispatch events
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true })); this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
this.hasInteracted = true; this.hasInteracted = true;
} }
@@ -625,7 +636,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
// Dispatch input events when the value changes by dragging // Dispatch input events when the value changes by dragging
if (this.value !== oldValue) { 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 // Dispatch input events
if (oldValue !== (thumb === 'min' ? this.minValue : this.maxValue)) { if (oldValue !== (thumb === 'min' ? this.minValue : this.maxValue)) {
this.dispatchEvent(new InputEvent('input'));
this.updateFormValue(); this.updateFormValue();
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
});
} }
} }

View File

@@ -117,22 +117,29 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement {
private handleClick() { private handleClick() {
this.hasInteracted = true; this.hasInteracted = true;
this.checked = !this.checked; 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) { private handleKeyDown(event: KeyboardEvent) {
if (event.key === 'ArrowLeft') { if (event.key === 'ArrowLeft') {
event.preventDefault(); event.preventDefault();
this.checked = false; this.checked = false;
this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input')); this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
});
} }
if (event.key === 'ArrowRight') { if (event.key === 'ArrowRight') {
event.preventDefault(); event.preventDefault();
this.checked = true; 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 }));
});
} }
} }

View File

@@ -206,13 +206,14 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
this.valueHasChanged = true; this.valueHasChanged = true;
this.value = this.input.value; this.value = this.input.value;
this.setTextareaDimensions(); this.setTextareaDimensions();
this.relayNativeEvent(event, { bubbles: true, composed: true });
this.checkValidity(); this.checkValidity();
this.relayNativeEvent(event, { bubbles: true, composed: true });
} }
private handleInput() { private handleInput(event: InputEvent) {
this.valueHasChanged = true; this.valueHasChanged = true;
this.value = this.input.value; this.value = this.input.value;
this.relayNativeEvent(event, { bubbles: true, composed: true });
} }
private setTextareaDimensions() { private setTextareaDimensions() {

View File

@@ -1,5 +1,3 @@
import type WaMenuItem from '../components/menu-item/menu-item.js';
export class WaSelectEvent extends Event { export class WaSelectEvent extends Event {
readonly detail; readonly detail;
@@ -10,7 +8,7 @@ export class WaSelectEvent extends Event {
} }
interface WaSelectEventDetail { interface WaSelectEventDetail {
item: WaMenuItem; item: Element;
} }
declare global { declare global {

View File

@@ -14,6 +14,9 @@ export function animateWithClass(el: Element, className: string) {
const controller = new AbortController(); const controller = new AbortController();
const { signal } = controller; const { signal } = controller;
if (el.classList.contains(className)) {
return;
}
el.classList.remove(className); el.classList.remove(className);
el.classList.add(className); el.classList.add(className);

View File

@@ -16,23 +16,6 @@
--wa-color-brand-10: var(--wa-color-blue-10); --wa-color-brand-10: var(--wa-color-blue-10);
--wa-color-brand-05: var(--wa-color-blue-05); --wa-color-brand-05: var(--wa-color-blue-05);
--wa-color-brand: var(--wa-color-blue); --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); --wa-color-brand-on: var(--wa-color-blue-on);
} }
} }

View File

@@ -16,23 +16,6 @@
--wa-color-brand-10: var(--wa-color-cyan-10); --wa-color-brand-10: var(--wa-color-cyan-10);
--wa-color-brand-05: var(--wa-color-cyan-05); --wa-color-brand-05: var(--wa-color-cyan-05);
--wa-color-brand: var(--wa-color-cyan); --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); --wa-color-brand-on: var(--wa-color-cyan-on);
} }
} }

View File

@@ -16,23 +16,6 @@
--wa-color-brand-10: var(--wa-color-gray-10); --wa-color-brand-10: var(--wa-color-gray-10);
--wa-color-brand-05: var(--wa-color-gray-05); --wa-color-brand-05: var(--wa-color-gray-05);
--wa-color-brand: var(--wa-color-gray); --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); --wa-color-brand-on: var(--wa-color-gray-on);
} }
} }

View File

@@ -16,23 +16,6 @@
--wa-color-brand-10: var(--wa-color-green-10); --wa-color-brand-10: var(--wa-color-green-10);
--wa-color-brand-05: var(--wa-color-green-05); --wa-color-brand-05: var(--wa-color-green-05);
--wa-color-brand: var(--wa-color-green); --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); --wa-color-brand-on: var(--wa-color-green-on);
} }
} }

View File

@@ -16,23 +16,6 @@
--wa-color-brand-10: var(--wa-color-indigo-10); --wa-color-brand-10: var(--wa-color-indigo-10);
--wa-color-brand-05: var(--wa-color-indigo-05); --wa-color-brand-05: var(--wa-color-indigo-05);
--wa-color-brand: var(--wa-color-indigo); --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); --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