mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-19 15:34:15 +00:00
Compare commits
23 Commits
v3.1.0
...
changelog-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75938684cc | ||
|
|
39ab883802 | ||
|
|
08ef969db3 | ||
|
|
72c4fb7af7 | ||
|
|
827d70b2f5 | ||
|
|
46aa1429a7 | ||
|
|
4b68a741bd | ||
|
|
bd58f7f0a1 | ||
|
|
e8f366835c | ||
|
|
70df6fea1f | ||
|
|
d76bef5dbb | ||
|
|
f8f54d543a | ||
|
|
5b0e3ed1e8 | ||
|
|
09ace44c17 | ||
|
|
f3de45803f | ||
|
|
ede1f0725d | ||
|
|
56b1196265 | ||
|
|
7e5f18ea97 | ||
|
|
c748721f12 | ||
|
|
6e5a720f51 | ||
|
|
f739121aaf | ||
|
|
5fb3625ee5 | ||
|
|
da206a8787 |
@@ -20,4 +20,5 @@ packages/**/*/src/react/index.ts
|
||||
node_modules
|
||||
|
||||
packages/**/*/_site
|
||||
packages/**/*/_bundle_
|
||||
packages/webawesome/docs/assets/scripts/prism-downloaded.js
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
3.0.0
|
||||
3.1.0
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@webawesome/monorepo",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@webawesome/monorepo",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
@@ -14023,7 +14023,7 @@
|
||||
},
|
||||
"packages/webawesome": {
|
||||
"name": "@awesome.me/webawesome",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "4.1.0",
|
||||
@@ -14048,7 +14048,7 @@
|
||||
},
|
||||
"packages/webawesome-pro": {
|
||||
"name": "@awesome.me/webawesome-pro",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "4.1.0",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@webawesome/monorepo",
|
||||
"private": true,
|
||||
"description": "A forward-thinking library of web components.",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"homepage": "https://webawesome.com/",
|
||||
"author": "Web Awesome",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -196,6 +196,8 @@
|
||||
</h2>
|
||||
<ul>
|
||||
<li><a href="/docs/utilities/align-items/">Align Items</a></li>
|
||||
<!-- Pending 3.2.0 release -->
|
||||
<!-- <li><a href="/docs/utilities/justify-content/">Justify Content</a></li> -->
|
||||
<li><a href="/docs/utilities/gap/">Gap</a></li>
|
||||
<li><a href="/docs/utilities/cluster/">Cluster</a></li>
|
||||
<li><a href="/docs/utilities/flank/">Flank</a></li>
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
// Search data
|
||||
const version = document.documentElement.getAttribute('data-version') || '';
|
||||
const res = await Promise.all([import('https://cdn.jsdelivr.net/npm/lunr/+esm'), fetch(`/search.json?v=${version}`)]);
|
||||
const res = await Promise.all([
|
||||
import('https://cdn.jsdelivr.net/npm/lunr/+esm'),
|
||||
fetch(`/search.json?v=${version}`),
|
||||
import('/assets/scripts/track.js').catch(() => null),
|
||||
]);
|
||||
const lunr = res[0].default;
|
||||
const searchData = await res[1].json();
|
||||
const searchIndex = lunr.Index.load(searchData.searchIndex);
|
||||
const map = searchData.map;
|
||||
const searchDebounce = 200;
|
||||
const queryTrackDelay = 1000;
|
||||
let searchTimeout;
|
||||
let queryTrackTimeout;
|
||||
let lastTrackedQuery = '';
|
||||
let resultSelected = false;
|
||||
|
||||
// Optional event tracking - works standalone if track.js isn't available
|
||||
const trackModule = res[2];
|
||||
const trackEvent = trackModule?.trackEvent || window.trackEvent || (() => {});
|
||||
|
||||
// We're using Turbo, so references to these elements aren't guaranteed to remain intact
|
||||
function getElements() {
|
||||
@@ -17,6 +29,24 @@ function getElements() {
|
||||
};
|
||||
}
|
||||
|
||||
function trackQuerySubmit(query, resultSelectedValue) {
|
||||
if (!query || query.length === 0) return;
|
||||
|
||||
const { results } = getElements();
|
||||
if (!results) return;
|
||||
|
||||
const matches = results.querySelectorAll('li').length;
|
||||
const truncatedQuery = query.length > 500 ? query.substring(0, 500) : query;
|
||||
|
||||
trackEvent('navigation:search_query_submit', {
|
||||
query: truncatedQuery,
|
||||
query_length: query.length,
|
||||
result_count: matches,
|
||||
has_results: matches > 0,
|
||||
result_selected: resultSelectedValue,
|
||||
});
|
||||
}
|
||||
|
||||
// Show the search dialog when slash (or CMD+K) is pressed and focus is not inside a form element
|
||||
document.addEventListener('keydown', event => {
|
||||
if (
|
||||
@@ -42,40 +72,98 @@ document.addEventListener('click', event => {
|
||||
|
||||
function show() {
|
||||
const { dialog, input, results } = getElements();
|
||||
if (!dialog || !input || !results) return;
|
||||
|
||||
const wasAlreadyOpen = dialog.open;
|
||||
|
||||
// Remove existing listeners before adding to prevent duplicates
|
||||
input.removeEventListener('input', handleInput);
|
||||
results.removeEventListener('click', handleSelection);
|
||||
dialog.removeEventListener('keydown', handleKeyDown);
|
||||
dialog.removeEventListener('wa-hide', handleClose);
|
||||
resultSelected = false;
|
||||
lastTrackedQuery = '';
|
||||
input.addEventListener('input', handleInput);
|
||||
results.addEventListener('click', handleSelection);
|
||||
dialog.addEventListener('keydown', handleKeyDown);
|
||||
dialog.addEventListener('wa-hide', handleClose);
|
||||
dialog.open = true;
|
||||
if (!wasAlreadyOpen) {
|
||||
trackEvent('navigation:search_dialog_open');
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
function cleanup() {
|
||||
const { dialog, input, results } = getElements();
|
||||
|
||||
if (!dialog || !input || !results) return;
|
||||
clearTimeout(searchTimeout);
|
||||
clearTimeout(queryTrackTimeout);
|
||||
input.removeEventListener('input', handleInput);
|
||||
results.removeEventListener('click', handleSelection);
|
||||
dialog.removeEventListener('keydown', handleKeyDown);
|
||||
dialog.removeEventListener('wa-hide', handleClose);
|
||||
dialog.open = false;
|
||||
|
||||
// Reset state to prevent leakage between dialog sessions
|
||||
resultSelected = false;
|
||||
lastTrackedQuery = '';
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
const { input } = getElements();
|
||||
async function handleClose() {
|
||||
const { dialog, input } = getElements();
|
||||
if (!dialog || !input) return;
|
||||
clearTimeout(queryTrackTimeout);
|
||||
queryTrackTimeout = null;
|
||||
dialog.removeEventListener('wa-hide', handleClose);
|
||||
if (!resultSelected) {
|
||||
const query = input.value.trim();
|
||||
if (query.length > 0 && query !== lastTrackedQuery) {
|
||||
trackQuerySubmit(query, false);
|
||||
lastTrackedQuery = query;
|
||||
}
|
||||
}
|
||||
|
||||
input.value = '';
|
||||
updateResults();
|
||||
try {
|
||||
await updateResults();
|
||||
} catch (error) {
|
||||
// Silently handle errors - UI cleanup should continue
|
||||
}
|
||||
cleanup();
|
||||
trackEvent('navigation:search_dialog_close');
|
||||
}
|
||||
|
||||
function handleInput() {
|
||||
const { input } = getElements();
|
||||
|
||||
if (!input) return;
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => updateResults(input.value), searchDebounce);
|
||||
clearTimeout(queryTrackTimeout);
|
||||
|
||||
const query = input.value.trim();
|
||||
|
||||
if (query.length === 0) {
|
||||
lastTrackedQuery = '';
|
||||
}
|
||||
|
||||
searchTimeout = setTimeout(async () => {
|
||||
await updateResults(query);
|
||||
if (query.length > 0 && query !== lastTrackedQuery) {
|
||||
queryTrackTimeout = setTimeout(() => {
|
||||
const { input: currentInput, results } = getElements();
|
||||
if (!currentInput || resultSelected) return;
|
||||
|
||||
const currentQuery = currentInput.value.trim();
|
||||
if (currentQuery === query && currentQuery !== lastTrackedQuery) {
|
||||
trackQuerySubmit(currentQuery, false);
|
||||
lastTrackedQuery = currentQuery;
|
||||
}
|
||||
}, queryTrackDelay);
|
||||
}
|
||||
}, searchDebounce);
|
||||
}
|
||||
|
||||
function handleKeyDown(event) {
|
||||
const { input, results } = getElements();
|
||||
if (!input || !results) return;
|
||||
|
||||
// Handle keyboard selections
|
||||
if (['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter'].includes(event.key)) {
|
||||
@@ -104,7 +192,12 @@ function handleKeyDown(event) {
|
||||
nextEl = items[items.length - 1];
|
||||
break;
|
||||
case 'Enter':
|
||||
currentEl?.querySelector('a')?.click();
|
||||
if (currentEl) {
|
||||
const link = currentEl.querySelector('a');
|
||||
if (link) {
|
||||
selectResult(link, 'keyboard_enter');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -121,27 +214,62 @@ function handleKeyDown(event) {
|
||||
}
|
||||
}
|
||||
|
||||
function selectResult(link, selectionMethod) {
|
||||
const { input, results } = getElements();
|
||||
if (!input || !link) return;
|
||||
|
||||
// Clear pending query tracking timeout to prevent duplicate events
|
||||
clearTimeout(queryTrackTimeout);
|
||||
queryTrackTimeout = null;
|
||||
resultSelected = true; // Set immediately so timeout callback (if executing) sees it
|
||||
|
||||
const query = input.value.trim();
|
||||
if (!link.dataset.searchResultIndex) return;
|
||||
const resultIndex = parseInt(link.dataset.searchResultIndex, 10);
|
||||
if (isNaN(resultIndex) || resultIndex < 1) return;
|
||||
|
||||
const resultUrl = link.dataset.searchResultUrl || link.getAttribute('href');
|
||||
if (!resultUrl) return;
|
||||
lastTrackedQuery = query;
|
||||
trackQuerySubmit(query, true);
|
||||
trackEvent('navigation:search_result_click', {
|
||||
query,
|
||||
result_index: resultIndex,
|
||||
result_url: resultUrl,
|
||||
selection_method: selectionMethod,
|
||||
});
|
||||
|
||||
const { dialog } = getElements();
|
||||
if (dialog) {
|
||||
dialog.removeEventListener('wa-hide', handleClose);
|
||||
cleanup();
|
||||
trackEvent('navigation:search_dialog_close');
|
||||
dialog.open = false;
|
||||
}
|
||||
|
||||
if (window.Turbo) {
|
||||
Turbo.visit(resultUrl);
|
||||
} else {
|
||||
location.href = resultUrl;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelection(event) {
|
||||
const link = event.target.closest('a');
|
||||
|
||||
if (link) {
|
||||
event.preventDefault();
|
||||
hide();
|
||||
|
||||
if (window.Turbo) {
|
||||
Turbo.visit(link.href);
|
||||
} else {
|
||||
location.href = link.href;
|
||||
}
|
||||
selectResult(link, 'mouse_click');
|
||||
}
|
||||
}
|
||||
|
||||
// Queries the search index and updates the results
|
||||
async function updateResults(query = '') {
|
||||
const { dialog, input, results } = getElements();
|
||||
|
||||
if (!dialog || !input || !results) return;
|
||||
try {
|
||||
const hasQuery = query.length > 0;
|
||||
const trimmedQuery = query.trim();
|
||||
const hasQuery = trimmedQuery.length > 0;
|
||||
let matches = [];
|
||||
|
||||
if (hasQuery) {
|
||||
@@ -149,13 +277,13 @@ async function updateResults(query = '') {
|
||||
const seenRefs = new Set();
|
||||
|
||||
// Start with a standard search to get the best "exact match" result
|
||||
searchIndex.search(`${query}`).forEach(match => {
|
||||
searchIndex.search(`${trimmedQuery}`).forEach(match => {
|
||||
matches.push(match);
|
||||
seenRefs.add(match.ref);
|
||||
});
|
||||
|
||||
// Add wildcard matches if not already included
|
||||
searchIndex.search(`${query}*`).forEach(match => {
|
||||
searchIndex.search(`${trimmedQuery}*`).forEach(match => {
|
||||
if (!seenRefs.has(match.ref)) {
|
||||
matches.push(match);
|
||||
seenRefs.add(match.ref);
|
||||
@@ -163,11 +291,10 @@ async function updateResults(query = '') {
|
||||
});
|
||||
|
||||
// Add fuzzy search matches last
|
||||
const fuzzyTokens = query
|
||||
const fuzzyTokens = trimmedQuery
|
||||
.split(' ')
|
||||
.map(term => `${term}~1`)
|
||||
.join(' ');
|
||||
|
||||
searchIndex.search(fuzzyTokens).forEach(match => {
|
||||
if (!seenRefs.has(match.ref)) {
|
||||
matches.push(match);
|
||||
@@ -180,12 +307,12 @@ async function updateResults(query = '') {
|
||||
|
||||
dialog.classList.toggle('has-results', hasQuery && hasResults);
|
||||
dialog.classList.toggle('no-results', hasQuery && !hasResults);
|
||||
|
||||
input.setAttribute('aria-activedescendant', '');
|
||||
results.innerHTML = '';
|
||||
|
||||
matches.forEach((match, index) => {
|
||||
const page = map[match.ref];
|
||||
if (!page || !page.url) return;
|
||||
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
const displayTitle = page.title ?? '';
|
||||
@@ -197,12 +324,10 @@ async function updateResults(query = '') {
|
||||
li.setAttribute('role', 'option');
|
||||
li.setAttribute('id', `search-result-item-${match.ref}`);
|
||||
li.setAttribute('data-selected', index === 0 ? 'true' : 'false');
|
||||
|
||||
if (page.url === '/') icon = 'home';
|
||||
if (page.url.startsWith('/docs/utilities/native')) icon = 'code';
|
||||
if (page.url.startsWith('/docs/components')) icon = 'puzzle-piece';
|
||||
if (page.url.startsWith('/docs/theme') || page.url.startsWith('/docs/restyle')) icon = 'palette';
|
||||
|
||||
a.href = page.url;
|
||||
a.innerHTML = `
|
||||
<div class="site-search-result-icon" aria-hidden="true">
|
||||
@@ -218,6 +343,9 @@ async function updateResults(query = '') {
|
||||
a.querySelector('.site-search-result-description').textContent = displayDescription;
|
||||
a.querySelector('.site-search-result-url').textContent = displayUrl;
|
||||
|
||||
// Use 1-based indexing for analytics
|
||||
a.dataset.searchResultIndex = (index + 1).toString();
|
||||
a.dataset.searchResultUrl = page.url;
|
||||
li.appendChild(a);
|
||||
results.appendChild(li);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -130,6 +130,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* dialogs */
|
||||
wa-dialog:has([slot='footer']) [slot='footer'] {
|
||||
border-block-start: var(--wa-border-width-s) solid var(--wa-color-surface-border);
|
||||
flex-grow: 1; /* make footer contents span entire width of dialog */
|
||||
padding-block-start: var(--wa-space-l);
|
||||
}
|
||||
|
||||
/* anchor headings */
|
||||
.anchor-heading a {
|
||||
opacity: 0;
|
||||
@@ -250,7 +257,6 @@
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* buttons with icon toggle on hover */
|
||||
wa-button .icon-hover {
|
||||
@@ -262,6 +268,13 @@
|
||||
wa-button:hover .icon-hover {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/* buttons that are "shushed" (visually muted) by default, but have their full presentation otherwise */
|
||||
wa-button.shush {
|
||||
&:not(:hover):not(active)::part(base) {
|
||||
color: var(--wa-color-text-quiet);
|
||||
}
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region resets */
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -11,6 +11,21 @@ Web Awesome follows [Semantic Versioning](https://semver.org/). Breaking changes
|
||||
|
||||
Components with the <wa-badge variant="warning">Experimental</wa-badge> badge should not be used in production. They are made available as release candidates for development and testing purposes. As such, changes to experimental components will not be subject to semantic versioning.
|
||||
|
||||
## Next
|
||||
|
||||
- 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
|
||||
|
||||
- Added `<wa-combobox>` as an experimental pro component [issue:1074]
|
||||
@@ -516,4 +531,4 @@ Many of these changes and improvements were the direct result of feedback from u
|
||||
|
||||
</details>
|
||||
|
||||
Did we miss something? [Let us know!](https://github.com/shoelace-style/webawesome/discussions)
|
||||
Did we miss something? [Let us know!](https://github.com/shoelace-style/webawesome/discussions)
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
---
|
||||
title: Align Items
|
||||
description: Align items utilities set the gap property of flex and grid containers, like other Web Awesome layout utilities.
|
||||
description: Align items utilities align items within flex and grid containers on the cross axis.
|
||||
layout: docs
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
|
||||
Web Awesome includes classes to set the `align-items` property of flex and grid containers. They can be used alongside other Web Awesome layout utilities, like [cluster](/docs/utilities/cluster) and [stack](/docs/utilities/stack), to align children in container on the container's cross axis.
|
||||
Web Awesome includes classes to set the `align-items` property of flex and grid containers. Use them alongside other Web Awesome layout utilities, like [cluster](/docs/utilities/cluster) and [stack](/docs/utilities/stack), to align items in a container on the container's [cross axis](#whats-the-cross-axis).
|
||||
|
||||
| Class Name | `align-items` Value | Preview |
|
||||
| ------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `wa-align-items-baseline` | `baseline` | <div class="wa-cluster wa-align-items-baseline preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-align-items-center` | `center` | <div class="wa-cluster wa-align-items-center preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-align-items-end` | `flex-end` | <div class="wa-cluster wa-align-items-end preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-align-items-start` | `flex-start` | <div class="wa-cluster wa-align-items-start preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-align-items-stretch` | `stretch` | <div class="wa-cluster wa-align-items-stretch preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| Class Name | `align-items` Value | Preview |
|
||||
| ------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `wa-align-items-baseline` | `baseline` | <div class="wa-cluster wa-gap-2xs wa-align-items-baseline preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-align-items-center` | `center` | <div class="wa-cluster wa-gap-2xs wa-align-items-center preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-align-items-end` | `flex-end` | <div class="wa-cluster wa-gap-2xs wa-align-items-end preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-align-items-start` | `flex-start` | <div class="wa-cluster wa-gap-2xs wa-align-items-start preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-align-items-stretch` | `stretch` | <div class="wa-cluster wa-gap-2xs wa-align-items-stretch preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
|
||||
## What's a Cross Axis?
|
||||
## What's the Cross Axis?
|
||||
|
||||
The cross axis runs perpendicular to a flex container's content direction. For containers where `flex-direction` is `row` and content flows in the inline direction, the cross axis runs in the block direction. For containers where `flex-direction` is `column` and content flows in the block direction, the cross axis runs in the inline direction.
|
||||
The cross axis runs perpendicular to a container's content direction. For containers where `flex-direction` is `row` and content flows in the inline direction, the cross axis runs in the block direction. For containers where `flex-direction` is `column` and content flows in the block direction, the cross axis runs in the inline direction.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -6,28 +6,37 @@ 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>
|
||||
|
||||
Web Awesome includes classes to set the `gap` property of flex and grid containers. They can be used alongside other Web Awesome layout utilities, like [cluster](/docs/utilities/cluster) and [stack](/docs/utilities/stack), to change the space between items.
|
||||
Or even by themselves — all gap properties also set `display: flex` with a specificity of 0 so that it can be trivially overridden.
|
||||
Web Awesome includes classes to set the `gap` property of flex and grid containers. Use them alone to create a flex container with a gap, or use them alongside other Web Awesome layout utilities, like [cluster](/docs/utilities/cluster) and [stack](/docs/utilities/stack), to change the space between items.
|
||||
|
||||
Besides `wa-gap-0`, which sets `gap` to zero, each class corresponds to one of the [`--wa-space-*`](/docs/tokens/space) tokens in your theme.
|
||||
|
||||
| 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="preview-wrapper wa-cluster wa-gap-4xl"><div class="preview-block"></div><div class="preview-block"></div></div> | -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
40
packages/webawesome/docs/docs/utilities/justify-content.md
Normal file
40
packages/webawesome/docs/docs/utilities/justify-content.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Justify Content
|
||||
description: Justify content utilities determine how space is distributed between items in flex and grid containers.
|
||||
layout: docs
|
||||
tags: layoutUtilities
|
||||
unpublished: true
|
||||
unlisted: true
|
||||
---
|
||||
|
||||
<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(--layout-example-element-background);
|
||||
border-radius: var(--wa-border-radius-s);
|
||||
min-block-size: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
Web Awesome includes classes to set the `justify-content` property of flex and grid containers. Use them alongside other Web Awesome layout utilities, like [cluster](/docs/utilities/cluster) and [stack](/docs/utilities/stack), to distribute space between items along the container's [main axis](#whats-the-main-axis).
|
||||
|
||||
| Class Name | `justify-content` Value | Preview |
|
||||
| ---------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `wa-justify-content-start` | `flex-start` | <div class="wa-cluster wa-gap-2xs wa-justify-content-start preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-justify-content-end` | `flex-end` | <div class="wa-cluster wa-gap-2xs wa-justify-content-end preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-justify-content-center` | `center` | <div class="wa-cluster wa-gap-2xs wa-justify-content-center preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-justify-content-space-around` | `space-around` | <div class="wa-cluster wa-gap-2xs wa-justify-content-space-around preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-justify-content-space-between` | `space-between` | <div class="wa-cluster wa-gap-2xs wa-justify-content-space-between preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
| `wa-justify-content-space-evenly` | `space-evenly` | <div class="wa-cluster wa-gap-2xs wa-justify-content-space-evenly preview-wrapper"><div class="preview-block"></div><div class="preview-block"></div></div> |
|
||||
|
||||
## What's the Main Axis?
|
||||
|
||||
The main axis runs parallel to a container's content direction. For grid containers and flex containers where `flex-direction` is `row`, the main axis runs in the inline direction. For containers where `flex-direction` is `column`, the main axis runs in the block direction.
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
:host {
|
||||
display: block;
|
||||
import { css } from "lit";
|
||||
|
||||
export default css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -10,8 +10,8 @@ const monorepoRoot = path.resolve(__dirname, '..', '..', '..');
|
||||
const rootPackageJSONFile = path.join(monorepoRoot, 'package.json');
|
||||
const webawesomePackageJSONFile = path.join(path.resolve(__dirname, '..'), 'package.json');
|
||||
|
||||
const rootPackageJSON = JSON.parse(fs.readFileSync(rootPackageJSONFile, { encoding: "utf8" }));
|
||||
const webawesomePackageJSON = JSON.parse(fs.readFileSync(webawesomePackageJSONFile, { encoding: "utf8" }));
|
||||
const rootPackageJSON = JSON.parse(fs.readFileSync(rootPackageJSONFile, { encoding: 'utf8' }));
|
||||
const webawesomePackageJSON = JSON.parse(fs.readFileSync(webawesomePackageJSONFile, { encoding: 'utf8' }));
|
||||
|
||||
const currentVersion = webawesomePackageJSON.version;
|
||||
rootPackageJSON.version = currentVersion;
|
||||
@@ -19,7 +19,7 @@ rootPackageJSON.version = currentVersion;
|
||||
fs.writeFileSync(rootPackageJSONFile, JSON.stringify(rootPackageJSON, null, 2));
|
||||
|
||||
const versionsFile = path.join(monorepoRoot, 'VERSIONS.txt');
|
||||
const versions = fs.readFileSync(versionsFile, { encoding: "utf8" }).split(/\r?\n/);
|
||||
const versions = fs.readFileSync(versionsFile, { encoding: 'utf8' }).split(/\r?\n/);
|
||||
|
||||
// TODO: Make this smart and understand semver and "insert" in the correct spot instead of appending.
|
||||
if (!versions.includes(currentVersion)) {
|
||||
|
||||
@@ -1277,7 +1277,14 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
})}
|
||||
part="trigger-container form-control"
|
||||
>
|
||||
<div part="form-control-label" class="label" id="form-control-label">
|
||||
<div
|
||||
part="form-control-label"
|
||||
class=${classMap({
|
||||
label: true,
|
||||
'has-label': hasLabel,
|
||||
})}
|
||||
id="form-control-label"
|
||||
>
|
||||
<slot name="label">${this.label}</slot>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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?.();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,7 +6,6 @@ export default css`
|
||||
}
|
||||
|
||||
.text-field {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: start;
|
||||
|
||||
@@ -48,6 +48,27 @@ describe('<wa-input>', () => {
|
||||
expect(input.title).to.equal('Test');
|
||||
});
|
||||
|
||||
it('should have label with "has-label" class if label has a slotted element', async () => {
|
||||
const el = await fixture<WaInput>(html` <wa-input><span slot="label">Name</span></wa-input> `);
|
||||
await el.updateComplete;
|
||||
const label = el.shadowRoot!.querySelector('[part~="form-control-label"]')!;
|
||||
expect(label.classList.contains('has-label')).to.equal(true);
|
||||
});
|
||||
|
||||
it('should have label with "has-label" class if label is provided as an attribute', async () => {
|
||||
const el = await fixture<WaInput>(html` <wa-input label="Name"></wa-input> `);
|
||||
await el.updateComplete;
|
||||
const label = el.shadowRoot!.querySelector('[part~="form-control-label"]')!;
|
||||
expect(label.classList.contains('has-label')).to.equal(true);
|
||||
});
|
||||
|
||||
it('should not have "has-label" class on label if no label content is provided', async () => {
|
||||
const el = await fixture<WaInput>(html` <wa-input></wa-input> `);
|
||||
await el.updateComplete;
|
||||
const label = el.shadowRoot!.querySelector('[part~="form-control-label"]')!;
|
||||
expect(label.classList.contains('has-label')).to.equal(false);
|
||||
});
|
||||
|
||||
it('should be disabled with the disabled attribute', async () => {
|
||||
const el = await fixture<WaInput>(html` <wa-input disabled></wa-input> `);
|
||||
await el.updateComplete;
|
||||
|
||||
@@ -342,7 +342,15 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
(typeof this.value === 'number' || (this.value && this.value.length > 0));
|
||||
|
||||
return html`
|
||||
<label part="form-control-label label" class="label" for="input" aria-hidden=${hasLabel ? 'false' : 'true'}>
|
||||
<label
|
||||
part="form-control-label label"
|
||||
class=${classMap({
|
||||
label: true,
|
||||
'has-label': hasLabel,
|
||||
})}
|
||||
for="input"
|
||||
aria-hidden=${hasLabel ? 'false' : 'true'}
|
||||
>
|
||||
<slot name="label">${this.label}</slot>
|
||||
</label>
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -352,7 +352,10 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
<label
|
||||
part="form-control-label"
|
||||
id="label"
|
||||
class="label"
|
||||
class=${classMap({
|
||||
label: true,
|
||||
'has-label': hasLabel,
|
||||
})}
|
||||
aria-hidden=${hasLabel ? 'false' : 'true'}
|
||||
@click=${this.handleLabelClick}
|
||||
>
|
||||
|
||||
@@ -938,7 +938,10 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
<label
|
||||
id="label"
|
||||
part="form-control-label label"
|
||||
class="label"
|
||||
class=${classMap({
|
||||
label: true,
|
||||
'has-label': hasLabel,
|
||||
})}
|
||||
aria-hidden=${hasLabel ? 'false' : 'true'}
|
||||
@click=${this.handleLabelClick}
|
||||
>
|
||||
|
||||
@@ -807,7 +807,7 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
|
||||
id="label"
|
||||
part="label"
|
||||
for=${this.isRange ? 'thumb-min' : 'text-box'}
|
||||
class=${classMap({ vh: !hasLabel })}
|
||||
class=${classMap({ vh: !hasLabel, 'has-label': hasLabel })}
|
||||
@pointerdown=${this.handleLabelPointerDown}
|
||||
>
|
||||
<slot name="label">${this.label}</slot>
|
||||
|
||||
@@ -331,7 +331,15 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
|
||||
const hasHint = this.hint ? true : !!hasHintSlot;
|
||||
|
||||
return html`
|
||||
<label part="form-control-label label" class="label" for="input" aria-hidden=${hasLabel ? 'false' : 'true'}>
|
||||
<label
|
||||
part="form-control-label label"
|
||||
class=${classMap({
|
||||
label: true,
|
||||
'has-label': hasLabel,
|
||||
})}
|
||||
for="input"
|
||||
aria-hidden=${hasLabel ? 'false' : 'true'}
|
||||
>
|
||||
<slot name="label">${this.label}</slot>
|
||||
</label>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -28,3 +28,4 @@ export type { WaSlideChangeEvent } from './slide-change.js';
|
||||
export type { WaStartEvent } from './start.js';
|
||||
export type { WaTabHideEvent } from './tab-hide.js';
|
||||
export type { WaTabShowEvent } from './tab-show.js';
|
||||
export type { WaVideoChangeEvent } from './video-change.js';
|
||||
|
||||
26
packages/webawesome/src/events/video-change.ts
Normal file
26
packages/webawesome/src/events/video-change.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export class WaVideoChangeEvent extends Event {
|
||||
readonly detail: WaVideoChangeEventDetail;
|
||||
|
||||
constructor(detail: WaVideoChangeEventDetail) {
|
||||
super('wa-video-change', { bubbles: true, cancelable: false, composed: true });
|
||||
this.detail = detail;
|
||||
}
|
||||
}
|
||||
|
||||
export interface WaVideoChangeEventDetail {
|
||||
previousIndex: number;
|
||||
currentIndex: number;
|
||||
video: {
|
||||
title?: string;
|
||||
poster?: string;
|
||||
duration?: string;
|
||||
sources: { src: string; type: string }[];
|
||||
tracks: { src: string; kind: string; srclang: string; label: string }[];
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface GlobalEventHandlersEventMap {
|
||||
'wa-video-change': WaVideoChangeEvent;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -6,8 +6,14 @@ export default css`
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Treat wrapped labels, inputs, and hints as direct children of the host element */
|
||||
[part~='form-control'] {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
/* Label */
|
||||
:is([part~='form-control-label'], [part~='label']):has(*:not(:empty)) {
|
||||
:is([part~='form-control-label'], [part~='label']):has(*:not(:empty)),
|
||||
:is([part~='form-control-label'], [part~='label']).has-label {
|
||||
display: inline-flex;
|
||||
color: var(--wa-form-control-label-color);
|
||||
font-weight: var(--wa-form-control-label-font-weight);
|
||||
@@ -29,7 +35,6 @@ export default css`
|
||||
line-height: var(--wa-form-control-hint-line-height);
|
||||
margin-block-start: 0.5em;
|
||||
font-size: var(--wa-font-size-smaller);
|
||||
line-height: var(--wa-form-control-label-line-height);
|
||||
|
||||
&:not(.has-slotted) {
|
||||
display: none;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@import url('utilities/scroll-lock.css');
|
||||
@import url('utilities/placeholder.css');
|
||||
@import url('utilities/align-items.css');
|
||||
@import url('utilities/justify-content.css');
|
||||
@import url('utilities/border-radius.css');
|
||||
@import url('utilities/gap.css');
|
||||
@import url('utilities/text.css');
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -45,4 +45,7 @@
|
||||
.wa-gap-3xl {
|
||||
gap: var(--wa-space-3xl);
|
||||
}
|
||||
.wa-gap-4xl {
|
||||
gap: var(--wa-space-4xl);
|
||||
}
|
||||
}
|
||||
|
||||
32
packages/webawesome/src/styles/utilities/justify-content.css
Normal file
32
packages/webawesome/src/styles/utilities/justify-content.css
Normal file
@@ -0,0 +1,32 @@
|
||||
@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;
|
||||
}
|
||||
.wa-justify-content-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.wa-justify-content-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.wa-justify-content-space-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
.wa-justify-content-space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.wa-justify-content-space-evenly {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
}
|
||||
@@ -27,11 +27,11 @@
|
||||
[class*='wa-cluster'] {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
:where([class*='wa-cluster']) {
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
[class*='wa-frame'] {
|
||||
display: flex;
|
||||
aspect-ratio: 1 / 1;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -98,6 +97,7 @@
|
||||
|
||||
:where([class*='wa-frame']) {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
@@ -118,7 +118,6 @@
|
||||
[class*='wa-split'] {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
[class*='wa-split'],
|
||||
@@ -141,6 +140,7 @@
|
||||
|
||||
:where([class*='wa-split']) {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
@@ -149,11 +149,11 @@
|
||||
[class*='wa-stack'] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
:where([class*='wa-stack']) {
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
/* #endregion */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user