Compare commits

...

19 Commits

Author SHA1 Message Date
lindsaym-fa
5779e0a8e4 Merge branch 'next' into lm/theming-docs 2026-01-15 12:00:46 -05:00
Cory LaViska
67785f64cb update changelog (#1952) 2026-01-15 11:48:26 -05:00
Leon Vogt
39ab883802 Set default cursor for empty <wa-tree-item> expand buttons (#1936) 2026-01-15 11:46:08 -05:00
Lindsay M
08ef969db3 Add missing layout utility descriptions (#1946) 2026-01-15 11:31:29 -05:00
Konnor Rogers
72c4fb7af7 update plop templates (#1940) 2026-01-15 11:14:06 -05:00
Cory LaViska
827d70b2f5 Fix popover and dropdown bugs (#1913)
* fix popover bug; closes #1911

* fixes #1911

* reword guard
2026-01-15 09:53:54 -05:00
Lindsay M
46aa1429a7 Apply flexbox by default with wa-align-items-* and wa-justify-content-* utils (#1943)
* add `display: flex` with 0 specificity to utils

* add changelog
2026-01-13 15:44:37 -05:00
Brian Talbot
4b68a741bd abstracting layout docs example styling (#1935) 2026-01-13 15:31:37 -05:00
Cory LaViska
bd58f7f0a1 Divider docs (#1945)
* don't you know me

* clarify and add note
2026-01-13 15:08:25 -05:00
Alex Rao
e8f366835c Update tooltip.ts (#1928)
This removes the second period in the docs, which gets reflected on the website
2026-01-13 14:52:17 -05:00
Cory LaViska
70df6fea1f fix drag bug; closes #1905 (#1908) 2026-01-13 14:12:29 -05:00
lindsaym-fa
c3ddc303cc minor refactor 2025-11-10 16:14:24 -05:00
lindsaym-fa
b57225d5b0 tweaks to color palette logic 2025-11-07 18:56:25 -05:00
lindsaym-fa
c29e4cadf5 make a mess with usage instructions 2025-11-07 18:56:03 -05:00
lindsaym-fa
e5cf6e8757 Merge branch 'next' into lm/theming-docs 2025-11-07 13:04:50 -05:00
lindsaym-fa
69ea84b490 add variants, share styles with color palettes 2025-11-07 11:21:46 -05:00
lindsaym-fa
f83707a9e4 add color palettes preview, color scheme preference info 2025-11-06 18:33:45 -05:00
lindsaym-fa
05bb1f3630 🔧 tweaks 2025-11-06 10:32:53 -05:00
lindsaym-fa
b2a2d0b9fd initial progress 2025-11-05 18:31:41 -05:00
28 changed files with 921 additions and 85 deletions

View File

@@ -70,6 +70,7 @@
"exportparts",
"fetchpriority",
"fieldsets",
"flexbox",
"focusin",
"focusout",
"fontawesome",
@@ -151,6 +152,7 @@
"ParamagicDev",
"peta",
"petabit",
"pointercancel",
"Preact",
"preconnect",
"prerendered",
@@ -204,6 +206,8 @@
"thead",
"Themer",
"tinycolor",
"touchcancel",
"touchend",
"transitionend",
"treeitem",
"treeshaking",
@@ -237,8 +241,6 @@
"src/translations/!(en).ts",
"**/*.min.js"
],
"ignoreRegExpList": [
"(^|[^a-z])sl[a-z]*(^|[^a-z])"
],
"ignoreRegExpList": ["(^|[^a-z])sl[a-z]*(^|[^a-z])"],
"useGitignore": true
}

View File

@@ -0,0 +1,195 @@
{% for palette in themer.palettes %}
<link rel="stylesheet" href="/dist/styles/color/palettes/{{palette.filename}}" />
{% endfor %}
<div id="color-palettes" class="wa-stack wa-gap-xl">
<wa-radio-group id="palette-picker" value="default" class="radio-cards">
<span slot="label" class="wa-visually-hidden">Color Palette</span>
<div class="wa-grid">
{%- for palette in themer.palettes -%}
{%- if not palette.isPro -%}
<wa-radio value="{{ palette.name | lower }}">
<div class="wa-stack wa-gap-xs">
<span>{{ palette.name }}</span>
<div class="wa-palette-{{ palette.name | lower }} palette-preview wa-grid wa-gap-3xs">
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
</div>
</div>
</wa-radio>
{%- else -%}
{% raw %}{%- if currentUser.hasPro -%}{% endraw %}
<wa-radio value="{{ palette.name | lower }}">
<div class="wa-stack wa-gap-xs">
<span>{{ palette.name }}</span>
<div class="wa-palette-{{ palette.name | lower }} palette-preview wa-grid wa-gap-3xs">
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
<span class="preview-swatch"></span>
</div>
</div>
</wa-radio>
{% raw %}{%- endif -%}{% endraw %}
{%- endif -%}
{%- endfor -%}
</div>
</wa-radio-group>
<div id="palette" class="wa-stack wa-gap-xs">
{%- for color in themer.colors -%}
<div class="color-scale wa-flank wa-gap-xs">
<div class="color-name">{{ color }}</div>
<div class="color-swatches wa-grid wa-gap-2xs">
{%- for tint in themer.tints -%}
<wa-copy-button
class="color-swatch"
copy-label="{{ color }} {{ tint }}"
value="var(--wa-color-{{ color }}-{{ tint }})"
style="--color: var(--wa-color-{{ color }}-{{ tint }}); --tint: '{{ tint }}'"
>
<span class="wa-visually-hidden">--wa-color-{{ color }}-{{ tint }}</span>
</wa-copy-button>
{%- endfor -%}
</div>
</div>
{%- endfor -%}
</div>
</div>
<script type="module">
const paletteContainer = document.getElementById('color-palettes');
const palettePreviewPicker = document.getElementById('palette-picker');
// Set first radio as checked and add initial palette class
const firstPaletteRadio = palettePreviewPicker.querySelector('wa-radio');
if (firstPaletteRadio) {
firstPaletteRadio.checked = true;
paletteContainer.classList.add(`wa-palette-${firstPaletteRadio.value}`);
}
// Listen for radio changes
palettePreviewPicker.addEventListener('input', function(event) {
const selectedValue = event.target.value;
// Update palette container class
const existingThemeClasses = [...paletteContainer.classList].filter(className => className.startsWith('wa-palette-'));
existingThemeClasses.forEach(className => paletteContainer.classList.remove(className));
paletteContainer.classList.add(`wa-palette-${selectedValue}`);
});
</script>
<style>
#color-palettes:has(+ *) {
margin-block-end: var(--wa-content-spacing);
}
wa-radio-group.radio-cards {
&::part(form-control-input) {
display: grid;
}
> .wa-grid {
--max-column-count: 3;
--min-column-size: 12ch;
--_max-gap-count: calc(var(--max-column-count) - 1);
--_gap-width-sum: calc(var(--_max-gap-count) * var(--wa-space-m));
--_max-column-width: calc((100% - var(--_gap-width-sum)) / var(--max-column-count));
grid-template-columns: repeat(auto-fill, minmax(max(var(--min-column-size), var(--_max-column-width)), 1fr));
}
wa-radio {
display: block;
inline-size: var(--popover-min-inline-size);
background-color: var(--wa-form-control-background-color);
border: var(--wa-form-control-border-width) var(--wa-form-control-border-style) var(--wa-color-neutral-border-quiet);
border-radius: var(--wa-border-radius-m);
padding: 0.75em;
font-size: var(--wa-font-size-s);
color: var(--wa-color-text-quiet);
&::part(control) {
display: none;
}
&::part(label) {
font-weight: var(--wa-font-weight-bold);
}
@media (hover: hover) {
&:hover {
border-color: var(--wa-color-neutral-border-normal);
}
}
&:state(checked) {
border-color: var(--wa-color-neutral-border-loud);
box-shadow: 0 0 0 0.0625rem var(--wa-color-neutral-border-loud);
color: var(--wa-color-text-normal);
}
&:focus-visible {
outline: var(--wa-focus-ring);
outline-offset: var(--wa-focus-ring-offset);
}
}
}
.palette-preview {
--min-column-size: 0rem;
}
.preview-swatch {
aspect-ratio: 1 / 1;
height: 100%;
width: 100%;
border-radius: var(--wa-border-radius-s);
&:nth-child(1) {
background-color: var(--wa-color-red);
}
&:nth-child(2) {
background-color: var(--wa-color-orange);
}
&:nth-child(3) {
background-color: var(--wa-color-yellow);
}
&:nth-child(4) {
background-color: var(--wa-color-green);
}
&:nth-child(5) {
background-color: var(--wa-color-cyan);
}
&:nth-child(6) {
background-color: var(--wa-color-blue);
}
&:nth-child(7) {
background-color: var(--wa-color-indigo);
}
&:nth-child(8) {
background-color: var(--wa-color-purple);
}
&:nth-child(9) {
background-color: var(--wa-color-pink);
}
&:nth-child(10) {
background-color: var(--wa-color-gray);
}
}
</style>

View File

@@ -0,0 +1,302 @@
{% from "pro-badge.njk" import proBadge %}
{% from "free-badge.njk" import freeBadge %}
<div id="using-themes">
<fieldset class="theme-options wa-stack" style="margin-block-end: var(--wa-content-spacing);">
<legend style="align-self: start;">Theme Options</legend>
<wa-select id="pick-theme" label="Theme" value="default" size="small">
<wa-badge slot="end" appearance="accent" pill class="pro pro-only-value">Pro</wa-badge>
{%- for theme in themer.themes -%}
{%- if not theme.isPro -%}
<wa-option
value="{{ theme.name | lower }}"
data-palette="{{ theme.palette.filename | stripExtension}}"
data-brand="{{ theme.colorBrand.color }}"
>
{{ theme.name | capitalize }}
</wa-option>
{%- endif -%}
{%- endfor -%}
{% raw %}{% if currentUser.hasPro %}{% endraw %}
{%- for theme in themer.themes -%}
{% if loop.first %}<wa-divider></wa-divider>{% endif %}
{%- if theme.isPro -%}
<wa-option
value="{{ theme.name | lower }}"
data-palette="{{ theme.palette.filename | stripExtension}}"
data-brand="{{ theme.colorBrand.color }}"
{% if theme.isPro %}data-is-pro{% endif %}
>
{{ theme.name | capitalize }}
</wa-option>
{%- endif -%}
{%- endfor -%}
{% raw %}{% endif %}{% endraw %}
</wa-select>
<wa-select id="pick-palette" label="Color Palette" value="default" size="small">
<wa-badge slot="end" appearance="accent" pill class="pro pro-only-value">Pro</wa-badge>
{%- for palette in themer.palettes -%}
{%- if not palette.isPro -%}
<wa-option
value="{{ palette.name | lower }}"
>
{{ palette.name | capitalize }}
</wa-option>
{%- endif -%}
{%- endfor -%}
{% raw %}{% if currentUser.hasPro %}{% endraw %}
{%- for palette in themer.palettes -%}
{% if loop.first %}<wa-divider></wa-divider>{% endif %}
{%- if palette.isPro -%}
<wa-option
value="{{ palette.name | lower }}"
{% if palette.isPro %}data-is-pro{% endif %}
>
{{ palette.name | capitalize }}
</wa-option>
{%- endif -%}
{%- endfor -%}
{% raw %}{% endif %}{% endraw %}
</wa-select>
<div class="wa-grid">
{%- set colorVariants = ['brand', 'neutral', 'success', 'warning', 'danger'] -%}
{%- for colorVariant in colorVariants -%}
<wa-select id="pick-{{ colorVariant }}" label="{{ colorVariant | capitalize }}" size="small">
<wa-icon slot="start" name="square" class="selected-color"></wa-icon>
{%- for color in themer.colors -%}
<wa-option value="{{ color }}">
{{ color | capitalize }}
<wa-icon slot="start" name="square" style="--color: var(--wa-color-{{ color }}); color: var(--color);"></wa-icon>
</wa-option>
{%- endfor -%}
</wa-select>
{%- endfor -%}
</div>
</fieldset>
</div>
<wa-tab-group>
<wa-tab panel="cdn"><wa-icon name="rocket-launch" variant="regular"></wa-icon> CDN</wa-tab>
<wa-tab panel="npm"><wa-icon name="box-open" variant="regular"></wa-icon> npm</wa-tab>
<wa-tab panel="self-hosted"><wa-icon name="arrow-down-to-line" variant="regular"></wa-icon> Self-Hosted</wa-tab>
<wa-tab-panel name="cdn">
<div class="wa-stack wa-gap-xl">
<wa-callout variant="neutral" size="small" id="pro-only-callout">
<wa-icon name="info-circle" variant="regular"></wa-icon>
This combination can only be used on teams with a Pro subscription.
</wa-callout>
<div id="free-instructions">
<strong>For projects on <wa-badge appearance="filled" variant="neutral" pill class="free">Free</wa-badge> teams:</strong>
<ol>
<li>Head over to your project's <wa-icon name="gear" variant="regular"></wa-icon> <strong>Settings</strong>.</li>
<li>For <strong>Theme</strong>, select <wa-icon name="paintbrush" variant="regular"></wa-icon> <strong class="theme-name"></strong>.</li>
<li>For <strong>Color Palette</strong>, select <wa-icon name="swatchbook" variant="regular"></wa-icon> <strong class="palette-name"></strong>.</li>
<li>Press <strong>Save Changes</strong> to update anywhere you're using your project.</li>
<li class="custom-variants">In your own files, apply the following classes to the <code>&lt;html&gt;</code> element:<br />
<pre><code class="variant-classes" class="language-html"></code></pre>
</li>
</ol>
</div>
<div id="pro-instructions">
<strong>For projects on <wa-badge appearance="accent" pill class="pro">Pro</wa-badge> teams:</strong>
<ol>
<li>Head over to your project's <wa-icon name="gear" variant="regular"></wa-icon> <strong>Settings</strong>.</li>
<li>Press <wa-icon name="paintbrush" variant="regular"></wa-icon> <strong>Edit Your Theme</strong> to open the Theme Builder.</li>
<li>Open <strong>Theme</strong> and select <strong class="theme-name"></strong>.</li>
<li>Open <strong>Colors</strong>. On the <strong>Basic</strong> tab, select <strong class="palette-name"></strong>. <span class="custom-variants">On the <strong>Advanced</strong> tab, select your preferred variant colors.</span></li>
<li>Press <strong>Save Changes</strong> to update anywhere you're using your project.</li>
</ol>
</div>
</div>
</wa-tab-panel>
<wa-tab-panel name="npm">
<p>To use this theme, include the following stylesheet(s):</p>
<pre><code id="stylesheet-imports" class="language-html"></code></pre>
<p>Then, apply the following classes to the <code>&lt;html&gt;</code> element:</p>
<pre><code class="html-classes" class="language-html"></code></pre>
</wa-tab-panel>
<wa-tab-panel name="self-hosted">
<p>To use this theme, include the following stylesheet(s):</p>
<pre><code id="stylesheet-links" class="language-html"></code></pre>
<p>Then, apply the following classes to the <code>&lt;html&gt;</code> element:</p>
<pre><code class="html-classes" class="language-html"></code></pre>
</wa-tab-panel>
</wa-tab-group>
<script type="module">
const themePicker = document.getElementById('pick-theme');
const palettePicker = document.getElementById('pick-palette');
const brandPicker = document.getElementById('pick-brand');
const neutralPicker = document.getElementById('pick-neutral');
const successPicker = document.getElementById('pick-success');
const warningPicker = document.getElementById('pick-warning');
const dangerPicker = document.getElementById('pick-danger');
const stylesheetLinks = document.getElementById('stylesheet-links');
const stylesheetImports = document.getElementById('stylesheet-imports');
const htmlClassList = document.querySelectorAll('.html-classes');
const variantClassList = document.querySelectorAll('.variant-classes');
const themeNames = document.querySelectorAll('.theme-name');
const paletteNames = document.querySelectorAll('.palette-name');
// Default values for color variants
const defaultColors = {
brand: 'blue',
neutral: 'gray',
success: 'green',
warning: 'yellow',
danger: 'red'
};
// Update dynamic instructions based on current picker values
function updateInstructions() {
const theme = themePicker.value;
const palette = palettePicker.value;
const brand = brandPicker.value;
const neutral = neutralPicker.value;
const success = successPicker.value;
const warning = warningPicker.value;
const danger = dangerPicker.value;
// Get the default palette for the selected theme
const selectedThemeOption = themePicker.querySelector(`wa-option[value="${theme}"]`);
const themeDefaultPalette = selectedThemeOption?.getAttribute('data-palette') || 'default';
const proOnlyTheme = selectedThemeOption?.hasAttribute('data-is-pro') || false;
const proOnlyPalette = palettePicker.querySelector(`wa-option[value="${palette}"]`)?.hasAttribute('data-is-pro') || false;
// Show/hide Pro-only callout
const proOnlyCallout = document.getElementById('pro-only-callout');
const freeInstructions = document.getElementById('free-instructions');
if (proOnlyTheme || proOnlyPalette) {
proOnlyCallout.style.display = '';
freeInstructions.style.display = 'none';
} else {
proOnlyCallout.style.display = 'none';
freeInstructions.style.display = '';
}
// Show/hide Pro badge on selected Pro values
document.querySelectorAll('.pro-only-value').forEach(badge => {
if ((badge.parentElement.id === 'pick-theme' && proOnlyTheme) ||
(badge.parentElement.id === 'pick-palette' && proOnlyPalette)) {
badge.style.display = '';
} else {
badge.style.display = 'none';
}
});
// Build stylesheet links
const links = [`<link rel="stylesheet" href="/dist/styles/themes/${theme}.css" />`];
const imports = [`import '@awesome.me/webawesome/dist/styles/themes/${theme}.css';`];
// Only include palette link if it differs from theme's default
if (palette !== themeDefaultPalette) {
links.push(`<link rel="stylesheet" href="/dist/styles/color/palettes/${palette}.css" />`);
imports.push(`import '@awesome.me/webawesome/dist/styles/color/palettes/${palette}.css';`);
}
stylesheetLinks.textContent = links.join('\n');
stylesheetImports.textContent = imports.join('\n');
// Build HTML classes
const classes = [`wa-theme-${theme}`];
const variantClasses = [];
// Only include palette class if it differs from theme's default
if (palette !== themeDefaultPalette) {
classes.push(`wa-palette-${palette}`);
}
// Only include color classes if they differ from defaults
if (brand !== defaultColors.brand) {
[classes, variantClasses].forEach(classList => classList.push(`wa-brand-${brand}`));
}
if (neutral !== defaultColors.neutral) {
[classes, variantClasses].forEach(classList => classList.push(`wa-neutral-${neutral}`));
}
if (success !== defaultColors.success) {
[classes, variantClasses].forEach(classList => classList.push(`wa-success-${success}`));
}
if (warning !== defaultColors.warning) {
[classes, variantClasses].forEach(classList => classList.push(`wa-warning-${warning}`));
}
if (danger !== defaultColors.danger) {
[classes, variantClasses].forEach(classList => classList.push(`wa-danger-${danger}`));
}
htmlClassList.forEach(instance => {
instance.textContent = `class="${classes.join(' ')}"`;
});
variantClassList.forEach(instance => {
instance.textContent = `class="${variantClasses.join(' ')}"`;
});
if (variantClasses.length === 0) {
document.querySelectorAll('.custom-variants').forEach(el => el.style.display = 'none');
} else {
document.querySelectorAll('.custom-variants').forEach(el => el.style.display = '');
}
// Update theme and palette names in CDN instructions
themeNames.forEach(instance => {
instance.textContent = theme;
instance.style.textTransform = 'capitalize';
});
paletteNames.forEach(instance => {
instance.textContent = palette;
instance.style.textTransform = 'capitalize';
});
// Match color picker icons to selected palette and color
[brandPicker, neutralPicker, successPicker, warningPicker, dangerPicker].forEach(picker => {
const color = picker.value;
const selectedColorIcon = picker.querySelector('wa-icon.selected-color');
const optionsColorIcons = picker.querySelectorAll('wa-option wa-icon[slot="start"]');
if (selectedColorIcon) {
selectedColorIcon.style.setProperty('color', `var(--wa-color-${color})`);
selectedColorIcon.classList.remove(...[...selectedColorIcon.classList].filter(className => className.startsWith('wa-palette-')));
selectedColorIcon.classList.add(`wa-palette-${palette}`);
}
optionsColorIcons.forEach(icon => {
icon.classList.remove(...[...icon.classList].filter(className => className.startsWith('wa-palette-')));
icon.classList.add(`wa-palette-${palette}`);
});
});
}
// Set palette and brand based on theme selection
themePicker.addEventListener('input', function() {
const selectedOption = themePicker.querySelector(`wa-option[value="${themePicker.value}"]`);
const palette = selectedOption.getAttribute('data-palette');
const brand = selectedOption.getAttribute('data-brand');
palettePicker.value = palette;
brandPicker.value = brand;
updateInstructions();
});
// Update instructions when any picker changes
[palettePicker, brandPicker, neutralPicker, successPicker, warningPicker, dangerPicker].forEach(picker => {
picker.addEventListener('input', updateInstructions);
});
// Set initial values
themePicker.value = 'default';
palettePicker.value = 'default';
brandPicker.value = 'blue';
neutralPicker.value = 'gray';
successPicker.value = 'green';
warningPicker.value = 'yellow';
dangerPicker.value = 'red';
// Initial update
updateInstructions();
</script>

View File

@@ -0,0 +1,27 @@
{% set colorVariants = ["brand", "neutral", "success", "warning", "danger"] %}
<div id="color-variants" class="wa-stack wa-gap-xs">
{%- for colorVariant in colorVariants -%}
<div class="color-scale wa-flank wa-gap-xs">
<div class="color-name">{{ colorVariant }}</div>
<div class="color-swatches wa-grid wa-gap-2xs">
{%- for tint in themer.tints -%}
<wa-copy-button
class="color-swatch"
copy-label="{{ colorVariant }} {{ tint }}"
value="var(--wa-color-{{ colorVariant }}-{{ tint }})"
style="--color: var(--wa-color-{{ colorVariant }}-{{ tint }}); --tint: '{{ tint }}'"
>
<span class="wa-visually-hidden">--wa-color-{{ colorVariant }}-{{ tint }}</span>
</wa-copy-button>
{%- endfor -%}
</div>
</div>
{%- endfor -%}
</div>
<style>
#color-variants:has(+ *) {
margin-block-end: var(--wa-content-spacing);
}
</style>

View File

@@ -10,6 +10,13 @@
:root {
--wa-brand-orange: #f36944;
--wa-brand-grey: #30323b;
/* layout-based example style aspects */
--layout-example-border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);
--layout-example-border-radius: var(--wa-border-radius-l);
--layout-example-padding: var(--wa-space-s);
--layout-example-element-background: var(--wa-color-indigo-60);
--layout-example-element-border-radius: var(--wa-border-radius-m);
}
.wa-dark .only-light,
@@ -627,6 +634,68 @@ table.colors {
--icon-color: var(--wa-color-success-fill-quiet);
}
/* Theming */
.color-scale.wa-flank {
--flank-size: 6ch;
--content-percentage: 88%;
--reserved-tint-space: calc(var(--wa-font-size-xs) + var(--wa-space-xs)); /* TODO: rename */
margin-bottom: var(--reserved-tint-space);
.color-swatches.wa-grid {
--min-column-size: 2.5rem;
grid-row-gap: calc(var(--reserved-tint-space) + var(--wa-space-xs));
}
.color-name {
font-weight: var(--wa-font-weight-bold);
text-transform: capitalize;
font-size: var(--wa-font-size-s);
color: var(--wa-color-text-normal);
}
wa-copy-button.color-swatch {
padding: 0;
aspect-ratio: 1;
position: relative;
&::before {
content: var(--tint);
position: absolute;
bottom: calc(-1 * var(--reserved-tint-space));
left: 50%;
transform: translateX(-50%);
font-size: var(--wa-font-size-xs);
line-height: 1;
color: var(--wa-color-text-quiet);
text-align: center;
z-index: 2;
}
&::part(button) {
width: 100%;
height: 100%;
cursor: pointer;
background-color: var(--color);
border-radius: var(--wa-border-radius-s);
transition: transform 0.1s ease, box-shadow 0.1s ease;
}
&:hover::part(button) {
transform: scale(1.075);
box-shadow: var(--wa-shadow-s);
z-index: 1;
position: relative;
}
&::part(copy-icon),
&::part(success-icon),
&::part(error-icon) {
visibility: hidden;
}
}
}
/* Layout Examples */
.layout-example-boundary {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);

View File

@@ -41,7 +41,7 @@ Use the `--spacing` custom property to change the amount of space between the di
### Orientation
The default orientation for dividers is `horizontal`. Set `orientation` attribute to `vertical` to draw a vertical divider. The divider will span the full height of its container.
The default orientation for dividers is `horizontal`. Set `orientation` attribute to `vertical` to draw a vertical divider. The divider will span the full height of its [Flexbox](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/CSS_layout/Flexbox) or [CSS Grid](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/grid) container.
```html {.example}
<div style="display: flex; align-items: center;">
@@ -53,6 +53,10 @@ The default orientation for dividers is `horizontal`. Set `orientation` attribut
</div>
```
:::info
If your container isn't Flexbox or CSS Grid, you may need to set an explicit height for the divider.
:::
### Dropdown Dividers
Use dividers in [dropdowns](/docs/components/dropdown) to visually group dropdown items.

View File

@@ -13,9 +13,18 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh
## Next
- Fixed a bug in `<wa-combobox>` that prevented the listbox from opening when options were preselected [issue:1883]
- Added `justify-content` CSS utilities [pr:1930]
- Added missing `.wa-gap-4xl` utility class [pr:1931]
- Added `pointercancel` and `touchcancel` event handling to draggable elements to prevent drags from getting stuck
- Added `wa-justify-content-*` utility classes [pr:1930]
- Added missing `wa-gap-4xl` utility class [pr:1931]
- Fixed a bug in `<wa-combobox>` that prevented the listbox from opening when options were preselected [issue:1883]
- Fixed a bug in `<wa-popup>` and `<wa-dropdown-item>` that caused an error when removing a popup while it was opening [issue:1910]
- Fixed a bug in `<wa-popup>` and `<wa-dropdown>` that caused errors when shadow DOM queries returned null [issue:1911]
- Fixed a bug in `<wa-combobox>` that prevented the listbox from opening when options were preselected [issue:1883]
- Fixed a bug in draggable elements that caused a TypeError on `touchend` events when `event.touches` was empty
- Fixed a bug in `<wa-tree-item>` that caused the cursor to show a pointer when no expand icon was present [pr:1936]
- Modified `wa-align-items-*` utility classes to apply `display: flex` by default [pr:1943]
## 3.1.0

View File

@@ -0,0 +1,165 @@
---
title: Theming
description: TODO
layout: page-outline
---
Web Awesome themes apply a cohesive look and feel across the entire library. A theme is a collection of predefined CSS custom properties that cover a range of styles from colors to transitions. We call these CSS custom properties design tokens.
There are 11 handcrafted themes to choose from; 3 are free to use with an additional 9 available in Web Awesome Pro. You can also build your own manually with CSS or with our Pro Theme Builder.
## Key Concepts
Themes are made up of several layers of increasing specificity, each represented by a CSS class on the document.
### Color Palette
`.wa-palette-{name}`
Color palettes give you a full spectrum of colors to use in your project. A color palette defines 10 hues — red, orange, yellow, green, cyan, blue, indigo, purple, pink, and gray — each with 11 tints. Tints are assigned numbers that correlate to their lightness.
There are 9 specially crafted color palettes; 3 are free to use with an additional 6 available in Web Awesome Pro.
{% include 'theming/color-palettes.njk' %}
Your color palette is determined by `class="wa-palette-{name}"` on the `<html>` element. If no class is specified, the default color palette for your chosen theme is used.
### Variants
`.wa-{variant}-{hue}`
Variants convey a specific meaning through color. There are five variants:
- **Brand** for product recognition
- **Neutral** for generic and ordinary content
- **Success** for validity or confirmation
- **Warning** for caution or uncertainty
- **Danger** for errors or risk
Brand and neutral are used by nearly every element, component, and pattern across the library. Success, warning, and danger are used selectively by components that could benefit from semantic reinforcement, such as buttons and callouts.
{% include 'theming/variants.njk' %}
Any hue from a color palette can be assigned to a variant. Each variant is determined by `class="wa-{variant}-{hue}"` on the `<html>` element. If no class is specified:
- **Brand** defaults to <wa-icon name="square" style="color: var(--wa-color-blue);"></wa-icon> **blue**
- **Neutral** defaults to <wa-icon name="square" style="color: var(--wa-color-gray);"></wa-icon> **gray**
- **Success** defaults to <wa-icon name="square" style="color: var(--wa-color-green);"></wa-icon> **green**
- **Warning** defaults to <wa-icon name="square" style="color: var(--wa-color-yellow);"></wa-icon> **yellow**
- **Danger** defaults to <wa-icon name="square" style="color: var(--wa-color-red);"></wa-icon> **red**
### Theme Styles
`.wa-theme-{name}`
Theme styles assign specific tints from your chosen variant colors — along with qualities like fonts, borders, space, and shadows — to design tokens that style elements and components. Themes may also contain custom CSS overrides to change the default look of components.
TODO: Add theme preview
Your theme is determined by `class="wa-theme-{name}"` on the `<html>` element. If no class is specified, the default theme is used.
### Light and Dark Mode
`.wa-light` | `.wa-dark`
Every theme is designed to adapt to light and dark mode. Light mode styles are applied by default, but you can apply a specific color scheme to an entire page or just a section with `class="wa-light"` or `class="wa-dark"`.
```html {.example}
<div class="wa-grid">
<wa-card class="wa-light">
<div slot="header" class="wa-split">
<h4 class="wa-heading-m">Light</h4>
<wa-icon name="brightness"></wa-icon>
</div>
<div class="wa-stack">
<wa-input label="# of Waffles" type="number" value="3"></wa-input>
<wa-select label="Toppings" multiple value="jelly-beans">
<wa-option value="whipped-cream">Whipped cream</wa-option>
<wa-option value="hershey">Hershey's Kisses</wa-option>
<wa-option value="jelly-beans">Jelly beans</wa-option>
</wa-select>
<wa-button appearance="filled" variant="brand">
<wa-icon slot="start" name="waffle"></wa-icon>
Make Waffles
</wa-button>
</div>
</wa-card>
<wa-card class="wa-dark">
<div slot="header" class="wa-split">
<h4 class="wa-heading-m">Dark</h4>
<wa-icon name="moon-stars"></wa-icon>
</div>
<div class="wa-stack">
<wa-input label="# of Waffles" type="number" value="3"></wa-input>
<wa-select label="Toppings" multiple value="jelly-beans">
<wa-option value="whipped-cream">Whipped cream</wa-option>
<wa-option value="hershey">Hershey's Kisses</wa-option>
<wa-option value="jelly-beans">Jelly beans</wa-option>
</wa-select>
<wa-button appearance="filled" variant="brand">
<wa-icon slot="start" name="waffle"></wa-icon>
Make Waffles
</wa-button>
</div>
</wa-card>
</div>
```
#### Inverting the Color Scheme
`.wa-invert`
You can force a section to behave like `.wa-dark` in light mode and like `.wa-light` in dark mode by using `class="wa-invert"`.
```html {.example}
<p>This card will always use the opposite of the color scheme applied to the docs.</p>
<wa-card class="wa-invert">
<div slot="header" class="wa-split">
<h4 class="wa-heading-m">Invert</h4>
<wa-icon name="swap"></wa-icon>
</div>
<div class="wa-flank:end wa-align-items-end">
<wa-select label="Location" value="upside-down">
<wa-option value="lab">Hawkins Lab</wa-option>
<wa-option value="mall">Starcourt Mall</wa-option>
<wa-option value="upside-down">The Upside Down</wa-option>
</wa-select>
<wa-button id="go-button" appearance="filled" variant="brand">
<wa-icon label="Go" name="person-to-portal" family="duotone"></wa-icon>
</wa-button>
<wa-tooltip for="go-button">
Go!
</wa-tooltip>
</div>
</wa-card>
```
#### Detecting Color Scheme Preference
While both light and dark mode styles are built-in to all themes, Web Awesome doesn't automatically detect the user's color scheme preference. We recommend doing this at the application level.
Follow these best practices for supporting both light and dark mode:
- Check for [`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-color-scheme) and use its value by default
- Allow the user to override this setting in your app
- Remember the user's preference and restore it on subsequent visits
Let's assume you store the user's color scheme preference for your app in a variable called `colorScheme` (values: `auto` | `light` | `dark`). You can use the following JS snippet to apply `class="wa-dark"` to the `<html>` element accordingly:
```js
const systemDark = window.matchMedia('(prefers-color-scheme: dark)');
const applyDark = function (event = systemDark) {
const isDark = colorScheme === 'auto' ? event.matches : colorScheme === 'dark';
document.documentElement.classList.toggle('wa-dark', isDark);
};
systemDark.addEventListener('change', applyDark);
applyDark();
```
## Using Themes
Use and update themes instantly via CDN with [Web Awesome projects](/teams). Or, assemble the pieces together using npm or in your self-hosted app.
Select your favorite options and follow the instructions for your preferred method.
{% include 'theming/instructions.njk' %}
## Creating Your Own
TODO

View File

@@ -7,15 +7,16 @@ tags: layoutUtilities
<style>
.preview-wrapper {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);
border: var(--layout-example-border);
border-radius: var(--wa-border-radius-m);
min-block-size: 3em;
min-inline-size: 5em;
padding: var(--wa-space-2xs);
}
.preview-block {
aspect-ratio: 1 / 1;
background-color: var(--wa-color-neutral-fill-loud);
background-color: var(--layout-example-element-background);
border-radius: var(--wa-border-radius-s);
min-block-size: 1em;
}

View File

@@ -7,19 +7,21 @@ tags: layoutUtilities
<style>
:is(.wa-flank, .wa-grid, .wa-stack) > [class*='wa-cluster']:has(div:empty) {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);
border-radius: var(--wa-border-radius-l);
padding: var(--wa-space-s);
border: var(--layout-example-border);
border-radius: var(--layout-example-border-radius);
padding: var(--layout-example-padding);
}
[class*='wa-cluster'] div:empty {
background-color: var(--wa-color-indigo-60);
border-radius: var(--wa-border-radius-m);
background-color: var(--layout-example-element-background);
border-radius: var(--layout-example-element-border-radius);
min-block-size: 4rem;
min-inline-size: 4rem;
}
</style>
{{ description }}
```html {.example}
<div class="wa-cluster">
<div></div>

View File

@@ -7,20 +7,20 @@ tags: layoutUtilities
<style>
:is(.wa-flank, .wa-grid, .wa-stack) > [class*='wa-flank']:has(div:empty) {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);
border-radius: var(--wa-border-radius-l);
padding: var(--wa-space-s);
border: var(--layout-example-border);
border-radius: var(--layout-example-border-radius);
padding: var(--layout-example-padding);
}
[class*='wa-flank'] div:empty {
background-color: var(--wa-color-indigo-60);
border-radius: var(--wa-border-radius-m);
background-color: var(--layout-example-element-background);
border-radius: var(--layout-example-element-border-radius);
min-block-size: 4rem;
min-inline-size: 4rem;
}
</style>
When space is limited, the items wrap.
{{ description }} When space is limited, the items wrap.
```html {.example}
<div class="wa-flank">

View File

@@ -7,18 +7,20 @@ tags: layoutUtilities
<style>
[class*='wa-frame']:has(div:empty) {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);
padding: var(--wa-space-s);
border: var(--layout-example-border);
padding: var(--layout-example-padding);
}
[class*='wa-frame'] div:empty {
background-color: var(--wa-color-indigo-60);
border-radius: var(--wa-border-radius-m);
background-color: var(--layout-example-element-background);
border-radius: var(--layout-example-element-border-radius);
min-block-size: 4rem;
min-inline-size: 4rem;
}
</style>
{{ description }}
```html {.example}
<div class="wa-frame" style="max-inline-size: 20rem;">
<div></div>

View File

@@ -6,11 +6,19 @@ tags: layoutUtilities
---
<style>
.preview-wrapper {
border: var(--layout-example-border);
border-radius: var(--wa-border-radius-m);
min-block-size: 3em;
min-inline-size: 5em;
padding: var(--wa-space-2xs);
}
.preview-block {
aspect-ratio: 1 / 1;
background-color: var(--wa-color-neutral-fill-loud);
background-color: var(--layout-example-element-background);
border-radius: var(--wa-border-radius-s);
min-block-size: 1.5em;
min-block-size: 1em;
}
</style>
@@ -20,15 +28,15 @@ Besides `wa-gap-0`, which sets `gap` to zero, each class corresponds to one of t
| Class Name | `gap` Value | Preview |
| ------------ | ---------------- | ----------------------------------------------------------------------------------------------------------- |
| `wa-gap-0` | `0` | <div class="wa-cluster wa-gap-0"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-3xs` | `--wa-space-3xs` | <div class="wa-cluster wa-gap-3xs"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-2xs` | `--wa-space-2xs` | <div class="wa-cluster wa-gap-2xs"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-xs` | `--wa-space-xs` | <div class="wa-cluster wa-gap-xs"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-s` | `--wa-space-s` | <div class="wa-cluster wa-gap-s"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-m` | `--wa-space-m` | <div class="wa-cluster wa-gap-m"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-l` | `--wa-space-l` | <div class="wa-cluster wa-gap-l"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-xl` | `--wa-space-xl` | <div class="wa-cluster wa-gap-xl"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-2xl` | `--wa-space-2xl` | <div class="wa-cluster wa-gap-2xl"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-3xl` | `--wa-space-3xl` | <div class="wa-cluster wa-gap-3xl"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-0` | `0` | <div class="preview-wrapper wa-cluster wa-gap-0"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-3xs` | `--wa-space-3xs` | <div class="preview-wrapper wa-cluster wa-gap-3xs"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-2xs` | `--wa-space-2xs` | <div class="preview-wrapper wa-cluster wa-gap-2xs"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-xs` | `--wa-space-xs` | <div class="preview-wrapper wa-cluster wa-gap-xs"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-s` | `--wa-space-s` | <div class="preview-wrapper wa-cluster wa-gap-s"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-m` | `--wa-space-m` | <div class="preview-wrapper wa-cluster wa-gap-m"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-l` | `--wa-space-l` | <div class="preview-wrapper wa-cluster wa-gap-l"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-xl` | `--wa-space-xl` | <div class="preview-wrapper wa-cluster wa-gap-xl"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-2xl` | `--wa-space-2xl` | <div class="preview-wrapper wa-cluster wa-gap-2xl"><div class="preview-block"></div><div class="preview-block"></div></div> |
| `wa-gap-3xl` | `--wa-space-3xl` | <div class="preview-wrapper wa-cluster wa-gap-3xl"><div class="preview-block"></div><div class="preview-block"></div></div> |
<!-- Pending 3.2.0 release -->
<!-- | `wa-gap-4xl` | `--wa-space-4xl` | <div class="wa-cluster wa-gap-4xl"><div class="preview-block"></div><div class="preview-block"></div></div> | -->
<!-- | `wa-gap-4xl` | `--wa-space-4xl` | <div class="preview-wrapper wa-cluster wa-gap-4xl"><div class="preview-block"></div><div class="preview-block"></div></div> | -->

View File

@@ -7,19 +7,21 @@ tags: layoutUtilities
<style>
:is(.wa-flank, .wa-grid, .wa-stack) > [class*='wa-grid']:has(div:empty) {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);
border-radius: var(--wa-border-radius-l);
padding: var(--wa-space-s);
border: var(--layout-example-border);
border-radius: var(--layout-example-border-radius);
padding: var(--layout-example-padding);
}
[class*='wa-grid'] div:empty {
background-color: var(--wa-color-indigo-60);
border-radius: var(--wa-border-radius-m);
background-color: var(--layout-example-element-background);
border-radius: var(--layout-example-element-border-radius);
min-block-size: 4rem;
min-inline-size: 4rem;
}
</style>
{{ description }}
```html {.example}
<div class="wa-grid">
<div></div>

View File

@@ -9,15 +9,16 @@ unlisted: true
<style>
.preview-wrapper {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);
border: var(--layout-example-border);
border-radius: var(--wa-border-radius-m);
min-block-size: 3em;
min-inline-size: 5em;
padding: var(--wa-space-2xs);
}
.preview-block {
aspect-ratio: 1 / 1;
background-color: var(--wa-color-neutral-fill-loud);
background-color: var(--layout-example-element-background);
border-radius: var(--wa-border-radius-s);
min-block-size: 1em;
}

View File

@@ -7,19 +7,21 @@ tags: layoutUtilities
<style>
:is(.wa-flank, .wa-grid, .wa-stack) > [class*='wa-split']:has(div:empty) {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);
border-radius: var(--wa-border-radius-l);
padding: var(--wa-space-s);
border: var(--layout-example-border);
border-radius: var(--layout-example-border-radius);
padding: var(--layout-example-padding);
}
[class*='wa-split'] div:empty {
background-color: var(--wa-color-indigo-60);
border-radius: var(--wa-border-radius-m);
background-color: var(--layout-example-element-background);
border-radius: var(--layout-example-element-border-radius);
min-block-size: 4rem;
min-inline-size: 4rem;
}
</style>
{{ description }}
```html {.example}
<div class="wa-split">
<div></div>

View File

@@ -7,19 +7,21 @@ tags: layoutUtilities
<style>
:is(.wa-flank, .wa-grid, .wa-stack) > [class*='wa-stack']:has(div:empty) {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);
border-radius: var(--wa-border-radius-l);
padding: var(--wa-space-s);
border: var(--layout-example-border);
border-radius: var(--layout-example-border-radius);
padding: var(--layout-example-padding);
}
[class*='wa-stack'] div:empty {
background-color: var(--wa-color-indigo-60);
border-radius: var(--wa-border-radius-m);
background-color: var(--layout-example-element-background);
border-radius: var(--layout-example-element-border-radius);
min-block-size: 4rem;
min-inline-size: 4rem;
}
</style>
{{ description }}
```html {.example}
<div class="wa-stack">
<div></div>

View File

@@ -37,7 +37,7 @@ export default function (plop) {
},
{
type: 'add',
path: '../../src/components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.css',
path: '../../src/components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.styles.ts',
templateFile: 'templates/component/styles.hbs',
},
{

View File

@@ -2,7 +2,7 @@ import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { watch } from '../../internal/watch.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import styles from './{{ tagWithoutPrefix tag }}.css';
import styles from './{{ tagWithoutPrefix tag }}.styles.js';
/**
* @summary Short summary of the component's intended use.
@@ -21,7 +21,7 @@ import styles from './{{ tagWithoutPrefix tag }}.css';
*/
@customElement("{{ tag }}")
export default class {{ properCase tag }} extends WebAwesomeElement {
static css = styles;
static css = [styles];
/** An example attribute. */
@property() attr = 'example';

View File

@@ -1,3 +1,7 @@
:host {
display: block;
import { css } from "lit";
export default css`
:host {
display: block;
}
`

View File

@@ -155,20 +155,21 @@ export default class WaDropdownItem extends WebAwesomeElement {
/** Opens the submenu. */
async openSubmenu() {
if (!this.hasSubmenu || !this.submenuElement) return;
const submenu = this.submenuElement;
if (!this.hasSubmenu || !submenu || !this.isConnected) 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', '');
submenu.showPopover?.();
submenu.hidden = false;
submenu.setAttribute('data-visible', '');
this.submenuOpen = true;
this.setAttribute('aria-expanded', 'true');
// Animate the submenu
await animateWithClass(this.submenuElement, 'show');
await animateWithClass(submenu, 'show');
// Set focus to the first submenu item
setTimeout(() => {
@@ -210,16 +211,19 @@ export default class WaDropdownItem extends WebAwesomeElement {
/** Closes the submenu. */
async closeSubmenu() {
if (!this.hasSubmenu || !this.submenuElement) return;
const submenu = this.submenuElement;
if (!this.hasSubmenu || !submenu) 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();
if (!submenu.hidden) {
await animateWithClass(submenu, 'hide');
if (submenu?.isConnected) {
submenu.hidden = true;
submenu.removeAttribute('data-visible');
submenu.hidePopover?.();
}
}
}

View File

@@ -138,9 +138,9 @@ export default class WaDropdown extends WebAwesomeElement {
/** Gets all dropdown items slotted in the menu. */
private getItems(includeDisabled = false): WaDropdownItem[] {
const items = this.defaultSlot
.assignedElements({ flatten: true })
.filter(el => el.localName === 'wa-dropdown-item') as WaDropdownItem[];
const items = (this.defaultSlot?.assignedElements({ flatten: true }) ?? []).filter(
el => el.localName === 'wa-dropdown-item',
) as WaDropdownItem[];
return includeDisabled ? items : items.filter(item => !item.disabled);
}
@@ -165,9 +165,9 @@ export default class WaDropdown extends WebAwesomeElement {
/** Syncs item sizes with the dropdown's size property. */
private syncItemSizes() {
const items = this.defaultSlot
.assignedElements({ flatten: true })
.filter(el => el.localName === 'wa-dropdown-item') as WaDropdownItem[];
const items = (this.defaultSlot?.assignedElements({ flatten: true }) ?? []).filter(
el => el.localName === 'wa-dropdown-item',
) as WaDropdownItem[];
items.forEach(item => (item.size = this.size));
}
@@ -230,7 +230,7 @@ export default class WaDropdown extends WebAwesomeElement {
/** Shows the dropdown menu. This should only be called from within updated(). */
private async showMenu() {
const anchor = this.getTrigger();
if (!anchor) return;
if (!anchor || !this.popup || !this.menu) return;
const showEvent = new WaShowEvent();
this.dispatchEvent(showEvent);
@@ -270,6 +270,8 @@ export default class WaDropdown extends WebAwesomeElement {
/** Hides the dropdown menu. This should only be called from within updated(). */
private async hideMenu() {
if (!this.popup || !this.menu) return;
const hideEvent = new WaHideEvent({ source: this });
this.dispatchEvent(hideEvent);
if (hideEvent.defaultPrevented) {
@@ -720,12 +722,12 @@ export default class WaDropdown extends WebAwesomeElement {
nativeButton.setAttribute('aria-haspopup', 'menu');
nativeButton.setAttribute('aria-expanded', this.open ? 'true' : 'false');
this.menu.setAttribute('aria-expanded', 'false');
this.menu?.setAttribute('aria-expanded', 'false');
}
render() {
// On initial render, we want to use this.open, for everything else, we sync off of this.popup.active to get animations working.
let active = this.hasUpdated ? this.popup.active : this.open;
let active = this.hasUpdated ? this.popup?.active : this.open;
return html`
<wa-popup

View File

@@ -286,11 +286,11 @@ export default class WaPopup extends WebAwesomeElement {
private start() {
// We can't start the positioner without an anchor
if (!this.anchorEl || !this.active) {
if (!this.anchorEl || !this.active || !this.isConnected) {
return;
}
this.popup.showPopover?.();
this.popup?.showPopover?.();
this.cleanup = autoUpdate(this.anchorEl, this.popup, () => {
this.reposition();
@@ -299,7 +299,7 @@ export default class WaPopup extends WebAwesomeElement {
private async stop(): Promise<void> {
return new Promise(resolve => {
this.popup.hidePopover?.();
this.popup?.hidePopover?.();
if (this.cleanup) {
this.cleanup();
@@ -317,7 +317,7 @@ export default class WaPopup extends WebAwesomeElement {
/** Forces the popup to recalculate and reposition itself. */
reposition() {
// Nothing to do if the popup is inactive or the anchor doesn't exist
if (!this.active || !this.anchorEl) {
if (!this.active || !this.anchorEl || !this.popup) {
return;
}
@@ -498,7 +498,7 @@ export default class WaPopup extends WebAwesomeElement {
}
private updateHoverBridge = () => {
if (this.hoverBridge && this.anchorEl) {
if (this.hoverBridge && this.anchorEl && this.popup) {
const anchorRect = this.anchorEl.getBoundingClientRect();
const popupRect = this.popup.getBoundingClientRect();
const isVertical = this.placement.includes('top') || this.placement.includes('bottom');

View File

@@ -79,7 +79,7 @@ export default class WaTooltip extends WebAwesomeElement {
/** The amount of time to wait before showing the tooltip when the user mouses in. */
@property({ attribute: 'show-delay', type: Number }) showDelay = 150;
/** The amount of time to wait before hiding the tooltip when the user mouses out.. */
/** The amount of time to wait before hiding the tooltip when the user mouses out. */
@property({ attribute: 'hide-delay', type: Number }) hideDelay = 0;
/**

View File

@@ -85,6 +85,10 @@ export default css`
display: none;
}
.tree-item:not(.tree-item-has-expand-button):not(.tree-item-loading) .expand-button {
cursor: default;
}
.tree-item-loading .expand-icon-slot wa-icon {
display: none;
}

View File

@@ -99,20 +99,24 @@ export class DraggableElement {
document.addEventListener('pointerup', this.handleDragStop);
document.addEventListener('pointermove', this.handleDragMove);
document.addEventListener('pointercancel', this.handleDragStop);
document.addEventListener('touchend', this.handleDragStop);
document.addEventListener('touchmove', this.handleDragMove);
document.addEventListener('touchcancel', this.handleDragStop);
this.options.start(clientX, clientY);
};
private handleDragStop = (event: PointerEvent | TouchEvent) => {
const clientX = 'touches' in event ? event.touches[0].clientX : (event as PointerEvent).clientX;
const clientY = 'touches' in event ? event.touches[0].clientY : (event as PointerEvent).clientY;
const clientX = 'changedTouches' in event ? event.changedTouches[0].clientX : (event as PointerEvent).clientX;
const clientY = 'changedTouches' in event ? event.changedTouches[0].clientY : (event as PointerEvent).clientY;
this.isDragging = false;
document.removeEventListener('pointerup', this.handleDragStop);
document.removeEventListener('pointermove', this.handleDragMove);
document.removeEventListener('pointercancel', this.handleDragStop);
document.removeEventListener('touchend', this.handleDragStop);
document.removeEventListener('touchmove', this.handleDragMove);
document.removeEventListener('touchcancel', this.handleDragStop);
this.options.stop(clientX, clientY);
};
@@ -141,8 +145,10 @@ export class DraggableElement {
public stop() {
document.removeEventListener('pointerup', this.handleDragStop);
document.removeEventListener('pointermove', this.handleDragMove);
document.removeEventListener('pointercancel', this.handleDragStop);
document.removeEventListener('touchend', this.handleDragStop);
document.removeEventListener('touchmove', this.handleDragMove);
document.removeEventListener('touchcancel', this.handleDragStop);
this.element.removeEventListener('pointerdown', this.handleDragStart);
if (supportsTouch) {
this.element.removeEventListener('touchstart', this.handleDragStart);

View File

@@ -1,4 +1,15 @@
@layer wa-utilities {
/* Apply Flexbox with 0 specificity to ensure an align-items util produces a visible change */
:where(
.wa-align-items-start,
.wa-align-items-end,
.wa-align-items-center,
.wa-align-items-stretch,
.wa-align-items-baseline
) {
display: flex;
}
.wa-align-items-start {
align-items: flex-start;
}

View File

@@ -1,4 +1,16 @@
@layer wa-utilities {
/* Apply Flexbox with 0 specificity to ensure a justify-content util produces a visible change */
:where(
.wa-justify-content-start,
.wa-justify-content-end,
.wa-justify-content-center,
.wa-justify-content-space-around,
.wa-justify-content-space-between,
.wa-justify-content-space-evenly
) {
display: flex;
}
.wa-justify-content-start {
justify-content: flex-start;
}