Compare commits

..

22 Commits

Author SHA1 Message Date
Cory LaViska
bcb139dcc1 update changelog 2025-03-24 16:27:18 -04:00
Cory LaViska
c66f241215 add allDefined util 2025-03-24 16:27:14 -04:00
Cory LaViska
4ea92905e8 simplify fouce util 2025-03-24 16:08:10 -04:00
Cory LaViska
c64a1754d7 no need to remove cloak class 2025-03-24 15:54:38 -04:00
Cory LaViska
a2f4197098 update fouce util 2025-03-24 15:49:25 -04:00
Cory LaViska
b0cf8bffa8 Merge branch 'next' into fouce-class 2025-03-24 15:44:16 -04:00
konnorrogers
65df1416dd workflow dispatch 2025-02-07 11:37:31 -05:00
konnorrogers
d271929e50 fix test suite 2025-02-07 11:31:05 -05:00
Cory LaViska
70b486fa96 disable SSR 2025-02-06 16:33:54 -05:00
Cory LaViska
884e11c6d7 disable SSR and add Turbo FOUCE helper 2025-02-06 16:22:29 -05:00
Cory LaViska
c0f558f52a reduce fade 2025-02-06 16:13:15 -05:00
Cory LaViska
af96d869ee move turbo to same file 2025-02-06 15:52:28 -05:00
Cory LaViska
d626d2c693 wait a cycle 2025-02-06 15:52:04 -05:00
Cory LaViska
96013f2d55 add cloak class 2025-02-06 15:29:33 -05:00
Cory LaViska
0fcc9390f6 remove class as requested 2025-02-06 15:24:02 -05:00
Cory LaViska
2a488d28b0 Merge branch 'fouce-class' of https://github.com/shoelace-style/webawesome into fouce-class 2025-02-06 12:02:44 -05:00
Cory LaViska
bcc1ccaa1c rename wa-reduce-fouce to wa-cloak 2025-02-06 12:01:52 -05:00
Cory LaViska
aa3cd97dde commit PR suggestion 2025-02-06 12:01:01 -05:00
Cory LaViska
16c5489f7a Update docs/docs/installation.md
Co-authored-by: Lea Verou <lea@verou.me>
2025-02-06 11:58:04 -05:00
Cory LaViska
02d0c1be75 Merge branch 'next' into fouce-class 2025-02-06 11:57:50 -05:00
Cory LaViska
db08435739 add comment 2025-02-04 13:29:22 -05:00
Cory LaViska
72a6d8544d add fouce utilities 2025-02-04 12:56:52 -05:00
32 changed files with 211 additions and 738 deletions

View File

@@ -7,10 +7,10 @@ import { highlightCodePlugin } from './_utils/highlight-code.js';
import { markdown } from './_utils/markdown.js';
import { removeDataAlphaElements } from './_utils/remove-data-alpha-elements.js';
// import { formatCodePlugin } from './_utils/format-code.js';
// import litPlugin from '@lit-labs/eleventy-plugin-lit';
import litPlugin from '@lit-labs/eleventy-plugin-lit';
import { readFile } from 'fs/promises';
import nunjucks from 'nunjucks';
// import componentList from './_data/componentList.js';
import componentList from './_data/componentList.js';
import * as filters from './_utils/filters.js';
import { outlinePlugin } from './_utils/outline.js';
import { replaceTextPlugin } from './_utils/replace-text.js';
@@ -29,6 +29,7 @@ const globalData = {
package: packageData,
isAlpha,
layout: 'page.njk',
server: {
head: '',
loginOrAvatar: '',
@@ -189,30 +190,30 @@ export default function (eleventyConfig) {
eleventyConfig.addPassthroughCopy(glob);
}
// // SSR plugin
// // Make sure this is the last thing, we don't want to run the risk of accidentally transforming shadow roots with the nunjucks 2nd transform.
// if (!isDev) {
// //
// // Problematic components in SSR land:
// // - animation (breaks on navigation + ssr with Turbo)
// // - mutation-observer (why SSR this?)
// // - resize-observer (why SSR this?)
// // - tooltip (why SSR this?)
// //
// const omittedModules = [];
// const componentModules = componentList
// .filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1]))
// .map(component => {
// const name = component.tagName.split(/wa-/)[1];
// const componentDirectory = process.env.UNBUNDLED_DIST_DIRECTORY || path.join('.', 'dist');
// return path.join(componentDirectory, 'components', name, `${name}.js`);
// });
//
// eleventyConfig.addPlugin(litPlugin, {
// mode: 'worker',
// componentModules,
// });
// }
// SSR plugin
// Make sure this is the last thing, we dont want to run the risk of accidentally transforming shadow roots with the nunjucks 2nd transform.
if (!isDev) {
//
// Problematic components in SSR land:
// - animation (breaks on navigation + ssr with Turbo)
// - mutation-observer (why SSR this?)
// - resize-observer (why SSR this?)
// - tooltip (why SSR this?)
//
const omittedModules = [];
const componentModules = componentList
.filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1]))
.map(component => {
const name = component.tagName.split(/wa-/)[1];
const componentDirectory = process.env.UNBUNDLED_DIST_DIRECTORY || path.join('.', 'dist');
return path.join(componentDirectory, 'components', name, `${name}.js`);
});
eleventyConfig.addPlugin(litPlugin, {
mode: 'worker',
componentModules,
});
}
return {
markdownTemplateEngine: 'njk',

View File

@@ -3,8 +3,8 @@
<section id="grid" class="index-grid">
{% set groupedPages = allPages | groupPages(categories, page) %}
{% for category, pages in groupedPages -%}
{% if groupedPages.meta.groupCount > 1 and pages.length > 0 %}
<h2 class="index-category" id="{{ category | slugify }}">
{% if groupedPages.meta.groupCount > 1 %}
<h2 class="index-category">
{% if pages.meta.url %}<a href="{{ pages.meta.url }}">{{ pages.meta.title }}</a>
{% else %}
{{ pages.meta.title }}

View File

@@ -24,9 +24,6 @@
<link rel="stylesheet" href="/assets/styles/hydration-errors.css">
<link rel="preconnect" href="https://cdn.jsdelivr.net">
{# Internal components #}
<script type="module" src="/assets/components/scoped.js"></script>
{# Web Awesome #}
<script type="module" src="/dist/webawesome.loader.js"></script>
@@ -50,5 +47,6 @@
<link id="color-stylesheet" rel="stylesheet" href="/dist/styles/utilities.css" />
<link rel="stylesheet" href="/dist/styles/forms.css" />
{# Used by Web Awesome App to inject other assets into the head. #}
{% server "head" %}

View File

@@ -1,4 +1,4 @@
{% if page | show -%}
{% if not (isAlpha and page.data.noAlpha) and not page.data.unlisted -%}
<li>
<a href="{{ page.url }}">{{ page.data.title }}</a>
{% if page.data.status == 'experimental' %}<wa-icon name="flask"></wa-icon>{% endif %}

View File

@@ -1,13 +1,13 @@
{% set themeId = theme.fileSlug %}
<wa-scoped class="theme-icon-host theme-color-icon-host">
<template>
<div>
<template shadowrootmode="open">
<link rel="stylesheet" href="/dist/styles/utilities.css">
<link rel="stylesheet" href="/dist/styles/themes/{{ page.fileSlug or 'default' }}.css">
<link rel="stylesheet" href="/dist/styles/themes/{{ themeId }}/color.css">
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
<div class="theme-icon theme-color-icon wa-theme-{{ themeId }}">
<div class="theme-color-icon wa-theme-{{ themeId }}">
<div class="wa-brand wa-accent">A</div>
<div class="wa-brand wa-outlined">A</div>
<div class="wa-brand wa-filled">A</div>
@@ -21,4 +21,4 @@
{# <div class="wa-warning wa-outlined wa-filled"><wa-icon slot="icon" name="triangle-exclamation" variant="regular"></wa-icon></div> #}
</div>
</template>
</wa-scoped>
</div>

View File

@@ -1,16 +1,16 @@
{% set themeId = theme.fileSlug or page.fileSlug %}
{% set themeId = theme.fileSlug %}
<wa-scoped class="theme-icon-host theme-typography-icon-host">
<template>
<div>
<template shadowrootmode="open">
<link rel="stylesheet" href="/dist/styles/native/content.css">
<link rel="stylesheet" href="/dist/styles/native/blockquote.css">
<link rel="stylesheet" href="/dist/styles/themes/{{ page.fileSlug or 'default' }}.css">
<link rel="stylesheet" href="/dist/styles/themes/{{ themeId }}/typography.css">
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
<div class="theme-icon theme-typography-icon wa-theme-{{ themeId }}" data-no-outline data-no-anchor role="presentation">
<div class="theme-typography-icon wa-theme-{{ themeId }}" data-no-outline data-no-anchor role="presentation">
<h3>Title</h3>
<p>Body text</p>
</div>
</template>
</wa-scoped>
</div>

View File

@@ -1,29 +0,0 @@
{% set themeId = theme.fileSlug or page.fileSlug %}
<wa-scoped class="theme-icon-host theme-overall-icon-host">
<template>
<link rel="stylesheet" href="/dist/styles/utilities.css">
<link rel="stylesheet" href="/dist/styles/native/content.css">
<link rel="stylesheet" href="/dist/styles/themes/{{ themeId }}.css">
<link rel="stylesheet" href="/assets/styles/theme-icons.css">
<div class="theme-icon theme-overall-icon" role="presentation" data-no-anchor data-no-outline>
<div class="row row-1">
<h2>Aa</h2>
<div class="swatches">
<div class="wa-brand"></div>
<div class="wa-success"></div>
<div class="wa-warning"></div>
<div class="wa-danger"></div>
<div class="wa-neutral"></div>
</div>
</div>
<div class="row row-2">
<wa-input value="Input" size="small"></wa-input>
<wa-button size="small" variant="brand">Go</wa-button>
</div>
</div>
</template>
</wa-scoped>

View File

@@ -178,10 +178,6 @@ export function sort(arr, by = { 'data.order': 1, 'data.title': '' }) {
});
}
export function show(page) {
return !(page.data.noAlpha && page.data.isAlpha) && !page.data.unlisted;
}
/**
* Group an 11ty collection (or any array of objects with a `data.tags` property) by certain tags.
* @param {object[]} collection
@@ -202,7 +198,7 @@ export function groupPages(collection, options = {}, page) {
options = { tags: options };
}
let { tags, groups, titles = {}, other = 'Other', filter = show } = options;
let { tags, groups, titles = {}, other = 'Other' } = options;
if (groups === undefined && Array.isArray(tags)) {
groups = tags;
@@ -241,10 +237,6 @@ export function groupPages(collection, options = {}, page) {
let byUrl = {};
let byParentUrl = {};
if (filter) {
collection = collection.filter(filter);
}
for (let item of collection) {
let url = item.page.url;
let parentUrl = item.data.parentUrl;
@@ -321,13 +313,6 @@ export function groupPages(collection, options = {}, page) {
if (sortedGroups) {
ret = sortObject(ret, sortedGroups);
} else {
// At least make sure other is last
if (ret.other) {
let otherGroup = ret.other;
delete ret.other;
ret.other = otherGroup;
}
}
Object.defineProperty(ret, 'meta', {

View File

@@ -39,7 +39,7 @@ export function outlinePlugin(options = {}) {
}
// Create a clone of the heading so we can remove links and [data-no-outline] elements from the text content
clone.querySelectorAll('.wa-visually-hidden, [hidden], [aria-hidden="true"]').forEach(el => el.remove());
clone.querySelectorAll('a').forEach(a => a.remove());
clone.querySelectorAll('[data-no-outline]').forEach(el => el.remove());
// Generate the link

View File

@@ -1,169 +0,0 @@
/**
* Low-level utility to encapsulate a bit of HTML (mainly to apply certain stylesheets to it without them leaking to the rest of the page)
* Usage: <wa-scoped><template><!-- your HTML here --></template></wa-scoped>
*/
// Map of <wa-scoped> elements to any global <style> elements theyve created
const imports = new Set();
const fontFaceRules = new Set();
export default class WaScoped extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.observer = new MutationObserver(() => this.render());
this.observer.observe(this, { childList: true, subtree: true, characterData: true });
}
connectedCallback() {
this.render();
this.ownerDocument.documentElement.addEventListener('wa-color-scheme-change', e =>
this.#applyDarkMode(e.detail.dark),
);
}
render() {
this.observer.takeRecords();
this.observer.disconnect();
this.shadowRoot.innerHTML = '';
// To avoid mutating this.childNodes while iterating over it
let nodes = [];
for (let template of this.childNodes) {
// Other solutions we can try if needed: <script type="text/html">, or comment nodes
if (template instanceof HTMLTemplateElement) {
if (template.content.childNodes.length > 0) {
nodes.push(template.content.cloneNode(true));
} else if (template.childNodes.length > 0) {
// Fake template, suck its children out of the light DOM
nodes.push(...template.childNodes);
}
} else {
// Regular child, suck it out of the light DOM
nodes.push(template);
}
}
this.shadowRoot.append(...nodes);
this.#fixStyles();
this.#applyDarkMode();
this.observer.observe(this, { childList: true, subtree: true, characterData: true });
}
#applyDarkMode(isDark = getComputedStyle(this).colorScheme === 'dark') {
// Hack to make dark mode work
// NOTE If any child nodes actually have .wa-dark, this will override it
for (let node of this.shadowRoot.children) {
node.classList.toggle('wa-dark', isDark);
}
this.classList.toggle('wa-dark', isDark);
}
/**
* @font-face does not work in shadow DOM in Chrome & FF, as of March 2025 https://issues.chromium.org/issues/41085401
* This works around this issue by traversing the shadow DOM CSS looking
* for @font-face rules or CSS imports to known font providers and copies them to the main document
*/
async #fixStyles() {
let styleElements = [...this.shadowRoot.querySelectorAll('link[rel="stylesheet"], style')];
let loadStates = styleElements.map(element => {
try {
if (element.sheet?.cssRules) {
// Already loaded
return Promise.resolve(element.sheet);
}
} catch (e) {
// CORS
return Promise.resolve(null);
}
return new Promise((resolve, reject) => {
element.addEventListener('load', e => resolve(element.sheet));
element.addEventListener('error', e => reject(null));
});
});
await Promise.allSettled(loadStates);
let fontRules = findFontFaceRules(...this.shadowRoot.styleSheets);
if (!fontRules.length) {
return;
}
let doc = this.ownerDocument;
// Why not adoptedStyleSheets? Can't have @import in those yet
let id = `wa-scoped-hoisted-fonts`;
let style = doc.head.querySelector('style#' + id);
if (!style) {
style = Object.assign(doc.createElement('style'), { id, textContent: ' ' });
doc.head.append(style);
}
let sheet = style.sheet;
for (let rule of fontRules) {
let cssText = rule.cssText;
if (rule.type === CSSRule.FONT_FACE_RULE) {
if (fontFaceRules.has(cssText)) {
continue;
}
fontFaceRules.add(cssText);
sheet.insertRule(cssText);
} else if (rule.type === CSSRule.IMPORT_RULE) {
if (imports.has(rule.href)) {
continue;
}
imports.add(rule.href);
sheet.insertRule(cssText, 0);
}
}
}
static observedAttributes = [];
}
customElements.define('wa-scoped', WaScoped);
export const WEB_FONT_HOSTS = [
'fonts.googleapis.com',
'fonts.gstatic.com',
'use.typekit.net',
'fonts.adobe.com',
'kit.fontawesome.com',
'pro.fontawesome.com',
'cdn.materialdesignicons.com',
];
function findFontFaceRules(...stylesheets) {
let ret = [];
for (let sheet of stylesheets) {
let rules;
try {
rules = sheet.cssRules;
} catch (e) {
// CORS
continue;
}
for (let rule of rules) {
if (rule.type === CSSRule.FONT_FACE_RULE) {
ret.push(rule);
} else if (rule.type === CSSRule.IMPORT_RULE) {
if (WEB_FONT_HOSTS.some(host => rule.href.includes(host))) {
ret.push(rule);
} else if (rule.styleSheet) {
ret.push(...findFontFaceRules(rule.styleSheet));
}
}
}
}
return ret;
}

View File

@@ -1,11 +1,3 @@
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
function updateResults(input) {
const filter = input.value.toLowerCase().trim();
let filtered = Boolean(filter);
@@ -26,10 +18,8 @@ function updateResults(input) {
}
}
const debouncedUpdateResults = debounce(updateResults, 300);
document.documentElement.addEventListener('input', e => {
if (e.target?.matches('#block-filter wa-input')) {
debouncedUpdateResults(e.target);
updateResults(e.target);
}
});

View File

@@ -1,30 +1,12 @@
let initialPageLoadComplete = false;
window.addEventListener('load', () => {
initialPageLoadComplete = true;
});
// Helper for view transitions
export function domChange(fn, { behavior = 'smooth', ignoreInitialLoad = true } = {}) {
export function domChange(fn, { behavior = 'smooth' } = {}) {
const canUseViewTransitions =
document.startViewTransition && !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// Skip transitions on initial page load
if (!initialPageLoadComplete && ignoreInitialLoad) {
fn(false);
return null;
}
if (canUseViewTransitions && behavior === 'smooth') {
const transition = document.startViewTransition(() => {
fn(true);
// Wait a brief delay before finishing the transition to prevent jumpiness
return new Promise(resolve => setTimeout(resolve, 200));
});
return transition;
document.startViewTransition(fn);
} else {
fn(false);
return null;
fn(true);
}
}
@@ -118,7 +100,6 @@ const colorScheme = new ThemeAspect({
domChange(() => {
let dark = this.computedValue === 'dark';
document.documentElement.classList.toggle(`wa-dark`, dark);
document.documentElement.dispatchEvent(new CustomEvent('wa-color-scheme-change', { detail: { dark } }));
});
},
});

View File

@@ -4,7 +4,6 @@
@import 'outline.css';
@import 'search.css';
@import 'cera_typeface.css';
@import 'theme-icons.css';
:root {
--wa-brand-orange: #f36944;
@@ -371,22 +370,10 @@ wa-page > main:has(> .index-grid) {
.index-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(auto-fit, minmax(min(22ch, 100%), 1fr));
gap: var(--wa-space-2xl);
margin-block-end: var(--wa-space-3xl);
@media screen and (max-width: 1470px) {
grid-template-columns: repeat(3, 1fr);
}
@media screen and (max-width: 960px) {
grid-template-columns: repeat(2, 1fr);
}
@media screen and (max-width: 500px) {
grid-template-columns: repeat(1, 1fr);
}
a {
border-radius: var(--wa-border-radius-l);
text-decoration: none;
@@ -413,6 +400,7 @@ wa-page > main:has(> .index-grid) {
&::part(header) {
background-color: var(--header-background, var(--wa-color-neutral-fill-quiet));
border-bottom: none;
display: flex;
align-items: center;
justify-content: center;

View File

@@ -7,9 +7,8 @@
margin: 0 auto;
overflow: hidden;
&::part(dialog) {
margin-block-start: 10vh;
margin-block-end: 0;
&::part(base) {
margin-block: 10rem;
}
&::part(body) {
@@ -24,20 +23,20 @@
@media screen and (max-width: 900px) {
max-width: calc(100% - 2rem);
&::part(dialog) {
&::part(base) {
margin-block: 1rem;
}
#site-search-container {
max-height: none;
}
}
}
#site-search-container {
display: flex;
flex-direction: column;
max-height: calc(100vh - 18rem);
max-height: calc(100vh - 20rem);
@media screen and (max-width: 900px) {
max-height: calc(100dvh - 2rem);
}
}
/* Header */

View File

@@ -1,37 +1,3 @@
wa-card:has(> .theme-icon-host, > [slot='header'] > .theme-icon-host) {
min-width: 22ch;
&::part(header) {
/* We want to add a background color, so any spacing needs to go on .theme-icon */
padding: 0;
min-block-size: 0;
}
[slot='header'] {
display: block;
width: 100%;
border-radius: inherit;
}
}
.theme-icon-host {
display: block;
border-radius: inherit;
}
.theme-icon {
padding: var(--wa-space-xs) var(--wa-space-m);
border-radius: inherit;
box-sizing: border-box;
h2,
h3,
p {
margin-block: 0;
padding: 0;
}
}
.theme-color-icon {
display: grid;
gap: var(--wa-space-xs);
@@ -59,50 +25,10 @@ wa-card:has(> .theme-icon-host, > [slot='header'] > .theme-icon-host) {
display: flex;
flex-direction: column;
gap: var(--wa-space-xs);
}
.theme-overall-icon {
display: flex;
flex-flow: column;
gap: var(--wa-space-xs);
justify-content: center;
width: 100%;
min-height: 7.5rem;
box-sizing: border-box;
background: var(--wa-color-surface-lowered);
.row {
display: flex;
gap: var(--wa-space-xs);
align-items: center;
justify-content: space-between;
}
.row-2 {
display: grid;
grid-template-columns: 1fr auto;
contain: inline-size;
width: 100%;
wa-input {
min-width: 1em;
}
}
.swatches {
display: flex;
gap: var(--wa-space-3xs);
> div {
width: 1.25rem;
height: 1.25rem;
border-radius: var(--wa-border-radius-s);
background: var(--wa-color-fill-loud);
color: var(--wa-color-on-loud);
&.wa-brand {
width: 2.5rem;
}
}
h3,
p {
margin-block: 0;
padding: 0;
}
}

View File

@@ -15,9 +15,9 @@ icon: card
<strong>Mittens</strong><br />
This kitten is as cute as he is playful. Bring him home today!<br />
<small class="wa-caption-m">6 weeks old</small>
<small>6 weeks old</small>
<div slot="footer" class="wa-split">
<div slot="footer">
<wa-button variant="brand" pill>More Info</wa-button>
<wa-rating label="Rating"></wa-rating>
</div>
@@ -27,6 +27,16 @@ icon: card
.card-overview {
width: 300px;
}
.card-overview small {
color: var(--wa-color-text-quiet);
}
.card-overview [slot='footer'] {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
```
@@ -55,9 +65,9 @@ If using SSR, you need to also use the `with-header` attribute to add a header t
```html {.example}
<wa-card with-header class="card-header">
<div slot="header" class="wa-split">
<div slot="header">
Header Title
<wa-icon-button name="gear" variant="solid" label="Settings" class="wa-size-m"></wa-icon-button>
<wa-icon-button name="gear" variant="solid" label="Settings"></wa-icon-button>
</div>
This card has a header. You can put all sorts of things in it!
@@ -68,9 +78,19 @@ If using SSR, you need to also use the `with-header` attribute to add a header t
max-width: 300px;
}
.card-header [slot='header'] {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-header h3 {
margin: 0;
}
.card-header wa-icon-button {
font-size: var(--wa-font-size-m);
}
</style>
```
@@ -83,7 +103,7 @@ If using SSR, you need to also use the `with-footer` attribute to add a footer t
<wa-card with-footer class="card-footer">
This card has a footer. You can put all sorts of things in it!
<div slot="footer" class="wa-split">
<div slot="footer">
<wa-rating></wa-rating>
<wa-button variant="brand">Preview</wa-button>
</div>
@@ -93,6 +113,12 @@ If using SSR, you need to also use the `with-footer` attribute to add a footer t
.card-footer {
max-width: 300px;
}
.card-footer [slot='footer'] {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
```
@@ -127,7 +153,7 @@ Use the `size` attribute to change a card's size.
<wa-card with-footer size="small">
This is a small card.
<footer slot="footer" class="wa-split">
<footer slot="footer" class="wa-flank">
<wa-button variant="brand" pill>More Info</wa-button>
<wa-rating></wa-rating>
</footer>
@@ -136,7 +162,7 @@ Use the `size` attribute to change a card's size.
<wa-card with-footer size="medium">
This is a medium card (default).
<footer slot="footer" class="wa-split">
<footer slot="footer" class="wa-flank">
<wa-button variant="brand" pill>More Info</wa-button>
<wa-rating></wa-rating>
</footer>
@@ -145,39 +171,14 @@ Use the `size` attribute to change a card's size.
<wa-card with-footer size="large">
This is a large card.
<footer slot="footer" class="wa-split">
<footer slot="footer" class="wa-flank">
<wa-button variant="brand" pill>More Info</wa-button>
<wa-rating></wa-rating>
</footer>
</wa-card>
</div>
```
### Appearance
Use the `appearance` attribute to change the card's visual appearance.
```html {.example}
<div class="wa-grid">
<wa-card>
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
alt="A kitten sits patiently between a terracotta pot and decorative grasses."
/>
<div slot="header">Outlined (default)</div>
Card content.
</wa-card>
{% for appearance in ['outlined filled', 'outlined accent', 'plain', 'filled', 'accent'] -%}
<wa-card appearance="{{ appearance }}">
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
alt="A kitten sits patiently between a terracotta pot and decorative grasses."
/>
<div slot="header">{{ appearance | capitalize }}</div>
Card content.
</wa-card>
{%- endfor %}
</div>
```
<style>
</style>

View File

@@ -77,31 +77,6 @@ The details component automatically adapts to right-to-left languages:
</wa-details>
```
### Appearance
Use the `appearance` attribute to change the elements visual appearance.
```html {.example}
<div class="wa-stack">
<wa-details summary="Outlined (default)">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</wa-details>
<wa-details summary="Filled" appearance="filled">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</wa-details>
<wa-details summary="Filled + Outlined" appearance="filled outlined">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</wa-details>
<wa-details summary="Plain" appearance="plain">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</wa-details>
</div>
```
### Grouping Details
Details are designed to function independently, but you can simulate a group or "accordion" where only one is shown at a time by listening for the `wa-show` event.

View File

@@ -33,7 +33,7 @@ Use the [variant utility classes](../utilities/color.md) to set the button's sem
### Appearance
Use the [appearance utility classes](/docs/utilities/appearance) to change the button's visual appearance:
Use the [appearance utility classes](../utilities/appearance.md) to change the button's visual appearance:
```html {.example}
<div style="margin-block-end: 1rem;">

View File

@@ -57,7 +57,7 @@ Use the [variant utility classes](../utilities/color.md) to set the callout's co
### Appearance
Use the [appearance utility classes](/docs/utilities/appearance) to change the callout's visual appearance (the default is `outlined filled`).
Use the [appearance utility classes](../utilities/appearance.md) to change the callout's visual appearance (the default is `outlined filled`).
```html {.example}
<article class="wa-callout wa-brand wa-outlined wa-accent">

View File

@@ -19,35 +19,6 @@ file: styles/native/details.css
## Examples
### Appearance
Use the [appearance utility classes](/docs/utilities/appearance) to change the element's visual appearance:
```html {.example}
<div class="wa-stack">
<details>
<summary>Outlined (default)</summary>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</details>
<details class="wa-filled">
<summary>Filled</summary>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</details>
<details class="wa-filled wa-outlined">
<summary>Filled + Outlined</summary>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</details>
<details class="wa-plain">
<summary>Plain</summary>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</details>
</div>
```
### Right-to-Left Languages
The details styling automatically adapts to right-to-left languages:

View File

@@ -14,22 +14,9 @@ During the alpha period, things might break! We take breaking changes very serio
## Next
- Fixed the search dialog's styles so it doesn't jump around as you search
- Removed close watcher logic to backdrop hide animation bugs in `<wa-dialog>` and `<wa-drawer>`; this logic is already handled and we'll revisit `CloseWatcher` when browser support is better and behaviors are consistent
- Revert `<wa-dialog>` structure and CSS to fix clipped content in dialogs (WA-A #123) and light dismiss in iOS Safari (WA-A #201)
### Enhancements
- Added `appearance` to [`<wa-details>`](/docs/components/details) and [`<wa-card>`](/docs/components/card) and support for the [appearance utilities](/docs/utilities/appearance/) in the [`<details>` native styles](/docs/native/details).
- Added an `orange` scale to all color palettes
- Added the `.wa-cloak` utility to prevent FOUCE
- Added the `wa-cloak` utility to prevent FOUCE
- Added the `allDefined()` utility for awaiting component registration
### Bugfixes
- Specifying inherited CSS properties on `<wa-tooltip>` now works as expected ([thanks Dennis!](https://github.com/shoelace-style/webawesome-alpha/discussions/203))
- Fixed a bug in `<wa-select>` that made it hard to use with VueJS, Svelte, and many other frameworks
- Fixed the `wa-pill` class for text fields
- Fixed `wa-pill` class for text fields
- Fixed `pill` style for `<wa-input>` elements
- Fixed a bug in `<wa-color-picker>` that prevented light dismiss from working when clicking immediately above the color picker dropdown
- Fixed a bug in `<wa-select multiple>` that sometimes resulted in empty `<div>` elements being output

View File

@@ -3,7 +3,6 @@
"wide": true,
"tags": ["themes", "theme"],
"brand": "blue",
"icon": "theme",
"eleventyComputed": {
"file": "styles/themes/{{ page.fileSlug }}.css"
}

View File

@@ -5,16 +5,15 @@
--spacing: var(--wa-space);
--border-width: var(--wa-panel-border-width);
--outlined-background-color: var(--wa-color-surface-default);
--outlined-border-color: var(--wa-color-surface-border);
--border-color: var(--wa-color-surface-border);
--border-radius: var(--wa-panel-border-radius);
--inner-border-radius: calc(var(--border-radius) - var(--border-width));
--inner-border-color: var(--outlined-border-color);
display: flex;
flex-direction: column;
background-color: var(--background-color, var(--wa-color-surface-default));
border-color: var(--border-color, var(--wa-color-surface-border));
background-color: var(--wa-color-surface-default);
border-color: var(--border-color);
border-radius: var(--border-radius);
border-style: var(--wa-panel-border-style);
box-shadow: var(--wa-shadow-s);
@@ -22,20 +21,6 @@
color: var(--wa-color-text-normal);
}
:host(:is([appearance~='accent'], .wa-accent)) {
color: var(--text-color, var(--wa-color-text-normal));
}
:host([appearance~='filled']),
:host(.wa-filled) {
--inner-border-color: oklab(from var(--outlined-border-color) l a b / 65%);
}
:host([appearance='plain']) {
--inner-border-color: transparent;
box-shadow: none;
}
/* Take care of top and bottom radii */
.image,
:host(:not([with-image])) .header,
@@ -61,19 +46,10 @@
}
}
/* Round all corners for plain appearance */
:host([appearance='plain']) .image {
border-radius: var(--inner-border-radius);
&::slotted(img) {
border-radius: inherit !important;
}
}
.header {
display: block;
border-block-end-style: inherit;
border-block-end-color: var(--inner-border-color);
border-block-end-color: var(--border-color);
border-block-end-width: var(--border-width);
padding: calc(var(--spacing) / 2) var(--spacing);
}
@@ -86,7 +62,7 @@
.footer {
display: block;
border-block-start-style: inherit;
border-block-start-color: var(--inner-border-color);
border-block-start-color: var(--border-color);
border-block-start-width: var(--border-width);
padding: var(--spacing);
}

View File

@@ -2,7 +2,6 @@ import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { HasSlotController } from '../../internal/slot.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import appearanceStyles from '../../styles/utilities/appearance.css';
import sizeStyles from '../../styles/utilities/size.css';
import styles from './card.css';
@@ -23,24 +22,19 @@ import styles from './card.css';
* @csspart footer - The container that wraps the card's footer.
*
* @cssproperty [--border-radius=var(--wa-panel-border-radius)] - The radius for the card's corners. Expects a single value.
* @cssproperty [--border-color=var(--wa-color-surface-border)] - The color of the card's borders. Expects a single value.
* @cssproperty [--inner-border-color=var(--wa-color-surface-border)] - The color of the card's inner borders, e.g. those separating headers and footers from the main content. Expects a single value.
* @cssproperty [--border-color=var(--wa-color-surface-border)] - The color of the card's borders, including inner borders. Expects a single value.
* @cssproperty [--border-width=var(--wa-panel-border-width)] - The width of the card's borders. Expects a single value.
* @cssproperty [--spacing=var(--wa-space)] - The amount of space around and between sections of the card. Expects a single value.
*/
@customElement('wa-card')
export default class WaCard extends WebAwesomeElement {
static shadowStyle = [sizeStyles, appearanceStyles, styles];
static shadowStyle = [sizeStyles, styles];
private readonly hasSlotController = new HasSlotController(this, 'footer', 'header', 'image');
/** The component's size. Will be inherited by any descendants with a `size` attribute. */
@property({ reflect: true, initial: 'medium' }) size: 'small' | 'medium' | 'large' | 'inherit' = 'inherit';
/** The card's visual appearance. */
@property({ reflect: true })
appearance: 'accent' | 'filled' | 'outlined' | 'plain' = 'outlined';
/** Renders the card with a header. Only needed for SSR, otherwise is automatically added. */
@property({ attribute: 'with-header', type: Boolean, reflect: true }) withHeader = false;

View File

@@ -9,7 +9,6 @@ import { getTargetElement, waitForEvent } from '../../internal/event.js';
import { watch } from '../../internal/watch.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import nativeStyles from '../../styles/native/details.css';
import appearanceStyles from '../../styles/utilities/appearance.css';
import { LocalizeController } from '../../utilities/localize.js';
import '../icon/icon.js';
import styles from './details.css';
@@ -46,7 +45,7 @@ import styles from './details.css';
*/
@customElement('wa-details')
export default class WaDetails extends WebAwesomeElement {
static shadowStyle = [appearanceStyles, nativeStyles, styles];
static shadowStyle = [nativeStyles, styles];
private detailsObserver: MutationObserver;
private readonly localize = new LocalizeController(this);
@@ -68,9 +67,6 @@ export default class WaDetails extends WebAwesomeElement {
/** Disables the details so it can't be toggled. */
@property({ type: Boolean, reflect: true }) disabled = false;
/** The element's visual appearance. */
@property({ reflect: true }) appearance: 'filled' | 'outlined' | 'plain' = 'outlined';
firstUpdated() {
this.body.style.height = this.open ? 'auto' : '0';
if (this.open) {

View File

@@ -1,89 +1,24 @@
:host {
--background-color: var(--wa-color-surface-raised);
--border-radius: var(--wa-panel-border-radius);
--box-shadow: var(--wa-shadow-l);
--width: 31rem;
--spacing: var(--wa-space-xl);
--show-duration: 200ms;
--hide-duration: 200ms;
display: contents;
}
:host(:not([open])) {
display: none;
}
:host([open]) {
display: block;
}
.dialog {
display: flex;
flex-direction: column;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: var(--width);
max-width: calc(100% - var(--wa-space-2xl));
max-height: calc(100% - var(--wa-space-2xl));
background-color: var(--background-color);
border-radius: var(--border-radius);
border: none;
box-shadow: var(--box-shadow);
padding: 0;
dialog {
width: inherit;
max-width: inherit;
max-height: inherit;
background-color: inherit;
border-radius: inherit;
border: inherit;
box-shadow: inherit;
padding: inherit;
margin: auto;
&.show {
animation: show-dialog var(--show-duration) ease;
&::backdrop {
animation: show-backdrop var(--show-duration, 200ms) ease;
}
}
&.hide {
animation: show-dialog var(--hide-duration) ease reverse;
&::backdrop {
animation: show-backdrop var(--hide-duration, 200ms) ease reverse;
}
}
&.pulse {
animation: pulse 250ms ease;
}
}
.dialog:focus {
outline: none;
}
/* Ensure there's enough vertical padding for phones that don't update vh when chrome appears (e.g. iPhone) */
@media screen and (max-width: 420px) {
.dialog {
max-height: 80vh;
}
}
.dialog--open {
display: flex;
opacity: 1;
}
.header {
flex: 0 0 auto;
display: flex;
flex-wrap: nowrap;
padding: var(--spacing);
padding-block-end: 0;
}
.title {
align-self: center;
flex: 1 1 auto;
font-family: inherit;
font-size: var(--wa-font-size-l);
font-weight: var(--wa-font-weight-heading);
line-height: var(--wa-line-height-condensed);
margin: 0;
transition: inherit;
}
.header-actions {
@@ -93,81 +28,13 @@
flex-wrap: wrap;
justify-content: end;
gap: var(--wa-space-2xs);
padding-inline-start: var(--spacing);
}
margin-inline-start: auto;
.header-actions wa-icon-button,
.header-actions ::slotted(wa-icon-button) {
flex: 0 0 auto;
display: flex;
align-items: center;
font-size: var(--wa-font-size-m);
}
.body {
flex: 1 1 auto;
display: block;
padding: var(--spacing);
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.footer {
flex: 0 0 auto;
display: flex;
flex-wrap: wrap;
gap: var(--wa-space-xs);
justify-content: end;
padding: var(--spacing);
padding-block-start: 0;
}
.footer ::slotted(wa-button:not(:first-of-type)) {
margin-inline-start: var(--wa-spacing-xs);
}
.dialog::backdrop {
/*
NOTE: the ::backdrop element doesn't inherit properly in Safari yet, but it will in 17.4! At that time, we can
remove the fallback values here.
*/
background-color: var(--wa-color-overlay-modal, rgb(0 0 0 / 0.25));
}
@keyframes pulse {
0% {
scale: 1;
}
50% {
scale: 1.02;
}
100% {
scale: 1;
}
}
@keyframes show-dialog {
from {
opacity: 0;
scale: 0.8;
}
to {
opacity: 1;
scale: 1;
}
}
@keyframes show-backdrop {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@media (forced-colors: active) {
.dialog {
border: solid 1px white;
wa-icon-button,
::slotted(wa-icon-button) {
flex: 0 0 auto;
display: flex;
align-items: center;
font-size: var(--wa-font-size-m);
}
}

View File

@@ -1,6 +1,5 @@
import { html, isServer } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { customElement, property, query, state } from 'lit/decorators.js';
import { WaAfterHideEvent } from '../../events/after-hide.js';
import { WaAfterShowEvent } from '../../events/after-show.js';
import { WaHideEvent } from '../../events/hide.js';
@@ -9,6 +8,7 @@ import { animateWithClass } from '../../internal/animate.js';
import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll.js';
import { watch } from '../../internal/watch.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import dialogStyles from '../../styles/native/dialog.css';
import { LocalizeController } from '../../utilities/localize.js';
import '../icon-button/icon-button.js';
import styles from './dialog.css';
@@ -35,6 +35,7 @@ import styles from './dialog.css';
* behavior such as data loss.
* @event wa-after-hide - Emitted after the dialog closes and all animations are complete.
*
* @csspart base - The inner `<dialog>` used to render this component.
* @csspart header - The dialog's header. This element wraps the title and header actions.
* @csspart header-actions - Optional actions to add to the header. Works best with `<wa-icon-button>`.
* @csspart title - The dialog's title.
@@ -43,22 +44,16 @@ import styles from './dialog.css';
* @csspart body - The dialog's body.
* @csspart footer - The dialog's footer.
*
* @cssproperty --background-color - The dialog's background color.
* @cssproperty --border-radius - The radius of the dialog's corners.
* @cssproperty --box-shadow - The shadow effects around the edges of the dialog.
* @cssproperty --spacing - The amount of space around and between the dialog's content.
* @cssproperty --width - The preferred width of the dialog. Note that the dialog will shrink to accommodate smaller screens.
* @cssproperty [--show-duration=200ms] - The animation duration when showing the dialog.
* @cssproperty [--hide-duration=200ms] - The animation duration when hiding the dialog.
*/
@customElement('wa-dialog')
export default class WaDialog extends WebAwesomeElement {
static shadowStyle = styles;
static shadowStyle = [dialogStyles, styles];
private readonly localize = new LocalizeController(this);
private originalTrigger: HTMLElement | null;
private closeWatcher: CloseWatcher | null;
@query('.dialog') dialog: HTMLDialogElement;
@query('dialog') dialog: HTMLDialogElement;
/**
* Indicates whether or not the dialog is open. You can toggle this attribute to show and hide the dialog, or you can
@@ -81,6 +76,9 @@ export default class WaDialog extends WebAwesomeElement {
/** When enabled, the dialog will be closed when the user clicks outside of it. */
@property({ attribute: 'light-dismiss', type: Boolean }) lightDismiss = false;
@state()
hasOpened = this.open;
firstUpdated() {
if (this.open) {
this.addOpenListeners();
@@ -102,14 +100,12 @@ export default class WaDialog extends WebAwesomeElement {
if (waHideEvent.defaultPrevented) {
this.open = true;
animateWithClass(this.dialog, 'pulse');
animateWithClass(this.dialog, 'wa-dialog-pulse');
return;
}
this.removeOpenListeners();
await animateWithClass(this.dialog, 'hide');
this.open = false;
this.dialog.close();
unlockBodyScrolling(this);
@@ -124,7 +120,16 @@ export default class WaDialog extends WebAwesomeElement {
}
private addOpenListeners() {
document.addEventListener('keydown', this.handleDocumentKeyDown);
if ('CloseWatcher' in window) {
this.closeWatcher?.destroy();
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => {
this.requestClose(this.dialog);
};
} else {
this.closeWatcher?.destroy();
document.addEventListener('keydown', this.handleDocumentKeyDown);
}
}
private removeOpenListeners() {
@@ -156,7 +161,7 @@ export default class WaDialog extends WebAwesomeElement {
if (this.lightDismiss) {
this.requestClose(this.dialog);
} else {
await animateWithClass(this.dialog, 'pulse');
await animateWithClass(this.dialog, 'wa-dialog-pulse');
}
}
}
@@ -193,6 +198,7 @@ export default class WaDialog extends WebAwesomeElement {
this.addOpenListeners();
this.originalTrigger = document.activeElement as HTMLElement;
this.open = true;
this.hasOpened = true;
this.dialog.showModal();
lockBodyScrolling(this);
@@ -205,28 +211,20 @@ export default class WaDialog extends WebAwesomeElement {
}
});
await animateWithClass(this.dialog, 'show');
this.dispatchEvent(new WaAfterShowEvent());
}
render() {
return html`
<dialog
part="dialog"
class=${classMap({
dialog: true,
'dialog--open': this.open,
'dialog--with-header': this.withHeader,
'dialog--with-footer': this.withFooter,
})}
part="base"
@cancel=${this.handleDialogCancel}
@click=${this.handleDialogClick}
@pointerdown=${this.handleDialogPointerDown}
>
${this.withHeader
? html`
<header part="header" class="header">
<header part="header">
<h2 part="title" class="title" id="title">
<!-- If there's no label, use an invisible character to prevent the header from collapsing -->
<slot name="label"> ${this.label.length > 0 ? this.label : String.fromCharCode(65279)} </slot>
@@ -248,11 +246,11 @@ export default class WaDialog extends WebAwesomeElement {
`
: ''}
<div part="body" class="body"><slot></slot></div>
<slot part="body" class="body"></slot>
${this.withFooter
? html`
<footer part="footer" class="footer">
<footer part="footer">
<slot name="footer"></slot>
</footer>
`
@@ -264,7 +262,7 @@ export default class WaDialog extends WebAwesomeElement {
// Ugly, but it fixes light dismiss in Safari: https://bugs.webkit.org/show_bug.cgi?id=267688
if (!isServer) {
document.addEventListener('pointerdown', () => {
document.body.addEventListener('pointerdown', () => {
/* empty */
});
}

View File

@@ -62,6 +62,7 @@ export default class WaDrawer extends WebAwesomeElement {
private readonly localize = new LocalizeController(this);
private originalTrigger: HTMLElement | null;
private closeWatcher: CloseWatcher | null;
@query('.drawer') drawer: HTMLDialogElement;
@@ -135,7 +136,16 @@ export default class WaDrawer extends WebAwesomeElement {
}
private addOpenListeners() {
document.addEventListener('keydown', this.handleDocumentKeyDown);
if ('CloseWatcher' in window) {
this.closeWatcher?.destroy();
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => {
this.requestClose(this.drawer);
};
} else {
this.closeWatcher?.destroy();
document.addEventListener('keydown', this.handleDocumentKeyDown);
}
}
private removeOpenListeners() {

View File

@@ -50,6 +50,8 @@ export default class WaDropdown extends WebAwesomeElement {
@query('#trigger') trigger: HTMLSlotElement;
@query('.panel') panel: HTMLSlotElement;
private closeWatcher: CloseWatcher | null;
/**
* Indicates whether or not the dropdown is open. You can toggle this attribute to show and hide the dropdown, or you
* can use the `show()` and `hide()` methods and this attribute will reflect the dropdown's open state.
@@ -146,7 +148,7 @@ export default class WaDropdown extends WebAwesomeElement {
private handleKeyDown = (event: KeyboardEvent) => {
// Close when escape is pressed inside an open dropdown. 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') {
if (this.open && event.key === 'Escape' && !this.closeWatcher) {
event.stopPropagation();
this.hide();
this.focusOnTrigger();
@@ -342,7 +344,16 @@ export default class WaDropdown extends WebAwesomeElement {
addOpenListeners() {
this.panel.addEventListener('wa-select', this.handlePanelSelect);
this.panel.addEventListener('keydown', this.handleKeyDown);
if ('CloseWatcher' in window) {
this.closeWatcher?.destroy();
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => {
this.hide();
this.focusOnTrigger();
};
} else {
this.panel.addEventListener('keydown', this.handleKeyDown);
}
document.addEventListener('keydown', this.handleDocumentKeyDown);
document.addEventListener('mousedown', this.handleDocumentMouseDown);
}
@@ -354,6 +365,7 @@ export default class WaDropdown extends WebAwesomeElement {
}
document.removeEventListener('keydown', this.handleDocumentKeyDown);
document.removeEventListener('mousedown', this.handleDocumentMouseDown);
this.closeWatcher?.destroy();
}
@watch('open', { waitUntilFirstUpdate: true })

View File

@@ -102,6 +102,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
private readonly localize = new LocalizeController(this);
private typeToSelectString = '';
private typeToSelectTimeout: number;
private closeWatcher: CloseWatcher | null;
@query('.select') popup: WaPopup;
@query('.combobox') combobox: HTMLSlotElement;
@@ -319,6 +320,17 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
if (this.getRootNode() !== document) {
this.getRootNode().addEventListener('focusin', this.handleDocumentFocusIn);
}
if ('CloseWatcher' in window) {
this.closeWatcher?.destroy();
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => {
if (this.open) {
this.hide();
this.displayInput.focus({ preventScroll: true });
}
};
}
}
private removeOpenListeners() {
@@ -329,6 +341,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
if (this.getRootNode() !== document) {
this.getRootNode().removeEventListener('focusin', this.handleDocumentFocusIn);
}
this.closeWatcher?.destroy();
}
private handleFocus() {

View File

@@ -44,6 +44,7 @@ export default class WaTooltip extends WebAwesomeElement {
static dependencies = { 'wa-popup': WaPopup };
private hoverTimeout: number;
private closeWatcher: CloseWatcher | null;
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
@query('.body') body: HTMLElement;
@@ -126,6 +127,7 @@ export default class WaTooltip extends WebAwesomeElement {
super.disconnectedCallback();
// Cleanup this event in case the tooltip is removed while open
this.closeWatcher?.destroy();
document.removeEventListener('keydown', this.handleDocumentKeyDown);
this.eventController.abort();
@@ -210,7 +212,15 @@ export default class WaTooltip extends WebAwesomeElement {
return;
}
document.addEventListener('keydown', this.handleDocumentKeyDown, { signal: this.eventController.signal });
if ('CloseWatcher' in window) {
this.closeWatcher?.destroy();
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => {
this.hide();
};
} else {
document.addEventListener('keydown', this.handleDocumentKeyDown, { signal: this.eventController.signal });
}
this.body.hidden = false;
this.popup.active = true;
@@ -227,6 +237,7 @@ export default class WaTooltip extends WebAwesomeElement {
return;
}
this.closeWatcher?.destroy();
document.removeEventListener('keydown', this.handleDocumentKeyDown);
await animateWithClass(this.popup.popup, 'hide-with-scale');

View File

@@ -2,12 +2,10 @@ details:where(:not(:host *)),
:host {
--icon-color: var(--wa-color-text-quiet);
--spacing: var(--wa-space-m);
--outlined-border-color: var(--wa-color-surface-border);
background-color: var(--background-color, var(--wa-color-surface-default));
border: var(--wa-panel-border-width) var(--border-color, var(--wa-color-surface-border)) var(--wa-panel-border-style);
background-color: var(--wa-color-surface-default);
border: var(--wa-panel-border-width) var(--wa-color-surface-border) var(--wa-panel-border-style);
border-radius: var(--wa-panel-border-radius);
color: var(--text-color, inherit);
padding: var(--spacing);
/* Print styles */
@@ -21,12 +19,6 @@ details:where(:not(:host *)),
}
}
details.wa-plain,
:host(wa-details[appearance='plain']),
:host(wa-details.wa-plain) {
border-radius: 0;
}
details:where(:not(:host *)) summary,
:host summary /* nesting these summary styles in the rule above breaks in Firefox and Safari */ {
display: flex;