Compare commits

...

28 Commits

Author SHA1 Message Date
Cory LaViska
8be2eea04a Merge branch 'next' into card-image-rounding 2025-07-17 11:55:16 -04:00
Cory LaViska
c9e6895ef7 fix pill buttons in groups; closes #1165 (#1191) 2025-07-17 11:28:57 -04:00
Konnor Rogers
64c8647dee build speed improvements and add optional incremental builds (#1183)
* build speed improvments

* fix button

* prettier

* move things to plugins / transformers

* final fixes

* build fixes

* fix build issues

* add comment

* build sstuf

* prettier

* fix badge.css

* fix build stuff

* fix eleventy stuff

* prettier
2025-07-16 17:37:47 -04:00
Cory LaViska
c328671992 fix media rounding; closes #1107 2025-07-16 17:01:29 -04:00
randomguy-2650
9e9a00547a fix: fix incorrect documentation link used in @documentation JSDoc (#1187) 2025-07-16 12:02:35 -04:00
Lindsay M
f747483e32 Add --track-height to <wa-progress-bar> (#1159)
* add `--track-height` to `<wa-progress-bar>`

* fix custom `<wa-progress-bar>` height instances

* add changelog
2025-07-14 18:16:19 -04:00
Lindsay M
8719bbc88b Fix extra whitespace in <wa-textarea> with resize="auto" (#1175)
* fix extra whitespace

* fix the fix

* add changelog
2025-07-14 17:56:53 -04:00
Cory LaViska
0ff5e7fb7a Fix domain in documentation links (#1180)
* fix domain in doc links

* unprettier
2025-07-14 17:19:28 -04:00
Konnor Rogers
2a52b2766f fix badge pulsing (#1173)
* fix badge pulsing

* use and document `--pulse-color`

* Add changelog entry

---------

Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-07-14 17:00:35 -04:00
Cory LaViska
1e8bbc3b06 fix domain (#1179) 2025-07-14 16:57:44 -04:00
Cory LaViska
1ef9cb9601 add missing dependency (#1176) 2025-07-14 16:55:43 -04:00
Lindsay M
5b54410212 Revise native styles documentation (#1153) 2025-07-14 16:21:15 -04:00
Joe Marquardt
f621fbb224 fix the build on windows (#1148)
- make-react importPath variable
 - esbuild entryPoints globby usage requires forward slashes
2025-07-14 16:13:11 -04:00
Cory LaViska
f5624fbf4a Slider hint (#1174)
* update example

* show hint in correct position when present

* update example

* update example
2025-07-14 16:05:02 -04:00
randomguy-2650
f65bc3918e fix: fix the language name in it.ts (#1171) 2025-07-14 15:48:18 -04:00
Lindsay M
f49c10b05b Replace old --wa-flow-spacing with --wa-content-spacing (#1157)
* remove old `--wa-flow-spacing`

* add changelog
2025-07-11 15:03:18 -04:00
Konnor Rogers
3b6c018fed Fix react imports to include a class name (#1158) 2025-07-10 15:40:45 -04:00
Lindsay M
c10e1e77c9 Update <wa-select> "Sizes" documentation (#1162) 2025-07-10 15:37:37 -04:00
Lindsay M
a1e879035c Fix code example styles (#1156) 2025-07-10 15:36:03 -04:00
Lindsay M
d2625fccab Remove extra stylesheets from CodePen editing (#1161) 2025-07-10 14:09:50 -04:00
randomguy-2650
29c8ad08bb Fix links pointing to / instead of the current page (#1145) 2025-07-09 13:11:24 -04:00
Mischa
0f68e6f0dd Refactor copy button documentation layout for improved alignment with input (#1141) 2025-07-09 13:09:53 -04:00
Tu Nguyen
4dca2b9541 Fixed a minor doc typo (#1137) 2025-07-09 12:17:20 -04:00
konnorrogers
36ccaa4aaa bump package.json 2025-07-03 18:32:12 -04:00
konnorrogers
c86d7635aa Bump package.json version 2025-07-03 18:31:59 -04:00
Konnor Rogers
d84e842a4e fixing select + tests (#1136)
* fixing select + tests

* fix TS

* prettier

* prettier

* fix CI test

* fix changelog
2025-07-03 18:29:02 -04:00
Lindsay M
fb8c06235f Fix link hover styles (#1135) 2025-07-03 14:20:51 -04:00
Cory LaViska
f6a10e9dda update changelog (#1134) 2025-07-03 12:06:38 -04:00
106 changed files with 1390 additions and 958 deletions

22
package-lock.json generated
View File

@@ -13975,7 +13975,7 @@
},
"packages/webawesome": {
"name": "@awesome.me/webawesome",
"version": "3.0.0-beta.1",
"version": "3.0.0-beta.2",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^4.1.0",
@@ -13984,6 +13984,7 @@
"@shoelace-style/localize": "^3.2.1",
"composed-offset-position": "^0.0.6",
"lit": "^3.2.1",
"nanoid": "^5.1.5",
"qr-creator": "^1.0.0",
"style-observer": "^0.0.7"
},
@@ -13994,7 +13995,7 @@
},
"packages/webawesome-pro": {
"name": "@shoelace-style/webawesome-pro",
"version": "3.0.0-beta.1",
"version": "3.0.0-beta.2",
"license": "TODO",
"dependencies": {
"@ctrl/tinycolor": "^4.1.0",
@@ -14010,6 +14011,23 @@
"engines": {
"node": ">=14.17.0"
}
},
"packages/webawesome/node_modules/nanoid": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz",
"integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^18 || >=20"
}
}
}
}

View File

@@ -1,30 +1,63 @@
import { parse as HTMLParse } from 'node-html-parser';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { parse } from 'path';
import { anchorHeadingsPlugin } from './_utils/anchor-headings.js';
import { codeExamplesPlugin } from './_utils/code-examples.js';
import { copyCodePlugin } from './_utils/copy-code.js';
import { currentLink } from './_utils/current-link.js';
import { highlightCodePlugin } from './_utils/highlight-code.js';
import { anchorHeadingsTransformer } from './_transformers/anchor-headings.js';
import { codeExamplesTransformer } from './_transformers/code-examples.js';
import { copyCodeTransformer } from './_transformers/copy-code.js';
import { currentLinkTransformer } from './_transformers/current-link.js';
import { highlightCodeTransformer } from './_transformers/highlight-code.js';
import { outlineTransformer } from './_transformers/outline.js';
import { getComponents } from './_utils/manifest.js';
import { markdown } from './_utils/markdown.js';
// import { formatCodePlugin } from './_utils/format-code.js';
import { SimulateWebAwesomeApp } from './_utils/simulate-webawesome-app.js';
// import { formatCodePlugin } from './_plugins/format-code.js';
// import litPlugin from '@lit-labs/eleventy-plugin-lit';
import { readFile } from 'fs/promises';
import nunjucks from 'nunjucks';
import process from 'process';
import * as url from 'url';
import { outlinePlugin } from './_utils/outline.js';
import { replaceTextPlugin } from './_utils/replace-text.js';
import { searchPlugin } from './_utils/search.js';
import { replaceTextPlugin } from './_plugins/replace-text.js';
import { searchPlugin } from './_plugins/search.js';
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const isDev = process.argv.includes('--develop');
const passThroughExtensions = ['js', 'css', 'png', 'svg', 'jpg', 'mp4'];
async function getPackageData() {
return JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf-8'));
}
export default async function (eleventyConfig) {
const packageData = JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf-8'));
const docsDir = path.join(process.env.BASE_DIR || '.', 'docs');
const allComponents = getComponents();
let packageData = await getPackageData();
let allComponents = getComponents();
const distDir = process.env.UNBUNDLED_DIST_DIRECTORY || path.resolve(__dirname, '../dist');
const customElementsManifest = path.join(distDir, 'custom-elements.json');
const stylesheets = path.join(distDir, 'styles');
eleventyConfig.addWatchTarget(customElementsManifest);
eleventyConfig.setWatchThrottleWaitTime(10); // in milliseconds
eleventyConfig.on('eleventy.beforeWatch', async function (changedFiles) {
let updatePackageData = false;
let updateComponentData = false;
changedFiles.forEach(file => {
if (file.includes('package.json')) {
updatePackageData = true;
}
if (file.includes('custom-elements.json')) {
updateComponentData = true;
}
});
if (updatePackageData) {
packageData = await getPackageData();
}
if (updateComponentData) {
allComponents = getComponents();
}
});
/**
* If you plan to add or remove any of these extensions, make sure to let either Konnor or Cory know as these
@@ -52,7 +85,7 @@ export default async function (eleventyConfig) {
// Template filters - {{ content | filter }}
eleventyConfig.addFilter('inlineMarkdown', content => markdown.renderInline(content || ''));
eleventyConfig.addFilter('markdown', content => markdown.render(content || ''));
eleventyConfig.addFilter('stripExtension', string => parse(string + '').name);
eleventyConfig.addFilter('stripExtension', string => path.parse(string + '').name);
eleventyConfig.addFilter('stripPrefix', content => content.replace(/^wa-/, ''));
// Trims whitespace and pipes from the start and end of a string. Useful for CEM types, which can be pipe-delimited.
// With Prettier 3, this means a leading pipe will exist be present when the line wraps.
@@ -109,31 +142,6 @@ export default async function (eleventyConfig) {
return '';
});
eleventyConfig.addTransform('second-nunjucks-transform', function NunjucksTransform(content) {
// For a server build, we expect a server to run the second transform.
if (serverBuild) {
return content;
}
// Only run the transform on files nunjucks would transform.
if (!this.page.inputPath.match(/.(md|html|njk)$/)) {
return content;
}
/** This largely mimics what an app would do and just stubs out what we don't care about. */
return nunjucks.renderString(content, {
// Stub the server EJS shortcodes.
currentUser: {
hasPro: false,
},
server: {
head: '',
loginOrAvatar: '',
flashes: '',
},
});
});
// Paired shortcodes - {% shortCode %}content{% endShortCode %}
eleventyConfig.addPairedShortcode('markdown', content => markdown.render(content || ''));
@@ -152,33 +160,33 @@ export default async function (eleventyConfig) {
eleventyConfig.setLibrary('md', markdown);
// Add anchors to headings
eleventyConfig.addPlugin(anchorHeadingsPlugin({ container: '#content' }));
eleventyConfig.addTransform('doc-transforms', function (content) {
let doc = HTMLParse(content, { blockTextElements: { code: true } });
// Add an outline to the page
eleventyConfig.addPlugin(
outlinePlugin({
container: '#content',
target: '.outline-links',
selector: 'h2, h3',
ifEmpty: doc => {
doc.querySelector('#outline')?.remove();
},
}),
);
const transformers = [
anchorHeadingsTransformer({ container: '#content' }),
outlineTransformer({
container: '#content',
target: '.outline-links',
selector: 'h2, h3',
ifEmpty: doc => {
doc.querySelector('#outline')?.remove();
},
}),
// Add current link classes
currentLinkTransformer(),
codeExamplesTransformer(),
highlightCodeTransformer(),
copyCodeTransformer(),
];
// Add current link classes
eleventyConfig.addPlugin(currentLink());
for (const transformer of transformers) {
transformer.call(this, doc);
}
// Add code examples for `<code class="example">` blocks
eleventyConfig.addPlugin(codeExamplesPlugin());
return doc.toString();
});
// Highlight code blocks with Prism
eleventyConfig.addPlugin(highlightCodePlugin());
// Add copy code buttons to code blocks
eleventyConfig.addPlugin(copyCodePlugin);
// Various text replacements
eleventyConfig.addPlugin(
replaceTextPlugin([
{
@@ -222,15 +230,14 @@ export default async function (eleventyConfig) {
// }
let assetsDir = path.join(process.env.BASE_DIR || 'docs', 'assets');
fs.cpSync(assetsDir, path.join(eleventyConfig.directories.output, 'assets'), { recursive: true });
const siteAssetsDir = path.join(eleventyConfig.directories.output, 'assets');
fs.cpSync(assetsDir, siteAssetsDir, { recursive: true });
for (let glob of passThrough) {
eleventyConfig.addPassthroughCopy(glob);
}
// // SSR plugin
// // Make sure this is the last thing, we don't want to run the risk of accidentally transforming shadow roots with
// // the nunjucks 2nd transform.
// if (!isDev) {
// //
// // Problematic components in SSR land:
@@ -253,6 +260,23 @@ export default async function (eleventyConfig) {
// componentModules,
// });
// }
if (!isDev) {
eleventyConfig.addTransform('simulate-webawesome-app', function (content) {
// For a server build, we expect a server to run the second transform.
if (serverBuild) {
return content;
}
// Only run the transform on files nunjucks would transform.
if (!this.page.inputPath.match(/.(md|html|njk)$/)) {
return content;
}
/** This largely mimics what an app would do and just stubs out what we don't care about. */
return SimulateWebAwesomeApp(content);
});
}
}
export const config = {

View File

@@ -13,7 +13,7 @@
<script type="module" src="/assets/scripts/color-scheme.js"></script>
<script type="module" src="/assets/scripts/theme.js"></script>
{% if hasSidebar %}<script type="module" src="/assets/scripts/sidebar.js"></script>{% endif %}
<script defer data-domain="backers.webawesome.com" src="https://plausible.io/js/script.js"></script>
<script defer data-domain="webawesome.com" src="https://plausible.io/js/script.js"></script>
{# Docs styles #}
<link rel="stylesheet" href="/assets/styles/docs.css" />

View File

@@ -288,7 +288,7 @@
<p>
To manually import this component from React, use the following code.
</p>
<pre><code class="language-js">import '@awesome.me/webawesome/dist/react/{{ componentName }}';</code></pre>
<pre><code class="language-js">import {{ component.name }} from '@awesome.me/webawesome/dist/react/{{ componentName }}';</code></pre>
</wa-tab-panel>
</wa-tab-group>

View File

@@ -1,4 +1,5 @@
/* eslint-disable no-invalid-this */
import { readFileSync } from 'fs';
import { mkdir, writeFile } from 'fs/promises';
import lunr from 'lunr';
import { parse } from 'node-html-parser';
@@ -23,19 +24,22 @@ export function searchPlugin(options = {}) {
...options,
};
// Hoist above so that it can "cache" properly for incremental builds.
return function (eleventyConfig) {
const pagesToIndex = new Map();
let pagesToIndex = new Map();
eleventyConfig.addPreprocessor('exclude-unlisted-from-search', '*', function (data, content) {
if (data.unlisted) {
// no-op
pagesToIndex.delete(data.page.inputPath);
} else {
pagesToIndex.set(data.page.inputPath, {});
pagesToIndex.set(data.page.inputPath, true);
}
return content;
});
// With incremental builds we need this to be last in case stuff was added from metadata. _BUT_ in incremental builds, not every page is added to the "transform".
eleventyConfig.addTransform('search', function (content) {
if (!pagesToIndex.has(this.page.inputPath)) {
return content;
@@ -67,11 +71,30 @@ export function searchPlugin(options = {}) {
return content;
});
eleventyConfig.on('eleventy.after', ({ directories }) => {
eleventyConfig.on('eleventy.after', async ({ directories }) => {
const { output } = directories;
const outputFilename = path.resolve(join(output, 'search.json'));
const cachedPages = path.resolve(join(output, 'cached_pages.json'));
function getCachedPages() {
let content = { pages: [] };
try {
content = JSON.parse(readFileSync(cachedPages));
} catch (e) {}
const cachedPagesMap = new Map(content.pages);
for (const [key, value] of cachedPagesMap.entries()) {
// A page uses a cached value if `true` and it didnt get its value set in the "transform" hook. This is to get around the limitation of incremental builds not going over every file in transform.
if (pagesToIndex.get(key) === true) {
pagesToIndex.set(key, value);
}
}
}
const map = [];
const searchIndex = lunr(async function () {
getCachedPages();
const searchIndex = lunr(function () {
let index = 0;
this.ref('id');
@@ -84,9 +107,11 @@ export function searchPlugin(options = {}) {
map[index] = { title: page.title, description: page.description, url: page.url };
index++;
}
await mkdir(dirname(outputFilename), { recursive: true });
await writeFile(outputFilename, JSON.stringify({ searchIndex, map }), 'utf-8');
});
await mkdir(dirname(outputFilename), { recursive: true });
await writeFile(outputFilename, JSON.stringify({ searchIndex, map }), 'utf-8');
await writeFile(cachedPages, JSON.stringify({ pages: [...pagesToIndex.entries()] }, null, 2));
});
};
}

View File

@@ -0,0 +1,81 @@
import { parse } from 'node-html-parser';
import slugify from 'slugify';
import { v4 as uuid } from 'uuid';
function createId(text) {
let slug = slugify(String(text), {
remove: /[^\w|\s]/g,
lower: true,
});
// ids must start with a letter
if (!/^[a-z]/i.test(slug)) {
slug = `wa_${slug}`;
}
return slug;
}
/**
* Eleventy plugin to add anchors to headings to content.
*/
export function anchorHeadingsTransformer(options = {}) {
options = {
container: 'body',
headingSelector: 'h2, h3, h4, h5, h6',
anchorLabel: 'Jump to heading',
...options,
};
/** doc is a parsed HTML document */
return function (doc) {
const container = doc.querySelector(options.container);
if (!container) {
return doc;
}
// Look for headings
let selector = `:is(${options.headingSelector}):not([data-no-anchor], [data-no-anchor] *)`;
container.querySelectorAll(selector).forEach(heading => {
const hasAnchor = heading.querySelector('a');
const existingId = heading.getAttribute('id');
const clone = parse(heading.outerHTML);
// Create a clone of the heading so we can remove [data-no-anchor] elements from the text content
clone.querySelectorAll('[data-no-anchor]').forEach(el => el.remove());
if (hasAnchor) {
return;
}
let id = existingId;
if (!id) {
const slug = createId(clone.textContent ?? '') ?? uuid().slice(-12);
id = slug;
let suffix = 1;
// Make sure the slug is unique in the document
while (doc.getElementById(id) !== null) {
id = `${slug}-${++suffix}`;
}
}
// Create the anchor
const anchor = parse(`
<a href="#${encodeURIComponent(id)}">
<span class="wa-visually-hidden"></span>
<span aria-hidden="true">#</span>
</a>
`);
anchor.querySelector('.wa-visually-hidden').textContent = options.anchorLabel;
// Update the heading
if (!existingId) {
heading.setAttribute('id', id);
}
heading.classList.add('anchor-heading');
heading.appendChild(anchor);
});
};
}

View File

@@ -1,43 +1,51 @@
import { parse } from 'node-html-parser';
import { v4 as uuid } from 'uuid';
import { markdown } from '../_utils/markdown.js';
import { copyCode } from './copy-code.js';
import { highlightCode } from './highlight-code.js';
/**
* Eleventy plugin to turn `<code class="example">` blocks into live examples.
*/
export function codeExamplesPlugin(options = {}) {
export function codeExamplesTransformer(options = {}) {
options = {
container: 'body',
...options,
};
return function (eleventyConfig) {
eleventyConfig.addTransform('code-examples', content => {
const doc = parse(content, { blockTextElements: { code: true } });
const container = doc.querySelector(options.container);
return function (doc) {
const container = doc.querySelector(options.container);
if (!container) {
return content;
}
if (!container) {
return;
}
// Look for external links
container.querySelectorAll('code.example').forEach(code => {
const pre = code.closest('pre');
const hasButtons = !code.classList.contains('no-buttons');
const isOpen = code.classList.contains('open') || !hasButtons;
const noEdit = code.classList.contains('no-edit');
const id = `code-example-${uuid().slice(-12)}`;
let preview = pre.textContent;
// Look for external links
container.querySelectorAll('code.example').forEach(code => {
let pre = code.closest('pre');
const hasButtons = !code.classList.contains('no-buttons');
const isOpen = code.classList.contains('open') || !hasButtons;
const noEdit = code.classList.contains('no-edit');
const id = `code-example-${uuid().slice(-12)}`;
let preview = pre.textContent;
// Run preview scripts as modules to prevent collisions
const root = parse(preview, { blockTextElements: { script: true } });
root.querySelectorAll('script').forEach(script => script.setAttribute('type', 'module'));
preview = root.toString();
const langClass = [...code.classList.values()].find(val => val.startsWith('language-'));
const lang = langClass ? langClass.replace(/^language-/, '') : 'plain';
const codeExample = parse(`
code.innerHTML = highlightCode(code.textContent ?? '', lang);
// Run preview scripts as modules to prevent collisions
const root = parse(preview, { blockTextElements: { script: true } });
root.querySelectorAll('script').forEach(script => script.setAttribute('type', 'module'));
preview = root.toString();
copyCode(code);
const codeExample = parse(`
<div class="code-example ${isOpen ? 'open' : ''}">
<div class="code-example-preview">
${preview}
<div>
${preview}
</div>
<div class="code-example-resizer" aria-hidden="true">
<wa-icon name="grip-lines-vertical"></wa-icon>
</div>
@@ -77,10 +85,7 @@ export function codeExamplesPlugin(options = {}) {
</div>
`);
pre.replaceWith(codeExample);
});
return doc.toString();
pre.replaceWith(codeExample);
});
};
}

View File

@@ -0,0 +1,38 @@
export function copyCode(code) {
const pre = code.closest('pre');
let preId = pre.getAttribute('id') || `code-block-${crypto.randomUUID()}`;
let codeId = code.getAttribute('id') || `${preId}-inner`;
if (!code.getAttribute('id')) {
code.setAttribute('id', codeId);
}
if (!pre.getAttribute('id')) {
pre.setAttribute('id', preId);
}
// Add a copy button
pre.innerHTML += `<wa-copy-button from="${codeId}" class="copy-button wa-dark"></wa-copy-button>`;
}
/**
* Eleventy plugin to add copy buttons to code blocks.
*/
export function copyCodeTransformer(options = {}) {
options = {
container: 'body',
...options,
};
return function (doc) {
const container = doc.querySelector(options.container);
if (!container) {
return;
}
// Look for code blocks
container.querySelectorAll('pre > code').forEach(code => {
copyCode(code);
});
};
}

View File

@@ -24,30 +24,25 @@ function normalize(pathname) {
/**
* Eleventy plugin to decorate current links with a custom class.
*/
export function currentLink(options = {}) {
export function currentLinkTransformer(options = {}) {
options = {
container: 'body',
className: 'current',
...options,
};
return function (eleventyConfig) {
eleventyConfig.addTransform('current-link', function (content) {
const doc = parse(content);
const container = doc.querySelector(options.container);
return function (doc) {
const container = doc.querySelector(options.container);
if (!container) {
return content;
if (!container) {
return;
}
// Compare the href attribute to 11ty's page URL
container.querySelectorAll('a[href]').forEach(a => {
if (normalize(a.getAttribute('href')) === normalize(this.page.url)) {
a.classList.add(options.className);
}
// Compare the href attribute to 11ty's page URL
container.querySelectorAll('a[href]').forEach(a => {
if (normalize(a.getAttribute('href')) === normalize(this.page.url)) {
a.classList.add(options.className);
}
});
return doc.toString();
});
};
}

View File

@@ -37,36 +37,31 @@ export function highlightCode(code, language = 'plain') {
* Eleventy plugin to highlight code blocks with the `language-*` attribute using Prism.js. Unlike most plugins, this
* works on the entire document not just markdown content.
*/
export function highlightCodePlugin(options = {}) {
export function highlightCodeTransformer(options = {}) {
options = {
container: 'body',
...options,
};
return function (eleventyConfig) {
eleventyConfig.addTransform('highlight-code', content => {
const doc = parse(content, { blockTextElements: { code: true } });
const container = doc.querySelector(options.container);
return function (doc) {
const container = doc.querySelector(options.container);
if (!container) {
return content;
}
if (!container) {
return;
}
// Look for <code class="language-*"> and highlight each one
container.querySelectorAll('code[class*="language-"]').forEach(code => {
const langClass = [...code.classList.values()].find(val => val.startsWith('language-'));
const lang = langClass ? langClass.replace(/^language-/, '') : 'plain';
// Look for <code class="language-*"> and highlight each one
container.querySelectorAll('code[class*="language-"]').forEach(code => {
const langClass = [...code.classList.values()].find(val => val.startsWith('language-'));
const lang = langClass ? langClass.replace(/^language-/, '') : 'plain';
try {
code.innerHTML = highlightCode(code.textContent ?? '', lang);
} catch (err) {
if (!options.ignoreMissingLangs) {
throw new Error(err.message);
}
try {
code.innerHTML = highlightCode(code.textContent ?? '', lang);
} catch (err) {
if (!options.ignoreMissingLangs) {
throw new Error(err.message);
}
});
return doc.toString();
}
});
};
}

View File

@@ -0,0 +1,64 @@
import { parse } from 'node-html-parser';
/**
* Eleventy plugin to add an outline (table of contents) to the page. Headings must have an id, otherwise they won't be
* included in the outline. An unordered list containing links will be appended to the target element.
*
* If no headings are found for the outline, the `ifEmpty()` function will be called with a `node-html-parser` object as
* the first argument. This can be used to toggle classes or remove elements when the outline is empty.
*
* See the `node-html-parser` docs for more details: https://www.npmjs.com/package/node-html-parser
*/
export function outlineTransformer(options = {}) {
options = {
container: 'body',
target: '.outline',
selector: 'h2,h3',
ifEmpty: () => null,
...options,
};
return function (doc) {
const container = doc.querySelector(options.container);
const ul = parse('<ul></ul>');
let numLinks = 0;
if (!container) {
return;
}
container.querySelectorAll(options.selector).forEach(heading => {
const id = heading.getAttribute('id');
const level = heading.tagName.slice(1);
const clone = parse(heading.outerHTML);
if (heading.closest('[data-no-outline]')) {
return;
}
// Create a clone of the heading so we can remove links and [data-no-outline] elements from the text content
clone.querySelectorAll('.wa-visually-hidden, [hidden], [aria-hidden="true"]').forEach(el => el.remove());
clone.querySelectorAll('[data-no-outline]').forEach(el => el.remove());
// Generate the link
const li = parse(`<li data-level="${level}"><a></a></li>`);
const a = li.querySelector('a');
a.setAttribute('href', `#${encodeURIComponent(id)}`);
a.textContent = clone.textContent.trim().replace(/#$/, '');
// Add it to the list
ul.firstChild.appendChild(li);
numLinks++;
});
if (numLinks > 0) {
// Append the list to all matching targets
doc.querySelectorAll(options.target).forEach(target => {
target.appendChild(parse(ul.outerHTML));
});
} else {
// Remove if empty
options.ifEmpty(doc);
}
};
}

View File

@@ -1,85 +0,0 @@
import { parse } from 'node-html-parser';
import slugify from 'slugify';
import { v4 as uuid } from 'uuid';
function createId(text) {
let slug = slugify(String(text), {
remove: /[^\w|\s]/g,
lower: true,
});
// ids must start with a letter
if (!/^[a-z]/i.test(slug)) {
slug = `wa_${slug}`;
}
return slug;
}
/**
* Eleventy plugin to add anchors to headings to content.
*/
export function anchorHeadingsPlugin(options = {}) {
options = {
container: 'body',
headingSelector: 'h2, h3, h4, h5, h6',
anchorLabel: 'Jump to heading',
...options,
};
return function (eleventyConfig) {
eleventyConfig.addTransform('anchor-headings', content => {
const doc = parse(content);
const container = doc.querySelector(options.container);
if (!container) {
return content;
}
// Look for headings
let selector = `:is(${options.headingSelector}):not([data-no-anchor], [data-no-anchor] *)`;
container.querySelectorAll(selector).forEach(heading => {
const hasAnchor = heading.querySelector('a');
const existingId = heading.getAttribute('id');
const clone = parse(heading.outerHTML);
// Create a clone of the heading so we can remove [data-no-anchor] elements from the text content
clone.querySelectorAll('[data-no-anchor]').forEach(el => el.remove());
if (hasAnchor) {
return;
}
let id = existingId;
if (!id) {
const slug = createId(clone.textContent ?? '') ?? uuid().slice(-12);
id = slug;
let suffix = 1;
// Make sure the slug is unique in the document
while (doc.getElementById(id) !== null) {
id = `${slug}-${++suffix}`;
}
}
// Create the anchor
const anchor = parse(`
<a href="#${encodeURIComponent(id)}">
<span class="wa-visually-hidden"></span>
<span aria-hidden="true">#</span>
</a>
`);
anchor.querySelector('.wa-visually-hidden').textContent = options.anchorLabel;
// Update the heading
if (!existingId) {
heading.setAttribute('id', id);
}
heading.classList.add('anchor-heading');
heading.appendChild(anchor);
});
return doc.toString();
});
};
}

View File

@@ -1,40 +0,0 @@
import { parse } from 'node-html-parser';
/**
* Eleventy plugin to add copy buttons to code blocks.
*/
export function copyCodePlugin(eleventyConfig, options = {}) {
options = {
container: 'body',
...options,
};
let codeCount = 0;
eleventyConfig.addTransform('copy-code', content => {
const doc = parse(content, { blockTextElements: { code: true } });
const container = doc.querySelector(options.container);
if (!container) {
return content;
}
// Look for code blocks
container.querySelectorAll('pre > code').forEach(code => {
const pre = code.closest('pre');
let preId = pre.getAttribute('id') || `code-block-${++codeCount}`;
let codeId = code.getAttribute('id') || `${preId}-inner`;
if (!code.getAttribute('id')) {
code.setAttribute('id', codeId);
}
if (!pre.getAttribute('id')) {
pre.setAttribute('id', preId);
}
// Add a copy button
pre.innerHTML += `<wa-copy-button from="${codeId}" class="copy-button wa-dark"></wa-copy-button>`;
});
return doc.toString();
});
}

View File

@@ -1,69 +0,0 @@
import { parse } from 'node-html-parser';
/**
* Eleventy plugin to add an outline (table of contents) to the page. Headings must have an id, otherwise they won't be
* included in the outline. An unordered list containing links will be appended to the target element.
*
* If no headings are found for the outline, the `ifEmpty()` function will be called with a `node-html-parser` object as
* the first argument. This can be used to toggle classes or remove elements when the outline is empty.
*
* See the `node-html-parser` docs for more details: https://www.npmjs.com/package/node-html-parser
*/
export function outlinePlugin(options = {}) {
options = {
container: 'body',
target: '.outline',
selector: 'h2,h3',
ifEmpty: () => null,
...options,
};
return function (eleventyConfig) {
eleventyConfig.addTransform('outline', content => {
const doc = parse(content);
const container = doc.querySelector(options.container);
const ul = parse('<ul></ul>');
let numLinks = 0;
if (!container) {
return content;
}
container.querySelectorAll(options.selector).forEach(heading => {
const id = heading.getAttribute('id');
const level = heading.tagName.slice(1);
const clone = parse(heading.outerHTML);
if (heading.closest('[data-no-outline]')) {
return;
}
// Create a clone of the heading so we can remove links and [data-no-outline] elements from the text content
clone.querySelectorAll('.wa-visually-hidden, [hidden], [aria-hidden="true"]').forEach(el => el.remove());
clone.querySelectorAll('[data-no-outline]').forEach(el => el.remove());
// Generate the link
const li = parse(`<li data-level="${level}"><a></a></li>`);
const a = li.querySelector('a');
a.setAttribute('href', `#${encodeURIComponent(id)}`);
a.textContent = clone.textContent.trim().replace(/#$/, '');
// Add it to the list
ul.firstChild.appendChild(li);
numLinks++;
});
if (numLinks > 0) {
// Append the list to all matching targets
doc.querySelectorAll(options.target).forEach(target => {
target.appendChild(parse(ul.outerHTML));
});
} else {
// Remove if empty
options.ifEmpty(doc);
}
return doc.toString();
});
};
}

View File

@@ -0,0 +1,18 @@
import nunjucks from 'nunjucks';
/**
* This function simulates what a server would do running "on top" of eleventy.
*/
export function SimulateWebAwesomeApp(str) {
return nunjucks.renderString(str, {
// Stub the server EJS shortcodes.
currentUser: {
hasPro: false,
},
server: {
head: '',
loginOrAvatar: '',
flashes: '',
},
});
}

View File

@@ -57,9 +57,7 @@ document.addEventListener('click', event => {
const cdnUrl = document.documentElement.dataset.cdnUrl;
const html =
`<script data-fa-kit-code="b10bfbde90" type="module" src="${cdnUrl}webawesome.loader.js"></script>\n` +
`<link rel="stylesheet" href="${cdnUrl}styles/themes/default.css">\n` +
`<link rel="stylesheet" href="${cdnUrl}styles/webawesome.css">\n` +
`<link rel="stylesheet" href="${cdnUrl}styles/utilities.css">\n\n` +
`<link rel="stylesheet" href="${cdnUrl}styles/webawesome.css">\n\n` +
`${code.textContent}`;
const css = 'html > body {\n padding: 2rem !important;\n}';
const js = '';

View File

@@ -3,7 +3,7 @@
border: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet);
border-radius: var(--wa-border-radius-l);
color: var(--wa-color-text-normal);
margin-block-end: var(--wa-flow-spacing);
margin-block-end: var(--wa-content-spacing);
isolation: isolate;
}
@@ -100,8 +100,8 @@
.code-example-buttons {
display: flex;
align-items: stretch;
background: var(--wa-color-surface-default) !important; /* TODO - remove after native styles refactor */
color: var(--wa-color-text-quiet) !important; /* TODO - remove after native styles refactor */
background: var(--wa-color-surface-default);
color: var(--wa-color-text-quiet);
border-bottom-left-radius: inherit;
border-bottom-right-radius: inherit;
@@ -116,14 +116,6 @@
padding: 0.5rem;
cursor: pointer;
@media (hover: hover) {
&:hover {
border-left: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet) !important; /* TODO - remove after native styles refactor */
background: var(--wa-color-surface-default) !important; /* TODO - remove after native styles refactor */
color: var(--wa-color-text-quiet) !important; /* TODO - remove after native styles refactor */
}
}
&:first-of-type {
border-left: none;
border-bottom-left-radius: var(--wa-border-radius-l);

View File

@@ -1,7 +1,8 @@
/* Only code blocks generated by our docs get these styles */
pre[id*='code-block-'] {
background-color: var(--wa-color-gray-20);
color-scheme: dark;
color: white;
background-color: var(--wa-color-neutral-20);
/* Ensures a discernible background color in dark mode
* Useful for themes that use gray-20 as --wa-color-surface-default */

View File

@@ -26,10 +26,6 @@ body.theme-transitioning {
transition: opacity 200ms ease-out;
}
wa-page {
--wa-flow-spacing: var(--wa-space-xl);
}
/* Header */
wa-page::part(header) {
background-color: var(--wa-color-surface-default);
@@ -148,8 +144,8 @@ wa-page > header {
border-inline-start: var(--wa-border-width-s) solid var(--wa-color-surface-border);
font-size: var(--wa-font-size-s);
line-height: var(--wa-line-height-condensed);
margin: 0;
padding-inline-start: var(--wa-space-m);
margin: 0;
}
ul ul {
@@ -162,6 +158,7 @@ wa-page > header {
li {
list-style: none;
margin: 0;
+ li {
margin-block-start: var(--wa-space-m);
@@ -314,7 +311,7 @@ h1.title {
gap: var(--wa-space-xs);
flex-wrap: wrap;
align-items: center;
margin-block-end: var(--wa-flow-spacing);
margin-block-end: var(--wa-content-spacing);
code {
line-height: var(--wa-line-height-condensed);
@@ -357,7 +354,7 @@ h1.title {
border: var(--wa-border-style) var(--wa-border-width-s);
border-radius: var(--wa-border-radius-l);
padding: 1rem;
margin-block-end: var(--wa-flow-spacing);
margin-block-end: var(--wa-content-spacing);
:first-child {
margin-block-start: 0;
@@ -624,12 +621,6 @@ table.colors {
min-inline-size: 8rem;
}
/* Utilities */
.two-columns {
columns: 2;
gap: 1rem;
}
/* Component API tables */
wa-scroller:has(.component-table) {
margin-block-end: var(--wa-space-xl);

View File

@@ -55,7 +55,7 @@ it is rarely a good idea to mix sizes within the same button group.
Set the `orientation` attribute to `vertical` to make a vertical button group.
```html {.example}
<wa-button-group orientation="vertical" label="Options" style="max-width: 120px;">
<wa-button-group orientation="vertical" label="Options">
<wa-button>
<wa-icon slot="start" name="plus"></wa-icon>
New

View File

@@ -251,4 +251,4 @@ This example demonstrates how to style buttons using a custom class. This is the
outline-offset: 4px;
}
</style>
```
```

View File

@@ -56,10 +56,12 @@ To copy data from an attribute, use `from="id[attr]"` where `id` is the id of th
<br /><br />
<!-- Copies the input's "value" property -->
<wa-input id="my-input" type="text" value="User input" style="display: inline-block; max-width: 300px;"></wa-input>
<wa-copy-button from="my-input.value"></wa-copy-button>
<span class="wa-align-items-center wa-gap-2xs">
<wa-input id="my-input" type="text" value="User input" style="display: inline-block; max-width: 300px;"></wa-input>
<wa-copy-button from="my-input.value"></wa-copy-button>
</span>
<br /><br />
<br />
<!-- Copies the link's "href" attribute -->
<a id="my-link" href="https://shoelace.style/">Web Awesome Website</a>
@@ -88,6 +90,7 @@ Copy buttons can be disabled by adding the `disabled` attribute.
A success indicator is briefly shown after copying. You can customize the length of time the indicator is shown using the `feedback-duration` attribute.
```html {.example}
<wa-copy-button value="Web Awesome rocks!" feedback-duration="250"></wa-copy-button>
```
@@ -132,4 +135,4 @@ You can customize the button to your liking with CSS.
outline-offset: 4px;
}
</style>
```
```

View File

@@ -32,7 +32,7 @@ Dropdowns are designed to work well with [dropdown items](/docs/components/dropd
<wa-dropdown-item slot="submenu" value="show-thumbnails">Show Thumbnails</wa-dropdown-item>
</wa-dropdown-item>
<wa-divider></wa-divider>
<wa-dropdown-item type="checkbox" checked>Emoji Shortcuts<wa-dropdown-item>
<wa-dropdown-item type="checkbox" checked>Emoji Shortcuts</wa-dropdown-item>
<wa-dropdown-item type="checkbox" checked>Word Wrap</wa-dropdown-item>
<wa-divider></wa-divider>
<wa-dropdown-item variant="danger">

View File

@@ -21,10 +21,10 @@ Use the `label` attribute to label the progress bar and tell assistive devices h
### Custom Height
Use the `height` CSS property to set the progress bar's height.
Use the `--track-height` custom property to set the progress bar's height.
```html {.example}
<wa-progress-bar value="50" style="height: 6px;"></wa-progress-bar>
<wa-progress-bar value="50" style="--track-height: 6px;"></wa-progress-bar>
```
### Showing Values

View File

@@ -7,7 +7,7 @@ category: Form Controls
```html {.example}
<wa-select>
<wa-option value="option-1">Option 1</wa-option>
<wa-option value="">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
<wa-option value="option-4">Option 4</wa-option>
@@ -173,7 +173,7 @@ Use `<wa-divider>` to group listbox items visually. You can also use `<small>` t
### Sizes
Use the `size` attribute to change a select's size. Note that size does not apply to listbox options.
Use the `size` attribute to change a select's size.
```html {.example}
<wa-select placeholder="Small" size="small">
@@ -366,6 +366,7 @@ Here's a comprehensive example showing different lazy loading scenarios:
const option = document.createElement('wa-option');
option.setAttribute('value', 'foo');
option.selected = true
option.innerText = 'Foo';
// For the multiple select with existing selected options, make the new option selected
@@ -402,4 +403,4 @@ Here's a comprehensive example showing different lazy loading scenarios:
:::info
The key principle is that the select component prioritizes user interactions and explicit selections over programmatic changes, ensuring a predictable user experience even with dynamically loaded content.
:::
:::

View File

@@ -7,8 +7,8 @@ category: Form Controls
```html {.example}
<wa-slider
label="Number of cats"
hint="Limit six per household"
label="Number of users"
hint="Limit six per team"
name="value"
value="3"
min="0"
@@ -40,7 +40,7 @@ Use the `label` attribute to give the slider an accessible label. For labels tha
Add descriptive hint to a slider with the `hint` attribute. For hints that contain HTML, use the `hint` slot instead.
```html {.example}
<wa-slider label="Volume" hint="Controls the volume of the current song." min="0" max="100"></wa-slider>
<wa-slider label="Volume" hint="Controls the volume of the current song." min="0" max="100" value="50"></wa-slider>
```
### Showing tooltips
@@ -72,7 +72,15 @@ Use the `with-markers` attribute to display visual indicators at each step incre
Use the `reference` slot to add contextual labels below the slider. References are automatically spaced using `space-between`, making them easy to align with the start, center, and end positions.
```html {.example}
<wa-slider label="Speed" name="speed" min="1" max="5" value="3" with-markers>
<wa-slider
label="Speed"
name="speed"
min="1"
max="5"
value="3"
with-markers
hint="Controls the speed of the thing you're currently doing."
>
<span slot="reference">Slow</span>
<span slot="reference">Medium</span>
<span slot="reference">Fast</span>
@@ -249,8 +257,8 @@ By default, the filled indicator extends from the minimum value to the current p
```html {.example}
<wa-slider
label="Cat playfulness"
hint="Energy level during playtime"
label="User Friendliness"
hint="Did you find our product easy to use?"
name="value"
value="0"
min="-5"
@@ -259,8 +267,9 @@ By default, the filled indicator extends from the minimum value to the current p
with-markers
with-tooltip
>
<span slot="reference">Lazy</span>
<span slot="reference">Zoomies</span>
<span slot="reference">Easy</span>
<span slot="reference">Moderate</span>
<span slot="reference">Difficult</span>
</wa-slider>
```

View File

@@ -6,7 +6,7 @@ category: Imagery
---
```html {.example}
<wa-zoomable-frame src="https://backers.webawesome.com/" zoom="0.5"> </wa-zoomable-frame>
<wa-zoomable-frame src="https://webawesome.com/" zoom="0.5"> </wa-zoomable-frame>
```
## Examples
@@ -43,7 +43,7 @@ Set the `zoom` attribute to control the frame's zoom level. Use `1` for 100%, `2
Define specific zoom increments with the `zoom-levels` attribute using space-separated percentages and decimal values like `zoom-levels="0.25 0.5 75% 100%"`.
```html {.example}
<wa-zoomable-frame src="https://backers.webawesome.com/" zoom="0.5" zoom-levels="50% 0.75 100%"> </wa-zoomable-frame>
<wa-zoomable-frame src="https://webawesome.com/" zoom="0.5" zoom-levels="50% 0.75 100%"> </wa-zoomable-frame>
```
### Hiding zoom controls
@@ -51,7 +51,7 @@ Define specific zoom increments with the `zoom-levels` attribute using space-sep
Add the `without-controls` attribute to hide the zoom control interface from the frame.
```html {.example}
<wa-zoomable-frame src="https://backers.webawesome.com/" without-controls zoom="0.5"> </wa-zoomable-frame>
<wa-zoomable-frame src="https://webawesome.com/" without-controls zoom="0.5"> </wa-zoomable-frame>
```
### Preventing user interaction
@@ -59,5 +59,5 @@ Add the `without-controls` attribute to hide the zoom control interface from the
Apply the `without-interaction` attribute to make the frame non-interactive. Note that this prevents keyboard navigation into the frame, which may impact accessibility for some users.
```html {.example}
<wa-zoomable-frame src="https://backers.webawesome.com/" zoom="0.5" without-interaction> </wa-zoomable-frame>
<wa-zoomable-frame src="https://webawesome.com/" zoom="0.5" without-interaction> </wa-zoomable-frame>
```

View File

@@ -12,12 +12,37 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh
### New Features {data-no-outline}
- Added `--track-height` custom property to `<wa-progress-bar>` [pr:1154]
- Added `--pulse-color` custom property to `<wa-badge>` [pr:1173]
### Bug Fixes and Improvements {data-no-outline}
- Fixed a bug in `<wa-badge>` where `appearance="pulse"` was not working as expected [pr:1173]
- Fixed a missing TypeScript type for `<wa-badge>` for its `attention` property missing `bounce` value. [pr:1173]
- Fixed the missing `nanoid` dependency in `package.json` [discuss:1139]
- Fixed a bug in `<wa-slider>` that prevented the hint from showing up [discuss:1172]
- Fixed a bug in `<wa-textarea>` where setting `resize="auto"` caused the height of the textarea to double [issue:1155]
- Fixed a bug in `<wa-card>` that caused slotted media to have incorrectly rounded corners [issue:1107]
- Fixed a bug in `<wa-button-group>` that prevented pill buttons from rendering corners properly [issue:1165]
- Fixed a bug in `<wa-button-group>` that caused some vertical groups to appear horizontal [issue:1152]
## 3.0.0-beta.2
### New Features {data-no-outline}
- Added `.wa-hover-rows` to native styles to opt-in to highlighting table rows on hover.
### Bug Fixes and Improvements {data-no-outline}
- Fixed a bug in `<wa-dropdown>` that prevented the menu from flipping/shifting to keep the menu in the viewport [discuss:1106]
- Fixed the themes page so it shows the correct palette and imports
- Fixed a bug in `<wa-select>` with options that had blank string values. [pr:1136]
- Added `.wa-hover-rows` to native styles to opt-in to highlighting table rows on hover [pr:1111]
- Added missing changelog entries for beta.1 [pr:1117]
- Fixed a bug in `<wa-dropdown>` that prevented the menu from flipping/shifting to keep the menu in the viewport [pr:1122]
- Fixed the themes page so it shows the correct palette and imports [pr:1125]
- Fixed `filled` and `outlined` appearance styles in various components [issue:1102]
- Fixed active state styles in the Awesome theme [pr:1129]
- Fixed native text styles when applied to certain backgrounds [pr:https://github.com/shoelace-style/webawesome/pull/1130]
- Improved the organization of essential and optional styles [pr:1113]
## 3.0.0-beta.1
@@ -39,10 +64,8 @@ Many of these changes and improvements were the direct result of feedback from u
- Renamed the `classic` theme to `shoelace`
- Removed `:root` selector from all theme, color palette, and semantic color stylesheets except for the default theme and colors. All of these styles are now solely scoped to classes, such as `.wa-theme-awesome`, `.wa-palette-bright`, and `.wa-brand-orange`.
- Removed most custom properties from components that can otherwise be styled with `::part()` selectors and standard CSS properties.
<<<<<<< HEAD
- `<wa-dropdown>` was reworked and simplified to not use menu, menu item, menu label; use `<wa-dropdown-item>` instead
- Renamed `pulse` attribute in `<wa-badge>` to `attention="pulse"` and added `attention="bounce"` [issue:940]
> > > > > > > next
- Renamed the `vertical` attribute to `orientation="vertical"` in `<wa-split-panel>` and `<wa-divider>` to align with other components and the platform [issue:674]
- Renamed certain boolean attributes to be consistent using the `with-*` and `without-*` pattern:
- `<wa-button caret>` => `<wa-button with-caret>`
@@ -87,6 +110,7 @@ Many of these changes and improvements were the direct result of feedback from u
- Added a new free component: `<wa-zoomable-frame>` (#3 of 14 per stretch goals)
- Added a `min-block-size` to `<wa-divider orientation="vertical">` to ensure the divider is visible regardless of container height
- Added support for `name` in `<wa-details>` for exclusively opening one in a group
- Added `--wa-content-spacing` to themes to set default spacing between HTML elements in Native Styles
- Added `--checked-icon-scale` to `<wa-checkbox>`
- Added `--tag-max-size` to `<wa-select>` when using `multiple`
- Added support for `data-dialog="open <id>"` to `<wa-dialog>`

View File

@@ -17,7 +17,7 @@ The [discussion forum](https://github.com/shoelace-style/shoelace/discussions) i
- Show the community what you're working on
- Learn more about the project, its values, and its roadmap
<wa-button variant="brand" href="https://github.com/shoelace-style/shoelace/discussions" target="_blank" style="margin-block-end: var(--wa-flow-spacing);">
<wa-button variant="brand" href="https://github.com/shoelace-style/shoelace/discussions" target="_blank" style="margin-block-end: var(--wa-content-spacing);">
<wa-icon name="github" family="brands" slot="start"></wa-icon>
Join the Discussion
</wa-button>
@@ -31,7 +31,7 @@ The [community chat](https://discord.gg/mg8f26C) is open to the public and power
- Show the community what you're working on
- Chat live with other designers, developers, and Web Awesome fans
<wa-button variant="brand" href="https://discord.gg/mg8f26C" target="_blank" style="margin-block-end: var(--wa-flow-spacing);">
<wa-button variant="brand" href="https://discord.gg/mg8f26C" target="_blank" style="margin-block-end: var(--wa-content-spacing);">
<wa-icon name="discord" family="brands" slot="start"></wa-icon>
Join the Chat
</wa-button>
@@ -42,7 +42,7 @@ Follow [@webawesomer](https://twitter.com/webawesomer) on Twitter for general up
**Please avoid using Twitter for support questions.** The [discussion forum](https://github.com/shoelace-style/shoelace/discussions) is a much better place to share code snippets, screenshots, and other troubleshooting info. You'll have much better luck there, as more users will have a chance to help you.
<wa-button variant="brand" href="https://twitter.com/webawesomer" target="_blank" style="margin-block-end: var(--wa-flow-spacing);">
<wa-button variant="brand" href="https://twitter.com/webawesomer" target="_blank" style="margin-block-end: var(--wa-content-spacing);">
<wa-icon name="twitter" family="brands" slot="start"></wa-icon>
Follow on Twitter
</wa-button>

View File

@@ -55,9 +55,9 @@ Web Awesome's color system is made up of CSS custom properties to help with cons
Color is organized by three main categories:
- [Color scales](/#color-scales) that gives you a full spectrum of hues to work with
- [Foundational colors](/#foundational-colors) that lay the groundwork for your theme
- [Semantic colors](/#semantic-colors) that draw attention and convey meaning
- [Color scales](#color-scales) that gives you a full spectrum of hues to work with
- [Foundational colors](#foundational-colors) that lay the groundwork for your theme
- [Semantic colors](#semantic-colors) that draw attention and convey meaning
## Color Scales

View File

@@ -5,27 +5,52 @@ layout: page-outline
tags: styleUtilities
---
Web Awesome provides optional Native Styles that make native HTML elements look good so you can continue using what you know and gradually adopt Web Awesome as you see fit.
Native styles use design tokens to spruce up native HTML elements so that they match the look and feel of your theme. While these native styles are completely optional, they're a great starting point for a cohesive design and a huge help when using a combination of native elements and Web Awesome components in your project.
## Installation
## Using native styles
To use all Web Awesome page styles (including [utilities](/docs/utilities/)), include the following stylesheet in your project:
To use all Web Awesome styles (including [utilities](/docs/utilities/)), include the following stylesheet in your project:
```html
<link rel="stylesheet" href="{% cdnUrl 'styles/webawesome.css' %}" />
```
Or, to _only_ include styles for native elements:
Or, if you only want styles for native elements, include the default theme and native styles individually:
```html
<link rel="stylesheet" href="{% cdnUrl 'styles/themes/default.css' %}" />
<link rel="stylesheet" href="{% cdnUrl 'styles/native.css' %}" />
```
## Elements
You can additionally include any pre-made [theme](/docs/themes/) or [color palette](/docs/color-palettes/) to change the look of native elements.
## Content flow
Native styles set default space between many block-level HTML elements using the `--wa-content-spacing` token from your theme. This helps ensure that your content is readable.
```html {.example}
<h3>Curabitur odio ligula</h3>
<p>Fusce mollis quam lorem, et gravida arcu laoreet ut. Pellentesque et malesuada mi. Morbi faucibus nisl nec nulla porta, ac scelerisque elit finibus.</p>
<blockquote>The Road goes ever on and on<br />
Out from the door where it began.</blockquote>
<p>Donec varius, ipsum sit amet lobortis tristique, quam arcu pellentesque turpis, non porta lacus arcu non arcu. Morbi luctus at nisl sit amet faucibus.</p>
<hr />
<ul>
<li>Aenean imperdiet</li>
<li>Vivamus consectetur at est</li>
<li>Quisque vel leo in leo semper</li>
</ul>
```
To remove this default spacing, you can set `--wa-content-spacing: 0` in your styles.
## Typography
Native styles use [typography design tokens](/docs/tokens/typography/) to style text elements. A number of styles — such as `color`, `font-family`, `font-size`, `font-weight`, and `line-height` — are set on the `<body>` element to be inherited by child elements.
### Headings
Semantic heading elements with proper hierarchy and styling.
Create headings with `<h1>` through `<h6>`. Headings use tokens with the `-heading` suffix, condensed line height, and `text-wrap: balance` for a prominent yet compact appearance.
```html {.example}
<h1>Heading 1</h1>
@@ -38,7 +63,7 @@ Semantic heading elements with proper hierarchy and styling.
### Paragraphs
Standard paragraph text with optimal spacing and readability.
Create paragraphs with `<p>`. Paragraphs inherit the default text styles set on the `<body>` element and use `text-wrap: pretty` to prevent orphaned lines in supported browsers.
```html {.example}
<p>
@@ -55,7 +80,7 @@ Standard paragraph text with optimal spacing and readability.
### Blockquotes
Styled quotations that stand out from regular text.
Emphasize longer quotations with `<blockquote>`. Block quotes use your theme's serif font family and a leading border to stand out.
```html {.example}
<blockquote>
@@ -67,51 +92,51 @@ Styled quotations that stand out from regular text.
### Lists
Organized content in bulleted or numbered format with proper nesting support.
Create ordered and unordered lists with `<ol>` and `<ul>`, plus `<li>` for list items within.
```html {.example}
<ul>
<li>List item 1</li>
<li>
List item 2
<ul>
<li>Subitem a</li>
<li>Subitem b</li>
</ul>
</li>
<li>List item 3</li>
</ul>
<div class="wa-grid">
<ol>
<li>First item</li>
<li>
Another item
<ol>
<li>Nested item</li>
<li>Another nested item</li>
</ol>
</li>
<li>Final item</li>
</ol>
<ol>
<li>List item 1</li>
<li>
List item 2
<ol>
<li>Subitem a</li>
<li>Subitem b</li>
</ol>
</li>
<li>List item 3</li>
</ol>
<ul>
<li>First item</li>
<li>
Another item
<ul>
<li>Nested item</li>
<li>Another nested item</li>
</ul>
</li>
<li>Final item</li>
</ul>
</div>
```
### Description Lists
Term and definition pairs for glossaries and descriptions.
Use `<dl>` to create lists of terms (`<dt>`) and definitions (`<dd>`).
```html {.example}
<dl>
<dt>Definition 1</dt>
<dt>First term</dt>
<dd>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</dd>
<dt>Definition 2</dt>
<dt>Second term</dt>
<dd>
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</dd>
<dt>Definition 3</dt>
<dt>Final term</dt>
<dd>
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam,
eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
@@ -119,13 +144,153 @@ Term and definition pairs for glossaries and descriptions.
</dl>
```
### Code blocks
Create code blocks or other preformatted text with `<pre>`. Preformatted text uses your theme's monospace font family and a subtle background color.
```html {.example}
<pre>
// do a thing
export function thing() {
return true;
}
</pre>
```
### Inline text
Use any inline text element like `<strong>`, `<em>`, `<a>`, `<kbd>`, and others to stylize or emphasize text.
```html {.example}
<div class="wa-grid">
<div class="wa-stack wa-align-items-start">
<strong>Bold</strong>
<em>Italic</em>
<u>Underline</u>
<s>Strike-through</s>
<del>Deleted</del>
<ins>Inserted</ins>
<small>Small</small>
</div>
<div class="wa-stack wa-align-items-start">
<span>Subscript <sub>Sub</sub></span>
<span>Superscript <sup>Sup</sup></span>
<abbr title="Abbreviation">Abbr.</abbr>
<mark>Highlighted</mark>
<a href="#">Link text</a>
<code>Inline code</code>
<kbd>Keyboard</kbd>
</div>
</div>
```
## Widgets & media
### Media
Add responsive media with `<img>`, `<svg>`, `<video>`, `<iframe>`, and others. Media takes up 100% width by default and scales according to its container's width.
```html {.example}
<img
src="https://images.unsplash.com/photo-1620196244888-d31ff5bbf163?q=80&w=1000&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="A gray kitten lays next to a toy"
/>
```
### Tables
Structure tabular data with `<table>` and related elements like `<caption>`, `<thead>`, `<tbody>`, `<th>`, `<tr>`, and `<td>`.
```html {.example}
<table>
<caption>
This <code>&lt;caption&gt;</code> describes the table
</caption>
<thead>
<tr>
<th>First column</th>
<th>Second column</th>
<th>Third column</th>
<th>Final column</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<td>Data</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<td>Data</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<td>Data</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
</tbody>
</table>
```
Add the `wa-hover-rows` class to highlight table rows on hover and the `wa-zebra-rows` class to add alternating row colors to your table.
```html {.example}
<table class="wa-zebra-rows wa-hover-rows">
<thead>
<tr>
<th>First column</th>
<th>Second column</th>
<th>Third column</th>
<th>Final column</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<td>Data</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<td>Data</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
<tr>
<td>Data</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
</tbody>
</table>
```
### Details
Collapsible content sections with expand/collapse functionality.
Create disclosure widgets with `<details>` and `<summary>`. Details closely match the appearance of [`<wa-details>`](/docs/components/details/).
```html {.example}
<details>
<summary>Tincidunt nunc pulvinar</summary>
<summary>Summary</summary>
<p>
Ut lectus arcu bibendum at varius. Convallis a cras semper auctor neque vitae. Odio pellentesque diam volutpat
commodo sed egestas. Amet dictum sit amet justo donec enim diam vulputate ut.
@@ -135,7 +300,7 @@ Collapsible content sections with expand/collapse functionality.
### Dialog
Modal dialog windows for alerts, confirmations, and overlays.
Create modal and non-modal dialog boxes with `<dialog>`. Dialogs closely match the appearance of [`<wa-dialog>`](/docs/components/dialog/).
```html {.example}
<dialog id="dialog-example">
@@ -155,55 +320,9 @@ Modal dialog windows for alerts, confirmations, and overlays.
</script>
```
### Inline Text
### Progress
Various text formatting elements for emphasis and semantic meaning.
```html {.example}
<div class="two-columns">
<p><strong>Bold</strong></p>
<p><em>Italic</em></p>
<p><u>Underline</u></p>
<p><s>Strike-through</s></p>
<p><del>Deleted</del></p>
<p><ins>Inserted</ins></p>
<p><small>Small</small></p>
<p>
<span>Subscript <sub>Sub</sub></span>
</p>
<p>
<span>Superscript <sup>Sup</sup></span>
</p>
<p><abbr title="Abbreviation">Abbr.</abbr></p>
<p><mark>Highlighted</mark></p>
<p><a href="#">Link text</a></p>
<p><code>Inline code</code></p>
<p><kbd>Keyboard</kbd></p>
</div>
```
### Code Blocks
Formatted code snippets with proper syntax styling.
```html {.example}
<pre>
// do a thing
export function thing() {
return true;
}
</pre>
```
### Images
Responsive images with proper scaling and styling.
![A gray kitten lays next to a toy](https://images.unsplash.com/photo-1620196244888-d31ff5bbf163?q=80&w=1000&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D)
### Progress Bars
Visual indicators for task completion and loading states.
Create progress indicators with `<progress>`. Progress indicators closely match the appearance of [`<wa-progress-bar>`](/docs/components/progress-bar/).
```html {.example}
<progress value="40" max="100"></progress>
@@ -211,151 +330,43 @@ Visual indicators for task completion and loading states.
<progress></progress>
```
### Tables
## Forms
Structured data presentation with clean styling, optional row highlighting on hover, and optional zebra striping.
```html {.example}
<table>
<caption>
I'm just a table
</caption>
<thead>
<tr>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
<th>Column 4</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
</tr>
<tr>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
</tr>
<tr>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
</tr>
<tr>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
</tr>
</tbody>
</table>
```
You can use the `wa-hover-rows` class to highlight table rows on hover and the `wa-zebra-rows` class to add alternating row colors to your table.
```html {.example}
<table class="wa-zebra-rows wa-hover-rows">
<caption>
I'm just a table
</caption>
<thead>
<tr>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
<th>Column 4</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
</tr>
<tr>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
</tr>
<tr>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
</tr>
<tr>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
<td>Cell</td>
</tr>
</tbody>
</table>
```
## Form Controls
Native styles use [form control design tokens](/docs/tokens/component-groups/#form-controls) to style form elements like buttons and inputs. Form elements additionally inherit `font-family` from the `<body>` element.
### Buttons
Use the [variant utility classes](../utilities/color.md) to set the button's semantic variant.
Create buttons with `<button>` or `<input type="button | submit | reset">`. Buttons closely match the appearance of [`<wa-button>`](/docs/components/button/).
```html {.example}
<button class="wa-neutral"><wa-icon name="home"></wa-icon> Neutral</button>
<button>Button</button>
<input type="button" value="Input (button)" />
<input type="submit" value="Input (submit)" />
<input type="reset" value="Input (reset)" />
```
Add the `wa-brand`, `wa-neutral`, `wa-success`, `wa-warning`, or `wa-danger` class to specify the button's [color variant](/docs/utilities/color/).
```html {.example}
<button class="wa-neutral">Neutral</button>
<button class="wa-brand">Brand</button>
<button class="wa-success">Success</button>
<button class="wa-warning">Warning</button>
<button class="wa-danger">Danger</button>
```
Use the [appearance utility classes](/docs/utilities/appearance) to change the button's visual appearance:
Add the `wa-accent`, `wa-filled`, `wa-outlined`, or `wa-plain` class to specify the button's visual appearance.
```html {.example}
<div style="margin-block-end: 1rem;">
<button class="wa-accent wa-neutral">Accent</button>
<button class="wa-filled wa-outlined wa-neutral">Filled + Outlined</button>
<button class="wa-filled wa-neutral">Filled</button>
<button class="wa-outlined wa-neutral">Outlined</button>
<button class="wa-plain wa-neutral">Plain</button>
</div>
<div style="margin-block-end: 1rem;">
<button class="wa-accent wa-brand">Accent</button>
<button class="wa-filled wa-outlined wa-brand">Filled + Outlined</button>
<button class="wa-filled wa-brand">Filled</button>
<button class="wa-outlined wa-brand">Outlined</button>
<button class="wa-plain wa-brand">Plain</button>
</div>
<div style="margin-block-end: 1rem;">
<button class="wa-accent wa-success">Accent</button>
<button class="wa-filled wa-outlined wa-success">Filled + Outlined</button>
<button class="wa-filled wa-success">Filled</button>
<button class="wa-outlined wa-success">Outlined</button>
<button class="wa-plain wa-success">Plain</button>
</div>
<div style="margin-block-end: 1rem;">
<button class="wa-accent wa-warning">Accent</button>
<button class="wa-filled wa-outlined wa-warning">Filled + Outlined</button>
<button class="wa-filled wa-warning">Filled</button>
<button class="wa-outlined wa-warning">Outlined</button>
<button class="wa-plain wa-warning">Plain</button>
</div>
<div>
<button class="wa-accent wa-danger">Accent</button>
<button class="wa-filled wa-outlined wa-danger">Filled + Outlined</button>
<button class="wa-filled wa-danger">Filled</button>
<button class="wa-outlined wa-danger">Outlined</button>
<button class="wa-plain wa-danger">Plain</button>
</div>
<button class="wa-accent wa-neutral">Accent</button>
<button class="wa-filled wa-outlined wa-neutral">Filled + Outlined</button>
<button class="wa-filled wa-neutral">Filled</button>
<button class="wa-outlined wa-neutral">Outlined</button>
<button class="wa-plain wa-neutral">Plain</button>
```
Use the [size utility classes](../utilities/size.md) to change a button's size.
Add the `wa-size-s`, `wa-size-m`, or `wa-size-l` class to specify the size of the button.
```html {.example}
<button class="wa-size-s">Small</button>
@@ -363,131 +374,113 @@ Use the [size utility classes](../utilities/size.md) to change a button's size.
<button class="wa-size-l">Large</button>
```
Use the `wa-pill` class to give buttons rounded edges.
Add the `wa-pill` class to give buttons rounded edges.
```html {.example}
<button class="wa-size-s wa-pill">Small</button>
<button class="wa-size-m wa-pill">Medium</button>
<button class="wa-size-l wa-pill">Large</button>
<button class="wa-pill">Pill button</button>
```
### Checkboxes
### Form controls
Multi-select form controls with checked, indeterminate, and disabled states.
Create a variety of form controls with `<input type="">`, `<select>`, and `<textarea>`. Each control closely matches the appearance of the corresponding Web Awesome component.
```html {.example}
<label><input type="checkbox" checked /> Checked</label><br />
<label><input type="checkbox" class="indeterminate" /> Indeterminate</label><br />
<label><input type="checkbox" disabled /> Disabled</label>
<div class="wa-stack">
<label>Text <input type="text" placeholder="add some text" /></label>
<label>Date <input type="date" /></label>
<label>Time <input type="time" /></label>
<label>Number <input type="number" placeholder="12345" /></label>
<label>Color <input type="color" value="#f36944" /></label>
<label>Range <input type="range" /></label>
<label>Select
<select>
<option value="option-1">Option 1</option>
<option value="option-2">Option 2</option>
<option value="option-3">Option 3</option>
</select>
</label>
<label>Textarea <textarea placeholder="add more text"></textarea></label>
<div class="wa-cluster">
<label><input type="checkbox" checked /> Checked</label>
<label><input type="checkbox" class="indeterminate" /> Indeterminate</label>
<label><input type="checkbox" /> Unchecked</label>
</div>
<div class="wa-cluster">
<label><input type="radio" name="radio-group" value="1" checked /> First radio</label>
<label><input type="radio" name="radio-group" value="2" /> Second radio</label>
<label><input type="radio" name="radio-group" value="3" /> Third radio</label>
</div>
</div>
<script>
document.querySelector('.indeterminate').indeterminate = true;
</script>
```
### Radios
Single-select form controls for mutually exclusive choices.
You can wrap native radios in a flex container to give them a horizontal or vertical orientation with even spacing. The convenience [`wa-cluster`](/docs/utilities/cluster) and [`wa-stack`](/docs/utilities/stack) utilities make this easy.
Add the `wa-filled` class to an input to give it a filled background.
```html {.example}
<div class="wa-cluster">
<label><input type="radio" name="b" value="1" checked /> Option 1</label>
<label><input type="radio" name="b" value="2" /> Option 2</label>
<label><input type="radio" name="b" value="3" /> Option 3</label>
</div>
<div class="wa-stack" style="margin-block-start: var(--wa-space-2xl);">
<label><input type="radio" name="g" value="1" checked /> Option 1</label>
<label><input type="radio" name="g" value="2" /> Option 2</label>
<label><input type="radio" name="g" value="3" /> Option 3</label>
</div>
```
### Selects
Dropdown menus for choosing from a list of options.
```html {.example}
<label
>Select
<select id="select">
<option value="option-1">Option 1</option>
<option value="option-2">Option 2</option>
<option value="option-3">Option 3</option>
<div class="wa-stack">
<input type="text" placeholder="Filled input" class="wa-filled" />
<select class="wa-filled">
<option value="filled">Filled select</option>
</select>
</label>
<textarea placeholder="Filled textarea" class="wa-filled"></textarea>
</div>
```
### Sliders
Range inputs for selecting numeric values within a specified range.
Add the `wa-pill` class to an input or select to give it rounded edges.
```html {.example}
<label>Select a value: <input type="range" /></label>
```
### Text Fields
Various input types for collecting user text and data.
```html {.example}
<label>Text <input type="text" placeholder="placeholder" /></label>
<label>Number <input type="number" /></label>
<label>Password <input type="password" required /></label>
<label>Email <input type="email" /></label>
<label>Search <input type="search" /></label>
<label>Telephone <input type="tel" /></label>
<label>URL <input type="url" /></label>
```
Add the `wa-pill` class to an `<input>` to make it pill-shaped.
```html {.example}
<label>Input <input type="text" placeholder="placeholder" class="wa-pill" /></label>
```
### Color Pickers
Visual color selection interface with hex value input.
```html {.example}
<label>Input (color) <input type="color" value="#ff0066" /></label>
```
### Date & Time Pickers
Specialized inputs for selecting dates, times, and datetime values.
```html {.example}
<label>Input (datetime-local) <input type="datetime-local" /></label>
<label>Input (date) <input type="date" /></label>
<label>Input (time) <input type="time" /></label>
```
### Textareas
Multi-line text input fields for longer content.
```html {.example}
<label>Textarea <textarea placeholder="Type something"></textarea></label>
<div class="wa-stack">
<input type="text" placeholder="Pill input" class="wa-pill" />
<select class="wa-pill">
<option value="pill">Pill select</option>
</select>
</div>
```
### Fieldsets
Group form controls together with `<fieldset>` and `<legend>`.
```html {.example}
<fieldset>
<fieldset class="wa-stack wa-align-items-start">
<legend>Legend</legend>
Nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Tincidunt id aliquet risus feugiat in ante. Ac turpis egestas
integer eget aliquet nibh praesent tristique magna.
<label><input type="radio" name="legends" value="1" checked /> King Arthur</label>
<label><input type="radio" name="legends" value="2" /> Robin Hood</label>
<label><input type="radio" name="legends" value="3" /> Odysseus</label>
</fieldset>
```
### Form layouts
Wrap form controls in a flex container to arrange them horizontally or vertically with even spacing. Layout utility classes like [`wa-cluster`](/docs/utilities/cluster) and [`wa-stack`](/docs/utilities/stack) can be added directly to a `<fieldset>` or `<form>` to make this especially easy.
```html {.example}
<fieldset class="wa-cluster">
<legend>Ducks in a row</legend>
<label><input type="checkbox" checked /> Mallard</label>
<label><input type="checkbox" /> Common Loon</label>
<label><input type="checkbox" /> Least Grebe</label>
</fieldset>
<br />
<form class="wa-stack">
<label>Number of pancakes <input type="number" value="5" /></label>
<label>Syrup flavor
<select>
<option value="maple">Maple</option>
<option value="strawberry">Strawberry</option>
<option value="blueberry">Blueberry</option>
<option value="pecan">Butter pecan</option>
</select>
</label>
<label><input type="checkbox" checked /> Add whipped butter</label>
<button>
<wa-icon name="pancakes"></wa-icon>
Stack 'em up
</button>
</form>
```

View File

@@ -616,8 +616,8 @@ layout: false
}
}
wa-progress-bar::part(base) {
height: 0.5em;
wa-progress-bar {
--track-height: 0.5em;
}
</style>
</body>

View File

@@ -4,7 +4,7 @@
"access": "public"
},
"description": "A forward-thinking library of web components.",
"version": "3.0.0-beta.1",
"version": "3.0.0-beta.2",
"homepage": "https://webawesome.com/",
"author": "Web Awesome",
"license": "MIT",
@@ -77,10 +77,10 @@
"@shoelace-style/localize": "^3.2.1",
"composed-offset-position": "^0.0.6",
"lit": "^3.2.1",
"nanoid": "^5.1.5",
"qr-creator": "^1.0.0",
"style-observer": "^0.0.7"
},
"devDependencies": {},
"lint-staged": {
"*.{ts,js}": [
"prettier --write"

View File

@@ -4,19 +4,20 @@ import { execSync } from 'child_process';
import { deleteAsync } from 'del';
import esbuild from 'esbuild';
import { replace } from 'esbuild-plugin-replace';
import { mkdir, readFile } from 'fs/promises';
import getPort, { portNumbers } from 'get-port';
import { globby } from 'globby';
import { dirname, join, relative } from 'node:path';
import { dirname, extname, join, posix, relative } from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import ora from 'ora';
import copy from 'recursive-copy';
import { getCdnDir, getDistDir, getDocsDir, getRootDir, getSiteDir, runScript } from './utils.js';
import { SimulateWebAwesomeApp } from '../docs/_utils/simulate-webawesome-app.js';
import { generateDocs } from './docs.js';
import { getCdnDir, getDistDir, getDocsDir, getRootDir, getSiteDir } from './utils.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const isDeveloping = process.argv.includes('--develop');
const spinner = ora({ text: 'Web Awesome', color: 'cyan' }).start();
const getPackageData = async () => JSON.parse(await readFile(join(getRootDir(), 'package.json'), 'utf-8'));
const getVersion = async () => JSON.stringify((await getPackageData()).version.toString());
@@ -25,6 +26,10 @@ let buildContexts = {
unbundledContext: {},
};
const debugPerf = process.env.DEBUG_PERFORMANCE === '1';
const isDeveloping = process.argv.includes('--develop');
/**
* @typedef {Object} BuildOptions
* @property {Array<string>} [watchedSrcDirectories]
@@ -44,6 +49,8 @@ export async function build(options = {}) {
options.watchedDocsDirectories = [getDocsDir()];
}
function measureStep() {}
/**
* Runs the full build.
*/
@@ -51,17 +58,24 @@ export async function build(options = {}) {
const start = Date.now();
try {
await cleanup();
await generateManifest();
await generateReactWrappers();
await generateTypes();
await generateStyles();
const steps = [cleanup, generateManifest, generateReactWrappers, generateTypes, generateStyles];
for (const step of steps) {
if (debugPerf) {
const stepStart = Date.now();
await step();
const elapsedTime = (Date.now() - stepStart) / 1000 + 's';
spinner.succeed(`${step.name}: ${elapsedTime}`);
} else {
await step();
}
}
// copy everything to unbundled before we generate bundles.
await copy(getCdnDir(), getDistDir(), { overwrite: true });
await generateBundle();
await generateDocs();
await generateDocs({ spinner });
const time = (Date.now() - start) / 1000 + 's';
spinner.succeed(`The build is complete ${chalk.gray(`(finished in ${time})`)}`);
@@ -186,11 +200,11 @@ export async function build(options = {}) {
join(rootDir, 'src/webawesome.loader.ts'),
join(rootDir, 'src/webawesome.ssr-loader.ts'),
// Individual components
...(await globby(join(rootDir, 'src/components/**/!(*.(style|test)).ts'))),
...(await globby(posix.join(rootDir, 'src/components/**/!(*.(style|test)).ts'))),
// Translations
...(await globby(join(rootDir, 'src/translations/**/*.ts'))),
...(await globby(posix.join(rootDir, 'src/translations/**/*.ts'))),
// React wrappers
...(await globby(join(rootDir, 'src/react/**/*.ts'))),
...(await globby(posix.join(rootDir, 'src/react/**/*.ts'))),
],
outdir: getCdnDir(),
chunkNames: 'chunks/[name].[hash]',
@@ -258,49 +272,6 @@ export async function build(options = {}) {
spinner.succeed();
}
/**
* Generates the documentation site.
*/
async function generateDocs() {
/**
* Used by the webawesome-app to skip doc generation since it will do its own.
*/
if (process.env.SKIP_ELEVENTY === 'true') {
return;
}
spinner.start('Writing the docs');
const args = [];
if (isDeveloping) args.push('--develop');
let output;
try {
// 11ty
output = (await runScript(join(__dirname, 'docs.js'), args, { env: process.env }))
// Cleanup the output
.replace('[11ty]', '')
.replace(' seconds', 's')
.replace(/\(.*?\)/, '')
.toLowerCase()
.trim();
// Copy dist (production only)
if (!isDeveloping) {
await copy(getCdnDir(), join(getSiteDir(), 'dist'));
}
spinner.succeed(`Writing the docs ${chalk.gray(`(${output}`)})`);
} catch (error) {
console.error('\n\n' + chalk.red(error) + '\n');
spinner.fail(chalk.red(`Error while writing the docs.`));
if (!isDeveloping) {
process.exit(1);
}
}
}
// Initial build
await buildAll();
@@ -338,6 +309,46 @@ export async function build(options = {}) {
'/dist/': './dist-cdn/',
},
},
middleware: [
function simulateWebawesomeApp(req, res, next) {
// Accumulator for strings so we can pass them through nunjucks a second time similar to how the webawesome-app
// will be running nunjucks twice.
const finalString = [];
const encoding = 'utf-8';
if (!next) {
return;
}
if (!req.url) {
next();
return;
}
const extension = extname(req.url);
if (extension !== '' && extension !== '.html') {
// Assume its something like .svg / .png / .css etc. that we don't want to transform.
next();
return;
}
const _write = res.write;
res.write = function (chunk, encoding) {
// Buffer chunks into an array so that we do a single transform.
finalString.push(chunk.toString());
};
const _end = res.end;
res.end = function (...args) {
const transformedStr = SimulateWebAwesomeApp(finalString.join(''));
_write.call(res, transformedStr, encoding);
_end.call(res, ...args);
};
next();
},
],
callbacks: {
ready: (_err, instance) => {
// 404 errors
@@ -397,7 +408,6 @@ export async function build(options = {}) {
if (typeof options.onWatchEvent === 'function') {
await options.onWatchEvent(evt, filename);
}
await regenerateBundle();
// Copy stylesheets when CSS files change
if (isCssStylesheet) {
@@ -409,8 +419,12 @@ export async function build(options = {}) {
await generateManifest();
}
// copy everything to unbundled before we generate bundles.
await copy(getCdnDir(), getDistDir(), { overwrite: true });
await regenerateBundle();
// This needs to be outside of "isComponent" check because SSR needs to run on CSS files too.
await generateDocs();
await generateDocs({ spinner });
reload();
} catch (err) {
@@ -438,7 +452,7 @@ export async function build(options = {}) {
if (typeof options.onWatchEvent === 'function') {
await options.onWatchEvent(evt, filename);
}
await generateDocs();
await generateDocs({ spinner });
reload();
};
}

View File

@@ -1,14 +1,257 @@
import Eleventy from '@11ty/eleventy';
import copy from 'recursive-copy';
import chalk from 'chalk';
import { deleteAsync } from 'del';
import { getDocsDir, getEleventyConfigPath, getSiteDir } from './utils.js';
import { join } from 'path';
import { getCdnDir, getDocsDir, getEleventyConfigPath, getSiteDir } from './utils.js';
const elev = new Eleventy(getDocsDir(), getSiteDir(), {
quietMode: true,
configPath: getEleventyConfigPath(),
});
let eleventyBuildResolver;
let eleventyBuildPromise;
// Cleanup
await deleteAsync(getSiteDir());
function queueBuild() {
eleventyBuildPromise = new Promise(resolve => {
eleventyBuildResolver = resolve;
});
}
// Write it
await elev.write();
// 11ty
export async function createEleventy(options = {}) {
let { isIncremental, isDeveloping, rootDir } = options;
isDeveloping ??= process.argv.includes('--develop');
isIncremental ??= isDeveloping && !process.argv.includes('--no-incremental');
const eleventy = new Eleventy(rootDir || getDocsDir(), getSiteDir(), {
quietMode: true,
configPath: getEleventyConfigPath(),
config: eleventyConfig => {
if (isDeveloping || isIncremental) {
eleventyConfig.setUseTemplateCache(false);
eleventyConfig.on('eleventy.before', function () {
queueBuild();
});
eleventyConfig.on('eleventy.beforeWatch', async function () {
queueBuild();
});
eleventyConfig.on('eleventy.after', async function () {
eleventyBuildResolver();
});
}
},
source: 'script',
runMode: isIncremental ? 'watch' : 'build',
});
eleventy.setIncrementalBuild(isIncremental);
await eleventy.init();
eleventy.logger.isChalkEnabled = false;
eleventy.logger.overrideLogger(new CustomLogger());
if (isIncremental) {
await eleventy.watch();
process.on('SIGINT', async () => {
await eleventy.stopWatch();
process.exitCode = 0;
});
}
return eleventy;
}
/**
* Generates the documentation site.
*/
export async function generateDocs(options = {}) {
let { spinner, isIncremental, isDeveloping } = options;
isDeveloping ??= process.argv.includes('--develop');
isIncremental ??= isDeveloping && !process.argv.includes('--no-incremental');
let eleventy = globalThis.eleventy;
/**
* Used by the webawesome-app to skip doc generation since it will do its own.
*/
if (process.env.SKIP_ELEVENTY === 'true') {
return;
}
spinner?.start?.('Writing the docs');
const outputs = {
warn: [],
};
function stubConsole(key) {
const originalFn = console[key];
console[key] = function (...args) {
outputs[key].push(...args);
};
return originalFn;
}
// Works around a bug in 11ty where it still prints warnings despite the logger being overriden and in quietMode.
const originalWarn = stubConsole('warn');
let output = '';
try {
if (isIncremental) {
if (!globalThis.eleventy) {
// First run
globalThis.eleventy = await createEleventy(options);
eleventy = globalThis.eleventy;
output = chalk.gray(`(${eleventy.logFinished()})`);
} else {
// eleventy incremental does its own writing, so we just kinda trust it for right now.
eleventy = globalThis.eleventy;
await eleventyBuildPromise;
let info = eleventy.logger.logger.outputs.log;
// TODO: The first write with incremental seems to be 1 behind. Not sure why. But its good enough for now.
info = info.filter(line => {
return !line.includes('Watching');
});
const lastLine = info[info.length - 1];
output = chalk.gray(`(${lastLine})`);
eleventy.logger.logger.reset();
}
} else {
// Cleanup
await deleteAsync(getSiteDir());
globalThis.eleventy = await createEleventy(options);
eleventy = globalThis.eleventy;
// Write it
await eleventy.write();
output = chalk.gray(`(${eleventy.logFinished()})`);
}
// Copy dist (production only)
if (!isDeveloping) {
await copy(getCdnDir(), join(getSiteDir(), 'dist'));
}
spinner?.succeed?.(`Writing the docs ${output}`);
} catch (error) {
console.warn = originalWarn;
console.error('\n\n' + chalk.red(error) + '\n');
spinner?.fail?.(chalk.red(`Error while writing the docs.`));
if (!isDeveloping) {
process.exit(1);
}
}
}
/**
* Much of this code is taken from 11ty's ConsoleLogger here:
* https://github.com/11ty/eleventy/blob/main/src/Util/ConsoleLogger.js
*
* Patches 11ty logger so it doesnt log everything, but we can still use its output for our own build.
* @typedef {'error'|'log'|'warn'|'info'} LogType
*/
class CustomLogger {
#outputStream;
constructor() {
this.reset();
}
flush() {
Object.keys(this.outputs).forEach(outputType => {
console[outputType](this.outputs[outputType].join(''));
});
this.reset();
}
reset() {
this.outputs = {
log: [],
info: [],
warn: [],
error: [],
};
}
/** @param {string} msg */
log(msg) {
this.message(msg);
}
/**
* @typedef LogOptions
* @property {string} message
* @property {string=} prefix
* @property {LogType=} type
* @property {string=} color
* @property {boolean=} force
* @param {LogOptions} options
*/
logWithOptions({ message, type, prefix, color, force }) {
this.message(message, type, color, force, prefix);
}
/** @param {string} msg */
forceLog(msg) {
this.message(msg, undefined, undefined, true);
}
/** @param {string} msg */
info(msg) {
this.message(msg, 'info', 'blue');
}
/** @param {string} msg */
warn(msg) {
this.message(msg, 'warn', 'yellow');
}
/** @param {string} msg */
error(msg) {
this.message(msg, 'error', 'red');
}
get outputStream() {
if (!this.#outputStream) {
this.#outputStream = new Readable({
read() {},
});
}
return this.#outputStream;
}
/** @param {string} msg */
toStream(msg) {
this.outputStream.push(msg);
}
closeStream() {
this.outputStream.push(null);
return this.outputStream;
}
/**
* Formats the message to log.
*
* @param {string} message - The raw message to log.
* @param {LogType} [type='log'] - The error level to log.
* @param {string|undefined} [chalkColor=undefined] - Color name or falsy to disable
* @param {boolean} [forceToConsole=false] - Enforce a log on console instead of specified target.
*/
message(message, type = 'log', chalkColor = undefined, _forceToConsole = false, prefix = '') {
// if (chalkColor && this.isChalkEnabled) {
// message = `${chalk.gray(prefix)} ${message.split("\n").join(`\n${chalk.gray(prefix)} `)}`;
// this.outputs[type].push(chalk[chalkColor](message));
// } else {
message = `${prefix}${message.split('\n').join(`\n${prefix}`)}`;
this.outputs[type].push(message);
// }
}
}

View File

@@ -25,7 +25,7 @@ for await (const component of components) {
const tagWithoutPrefix = component.tagName.replace(/^wa-/, '');
const componentDir = path.join(reactDir, tagWithoutPrefix);
const componentFile = path.join(componentDir, 'index.ts');
const importPath = path.relative(srcDir, component.path);
const importPath = path.posix.relative(srcDir, component.path);
// We only want to wrap wa- prefixed events, because the others are native
const eventsToWrap = component.events?.filter(event => event.name.startsWith('wa-')) || [];

View File

@@ -6,7 +6,7 @@ import styles from './{{ tagWithoutPrefix tag }}.css';
/**
* @summary Short summary of the component's intended use.
* @documentation https://backers.webawesome.com/docs/components/{{ tagWithoutPrefix tag }}
* @documentation https://webawesome.com/docs/components/{{ tagWithoutPrefix tag }}
* @status experimental
* @since 3.0
*

View File

@@ -9,7 +9,7 @@ import styles from './animated-image.css';
/**
* @summary A component for displaying animated GIFs and WEBPs that play and pause on interaction.
* @documentation https://backers.webawesome.com/docs/components/animated-image
* @documentation https://webawesome.com/docs/components/animated-image
* @status stable
* @since 2.0
*

View File

@@ -10,7 +10,7 @@ import { animations } from './animations.js';
/**
* @summary Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes. Powered by the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
* @documentation https://backers.webawesome.com/docs/components/animation
* @documentation https://webawesome.com/docs/components/animation
* @status stable
* @since 2.0
*

View File

@@ -8,7 +8,7 @@ import styles from './avatar.css';
/**
* @summary Avatars are used to represent a person or object.
* @documentation https://backers.webawesome.com/docs/components/avatar
* @documentation https://webawesome.com/docs/components/avatar
* @status stable
* @since 2.0
*

View File

@@ -1,4 +1,6 @@
:host {
--pulse-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud));
display: inline-flex;
align-items: center;
justify-content: center;
@@ -19,29 +21,31 @@
}
/* Appearance modifiers */
:host([appearance~='plain']) {
color: var(--wa-color-on-quiet, var(--wa-color-brand-on-quiet));
background-color: transparent;
border-color: transparent;
}
:host([appearance~='outlined']) {
--pulse-color: var(--wa-color-border-loud, var(--wa-color-brand-border-loud));
color: var(--wa-color-on-quiet, var(--wa-color-brand-on-quiet));
background-color: transparent;
border-color: var(--wa-color-border-loud, var(--wa-color-brand-border-loud));
}
:host([appearance~='filled']) {
--pulse-color: var(--wa-color-fill-normal, var(--wa-color-brand-fill-normal));
color: var(--wa-color-on-normal, var(--wa-color-brand-on-normal));
background-color: var(--wa-color-fill-normal, var(--wa-color-brand-fill-normal));
border-color: transparent;
}
:host([appearance~='filled'][appearance~='outlined']) {
--pulse-color: var(--wa-color-border-normal, var(--wa-color-brand-border-normal));
border-color: var(--wa-color-border-normal, var(--wa-color-brand-border-normal));
}
:host([appearance~='accent']) {
--pulse-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud));
color: var(--wa-color-on-loud, var(--wa-color-brand-on-loud));
background-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud));
border-color: transparent;
@@ -54,8 +58,6 @@
/* Pulse attention */
:host([attention='pulse']) {
--pulse-color: var(--background-color);
animation: pulse 1.5s infinite;
}

View File

@@ -6,7 +6,7 @@ import styles from './badge.css';
/**
* @summary Badges are used to draw attention and display statuses or counts.
* @documentation https://backers.webawesome.com/docs/components/badge
* @documentation https://webawesome.com/docs/components/badge
* @status stable
* @since 2.0
*
@@ -14,6 +14,8 @@ import styles from './badge.css';
*
* @csspart base - The component's base wrapper.
*
* @cssproperty --pulse-color - The color of the badge's pulse effect when using `attention="pulse"`.
*
*/
@customElement('wa-badge')
export default class WaBadge extends WebAwesomeElement {
@@ -28,8 +30,8 @@ export default class WaBadge extends WebAwesomeElement {
/** Draws a pill-style badge with rounded edges. */
@property({ type: Boolean, reflect: true }) pill = false;
/** Makes the badge pulsate to draw attention. */
@property({ reflect: true }) attention: 'none' | 'pulse' = 'none';
/** Adds an animation to draw attention to the badge. */
@property({ reflect: true }) attention: 'none' | 'pulse' | 'bounce' = 'none';
render() {
return html` <slot part="base" role="status"></slot>`;

View File

@@ -7,7 +7,7 @@ import styles from './breadcrumb-item.css';
/**
* @summary Breadcrumb Items are used inside breadcrumbs to represent different links.
* @documentation https://backers.webawesome.com/docs/components/breadcrumb-item
* @documentation https://webawesome.com/docs/components/breadcrumb-item
* @status stable
* @since 2.0
*

View File

@@ -8,7 +8,7 @@ import styles from './breadcrumb.css';
/**
* @summary Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
* @documentation https://backers.webawesome.com/docs/components/breadcrumb
* @documentation https://webawesome.com/docs/components/breadcrumb
* @status stable
* @since 2.0
*

View File

@@ -26,8 +26,7 @@
z-index: 2 !important;
}
}
:host([orientation='vertical']) {
:host([orientation='vertical']) .button-group {
flex-direction: column;
}

View File

@@ -10,7 +10,7 @@ import styles from './button-group.css';
/**
* @summary Button groups can be used to group related buttons into sections.
* @documentation https://backers.webawesome.com/docs/components/button-group
* @documentation https://webawesome.com/docs/components/button-group
* @status stable
* @since 2.0
*
@@ -101,7 +101,10 @@ export default class WaButtonGroup extends WebAwesomeElement {
return html`
<slot
part="base"
class=${classMap({ 'button-group': true, 'has-outlined': this.hasOutlined })}
class=${classMap({
'button-group': true,
'has-outlined': this.hasOutlined,
})}
role="${this.disableRole ? 'presentation' : 'group'}"
aria-label=${this.label}
aria-orientation=${this.orientation}

View File

@@ -309,22 +309,22 @@ slot[name='end']::slotted(*),
}
/* Handle pill modifier for button groups */
:host([pill]) .wa-button-group__horizontal.wa-button-group__button-first {
:host([pill].wa-button-group__horizontal.wa-button-group__button-first) .button {
border-start-start-radius: var(--wa-border-radius-pill);
border-end-start-radius: var(--wa-border-radius-pill);
}
:host([pill]) .wa-button-group__horizontal.wa-button-group__button-last {
:host([pill].wa-button-group__horizontal.wa-button-group__button-last) .button {
border-start-end-radius: var(--wa-border-radius-pill);
border-end-end-radius: var(--wa-border-radius-pill);
}
:host([pill]) .wa-button-group__vertical.wa-button-group__button-first {
:host([pill].wa-button-group__vertical.wa-button-group__button-first) .button {
border-start-start-radius: var(--wa-border-radius-pill);
border-start-end-radius: var(--wa-border-radius-pill);
}
:host([pill]) .wa-button-group__vertical.wa-button-group__button-last {
:host([pill].wa-button-group__vertical.wa-button-group__button-last) .button {
border-end-start-radius: var(--wa-border-radius-pill);
border-end-end-radius: var(--wa-border-radius-pill);
}

View File

@@ -16,7 +16,7 @@ import styles from './button.css';
/**
* @summary Buttons represent actions that are available to the user.
* @documentation https://backers.webawesome.com/docs/components/button
* @documentation https://webawesome.com/docs/components/button
* @status stable
* @since 2.0
*

View File

@@ -7,7 +7,7 @@ import styles from './callout.css';
/**
* @summary Callouts are used to display important messages inline.
* @documentation https://backers.webawesome.com/docs/components/callout
* @documentation https://webawesome.com/docs/components/callout
* @status stable
* @since 2.0
*

View File

@@ -63,7 +63,7 @@
&::slotted(*) {
display: block;
width: 100%;
border-radius: 0;
border-radius: 0 !important;
}
}

View File

@@ -7,7 +7,7 @@ import styles from './card.css';
/**
* @summary Cards can be used to group related subjects in a container.
* @documentation https://backers.webawesome.com/docs/components/card
* @documentation https://webawesome.com/docs/components/card
* @status stable
* @since 2.0
*

View File

@@ -15,7 +15,7 @@ import styles from './checkbox.css';
/**
* @summary Checkboxes allow the user to toggle an option on or off.
* @documentation https://backers.webawesome.com/docs/components/checkbox
* @documentation https://webawesome.com/docs/components/checkbox
* @status stable
* @since 2.0
*

View File

@@ -39,7 +39,7 @@ declare const EyeDropper: EyeDropperConstructor;
/**
* @summary Color pickers allow the user to select a color.
* @documentation https://backers.webawesome.com/docs/components/color-picker
* @documentation https://webawesome.com/docs/components/color-picker
* @status stable
* @since 2.0
*

View File

@@ -11,7 +11,7 @@ import styles from './comparison.css';
/**
* @summary Compare visual differences between similar content with a sliding panel.
* @documentation https://backers.webawesome.com/docs/components/comparison
* @documentation https://webawesome.com/docs/components/comparison
* @status stable
* @since 2.0
*

View File

@@ -14,7 +14,7 @@ import styles from './copy-button.css';
/**
* @summary Copies text data to the clipboard when the user clicks the trigger.
* @documentation https://backers.webawesome.com/docs/components/copy
* @documentation https://webawesome.com/docs/components/copy
* @status experimental
* @since 2.7
*

View File

@@ -196,8 +196,9 @@ describe('<wa-details>', () => {
await first.show();
await second.show();
expect(firstBody.clientHeight).to.equal(200);
expect(secondBody.clientHeight).to.equal(400);
// height + 32 (padding probably?)
expect(firstBody.clientHeight).to.equal(232);
expect(secondBody.clientHeight).to.equal(432);
});
});
}

View File

@@ -14,7 +14,7 @@ import styles from './details.css';
/**
* @summary Details show a brief summary and expand to show additional content.
* @documentation https://backers.webawesome.com/docs/components/details
* @documentation https://webawesome.com/docs/components/details
* @status stable
* @since 2.0
*

View File

@@ -17,7 +17,7 @@ import styles from './dialog.css';
/**
* @summary Dialogs, sometimes called "modals", appear above the page and require the user's immediate attention.
* @documentation https://backers.webawesome.com/docs/components/dialog
* @documentation https://webawesome.com/docs/components/dialog
* @status stable
* @since 2.0
*

View File

@@ -5,7 +5,7 @@ import styles from './divider.css';
/**
* @summary Dividers are used to visually separate or group elements.
* @documentation https://backers.webawesome.com/docs/components/divider
* @documentation https://webawesome.com/docs/components/divider
* @status stable
* @since 2.0
*

View File

@@ -17,7 +17,7 @@ import styles from './drawer.css';
/**
* @summary Drawers slide in from a container to expose additional options and information.
* @documentation https://backers.webawesome.com/docs/components/drawer
* @documentation https://webawesome.com/docs/components/drawer
* @status stable
* @since 2.0
*

View File

@@ -8,7 +8,7 @@ import styles from './dropdown-item.css';
/**
* @summary Represents an individual item within a dropdown menu, supporting standard items, checkboxes, and submenus.
* @documentation https://backers.webawesome.com/docs/components/dropdown-item
* @documentation https://webawesome.com/docs/components/dropdown-item
* @status experimental
* @since 3.0
*

View File

@@ -24,7 +24,7 @@ const openDropdowns = new Set<WaDropdown>();
/**
* @summary Dropdowns display a list of options that can be triggered by a button or other element. They support
* keyboard navigation, submenus, and various customization options.
* @documentation https://backers.webawesome.com/docs/components/dropdown
* @documentation https://webawesome.com/docs/components/dropdown
* @status stable
* @since 2.0
*

View File

@@ -4,7 +4,7 @@ import { LocalizeController } from '../../utilities/localize.js';
/**
* @summary Formats a number as a human readable bytes value.
* @documentation https://backers.webawesome.com/docs/components/format-bytes
* @documentation https://webawesome.com/docs/components/format-bytes
* @status stable
* @since 2.0
*/

View File

@@ -5,7 +5,7 @@ import { LocalizeController } from '../../utilities/localize.js';
/**
* @summary Formats a date/time using the specified locale and options.
* @documentation https://backers.webawesome.com/docs/components/format-date
* @documentation https://webawesome.com/docs/components/format-date
* @status stable
* @since 2.0
*/

View File

@@ -4,7 +4,7 @@ import { LocalizeController } from '../../utilities/localize.js';
/**
* @summary Formats a number using the specified locale and options.
* @documentation https://backers.webawesome.com/docs/components/format-number
* @documentation https://webawesome.com/docs/components/format-number
* @status stable
* @since 2.0
*/

View File

@@ -24,7 +24,7 @@ interface IconSource {
/**
* @summary Icons are symbols that can be used to represent various options within an application.
* @documentation https://backers.webawesome.com/docs/components/icon
* @documentation https://webawesome.com/docs/components/icon
* @status stable
* @since 2.0
*

View File

@@ -9,7 +9,7 @@ import { requestInclude } from './request.js';
/**
* @summary Includes give you the power to embed external HTML files into the page.
* @documentation https://backers.webawesome.com/docs/components/include
* @documentation https://webawesome.com/docs/components/include
* @status stable
* @since 2.0
*

View File

@@ -17,7 +17,7 @@ import styles from './input.css';
/**
* @summary Inputs collect data from the user.
* @documentation https://backers.webawesome.com/docs/components/input
* @documentation https://webawesome.com/docs/components/input
* @status stable
* @since 2.0
*

View File

@@ -7,7 +7,7 @@ import styles from './mutation-observer.css';
/**
* @summary The Mutation Observer component offers a thin, declarative interface to the [`MutationObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
* @documentation https://backers.webawesome.com/docs/components/mutation-observer
* @documentation https://webawesome.com/docs/components/mutation-observer
* @status stable
* @since 2.0
*

View File

@@ -9,7 +9,7 @@ import styles from './option.css';
/**
* @summary Options define the selectable items within a select component.
* @documentation https://backers.webawesome.com/docs/components/option
* @documentation https://webawesome.com/docs/components/option
* @status stable
* @since 2.0
*

View File

@@ -79,7 +79,7 @@ function toLength(px: number | string): string {
/**
* @summary Pages offer an easy way to scaffold entire page layouts using minimal markup.
* @documentation https://backers.webawesome.com/docs/components/page
* @documentation https://webawesome.com/docs/components/page
* @status experimental
* @since 3.0
*

View File

@@ -18,7 +18,7 @@ const openPopovers = new Set<WaPopover>();
/**
* @summary Popovers display contextual content and interactive elements in a floating panel.
* @documentation https://backers.webawesome.com/docs/components/popover
* @documentation https://webawesome.com/docs/components/popover
* @status stable
* @since 3.0
*

View File

@@ -37,7 +37,7 @@ const SUPPORTS_POPOVER = globalThis?.HTMLElement?.prototype.hasOwnProperty('popo
/**
* @summary Popup is a utility that lets you declaratively anchor "popup" containers to another element.
* @documentation https://backers.webawesome.com/docs/components/popup
* @documentation https://webawesome.com/docs/components/popup
* @status stable
* @since 2.0
*

View File

@@ -1,4 +1,5 @@
:host {
--track-height: 1rem;
--track-color: var(--wa-color-neutral-fill-normal);
--indicator-color: var(--wa-color-brand-fill-loud);
@@ -10,10 +11,11 @@
display: flex;
position: relative;
overflow: hidden;
height: 1rem;
height: var(--track-height);
border-radius: var(--wa-border-radius-pill);
background-color: var(--track-color);
color: var(--wa-color-brand-on-loud);
font-size: var(--wa-font-size-s);
}
.indicator {

View File

@@ -9,7 +9,7 @@ import styles from './progress-bar.css';
/**
* @summary Progress bars are used to show the status of an ongoing operation.
* @documentation https://backers.webawesome.com/docs/components/progress-bar
* @documentation https://webawesome.com/docs/components/progress-bar
* @status stable
* @since 2.0
*
@@ -19,8 +19,9 @@ import styles from './progress-bar.css';
* @csspart indicator - The progress bar's indicator.
* @csspart label - The progress bar's label.
*
* @cssproperty --track-color - The color of the track.
* @cssproperty --indicator-color - The color of the indicator.
* @cssproperty [--track-height=1rem] - The color of the track.
* @cssproperty [--track-color=var(--wa-color-neutral-fill-normal)] - The color of the track.
* @cssproperty [--indicator-color=var(--wa-color-brand-fill-loud)] - The color of the indicator.
*/
@customElement('wa-progress-bar')
export default class WaProgressBar extends WebAwesomeElement {

View File

@@ -7,7 +7,7 @@ import styles from './progress-ring.css';
/**
* @summary Progress rings are used to show the progress of a determinate operation in a circular fashion.
* @documentation https://backers.webawesome.com/docs/components/progress-ring
* @documentation https://webawesome.com/docs/components/progress-ring
* @status stable
* @since 2.0
*

View File

@@ -10,7 +10,7 @@ let QrCreator: _QrCreator.default;
/**
* @summary Generates a [QR code](https://www.qrcode.com/) and renders it using the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API).
* @documentation https://backers.webawesome.com/docs/components/qr-code
* @documentation https://webawesome.com/docs/components/qr-code
* @status stable
* @since 2.0
*

View File

@@ -14,7 +14,7 @@ import styles from './radio-group.css';
/**
* @summary Radio groups are used to group multiple [radios](/docs/components/radio) so they function as a single form control.
* @documentation https://backers.webawesome.com/docs/components/radio-group
* @documentation https://webawesome.com/docs/components/radio-group
* @status stable
* @since 2.0
*

View File

@@ -9,7 +9,7 @@ import styles from './radio.css';
/**
* @summary Radios allow the user to select a single option from a group.
* @documentation https://backers.webawesome.com/docs/components/radio
* @documentation https://webawesome.com/docs/components/radio
* @status stable
* @since 2.0
*

View File

@@ -14,7 +14,7 @@ import styles from './rating.css';
/**
* @summary Ratings give users a way to quickly view and provide feedback.
* @documentation https://backers.webawesome.com/docs/components/rating
* @documentation https://webawesome.com/docs/components/rating
* @status stable
* @since 2.0
*

View File

@@ -20,7 +20,7 @@ const availableUnits: UnitConfig[] = [
/**
* @summary Outputs a localized time phrase relative to the current date and time.
* @documentation https://backers.webawesome.com/docs/components/relative-time
* @documentation https://webawesome.com/docs/components/relative-time
* @status stable
* @since 2.0
*/

View File

@@ -7,7 +7,7 @@ import styles from './resize-observer.css';
/**
* @summary The Resize Observer component offers a thin, declarative interface to the [`ResizeObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).
* @documentation https://backers.webawesome.com/docs/components/resize-observer
* @documentation https://webawesome.com/docs/components/resize-observer
* @status stable
* @since 2.0
*

View File

@@ -7,7 +7,7 @@ import styles from './scroller.css';
/**
* @summary Scrollers create an accessible container while providing visual cues that help users identify and navigate
* through content that scrolls.
* @documentation https://backers.webawesome.com/docs/components/card
* @documentation https://webawesome.com/docs/components/scroller
* @status stable
* @since 3.0
*

View File

@@ -1,5 +1,5 @@
import { aTimeout, expect, waitUntil } from '@open-wc/testing';
import { sendKeys } from '@web/test-runner-commands';
import { resetMouse, sendKeys } from '@web/test-runner-commands';
import { html } from 'lit';
import sinon from 'sinon';
import { fixtures } from '../../internal/test/fixture.js';
@@ -200,21 +200,22 @@ describe('<wa-select>', () => {
</wa-select>
`);
const option2 = el.querySelectorAll('wa-option')[1];
const handler = sinon.spy((event: CustomEvent) => {
if (el.validationMessage) {
expect.fail(`Validation message should be empty when ${event.type} is emitted and a value is set`);
}
});
const handler = sinon.spy((_event: InputEvent | Event) => {});
el.addEventListener('change', handler);
el.addEventListener('input', handler);
await clickOnElement(el);
await aTimeout(500);
await el.updateComplete;
await aTimeout(100);
await clickOnElement(option2);
await el.updateComplete;
await aTimeout(500);
// debugger
expect(handler).to.be.calledTwice;
expect(el.value).to.equal(option2.value);
});
});
@@ -648,8 +649,8 @@ describe('<wa-select>', () => {
const el = form.querySelector<WaSelect>('wa-select')!;
expect(el.defaultValue).to.equal('option-1');
expect(el.value).to.equal('');
expect(new FormData(form).get('select')).equal('');
expect(el.value).to.equal(null);
expect(new FormData(form).get('select')).equal(null);
const option = document.createElement('wa-option');
option.value = 'option-1';
@@ -697,8 +698,8 @@ describe('<wa-select>', () => {
);
const el = form.querySelector<WaSelect>('wa-select')!;
expect(el.value).to.equal('');
expect(new FormData(form).get('select')).to.equal('');
expect(el.value).to.equal(null);
expect(new FormData(form).get('select')).to.equal(null);
const option = document.createElement('wa-option');
option.value = 'foo';
@@ -771,12 +772,12 @@ describe('<wa-select>', () => {
);
const el = form.querySelector<WaSelect>('wa-select')!;
expect(el.value).to.equal('');
expect(el.value).to.equal(null);
el.value = 'foo';
el.defaultValue = 'foo';
await aTimeout(10);
await el.updateComplete;
expect(el.value).to.equal('');
expect(el.value).to.equal(null);
const option = document.createElement('wa-option');
option.value = 'foo';
@@ -888,6 +889,43 @@ describe('<wa-select>', () => {
// Get the popup element and check its active state
expect(popup?.active).to.be.true;
});
// https://github.com/shoelace-style/webawesome/issues/1131
// new test, failing only in CI
it.skip('Should work properly with empty values on select', async () => {
const el = await fixture<WaSelect>(html`
<wa-select label="Select one">
<wa-option value="">Blank Option</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
</wa-select>
`);
await resetMouse();
await el.show();
const options = el.querySelectorAll('wa-option');
await aTimeout(100);
// firefox doesnt like clicks -.-
await clickOnElement(options[0]);
await resetMouse();
await el.updateComplete;
expect(el.value).to.equal('');
await aTimeout(100);
await clickOnElement(options[1]);
await resetMouse();
await el.updateComplete;
await aTimeout(100);
expect(el.value).to.equal('option-2');
await clickOnElement(options[0]);
await resetMouse();
await el.updateComplete;
await aTimeout(100);
expect(el.value).to.equal('');
await resetMouse();
});
});
}
});

View File

@@ -29,7 +29,7 @@ import styles from './select.css';
/**
* @summary Selects allow you to choose items from a menu of predefined options.
* @documentation https://backers.webawesome.com/docs/components/select
* @documentation https://webawesome.com/docs/components/select
* @status stable
* @since 2.0
*
@@ -114,22 +114,22 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
@state() displayLabel = '';
@state() currentOption: WaOption;
@state() selectedOptions: WaOption[] = [];
@state() optionValues: Set<string> | undefined;
@state() optionValues: Set<string | null> | undefined;
/** The name of the select, submitted as a name/value pair with form data. */
@property() name = '';
private _defaultValue: string | string[] = '';
private _defaultValue: null | string | string[] = null;
@property({
attribute: false,
})
set defaultValue(val: string | string[]) {
set defaultValue(val: null | string | string[]) {
this._defaultValue = this.convertDefaultValue(val);
}
get defaultValue() {
return this._defaultValue;
return this.convertDefaultValue(this._defaultValue);
}
/**
@@ -147,35 +147,40 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
return val;
}
private _value: string[] | undefined;
private _value: string[] | undefined | null;
/** The select's value. This will be a string for single select or an array for multi-select. */
@property({ attribute: 'value', reflect: false })
set value(val: string | string[]) {
set value(val: string | string[] | null) {
let oldValue = this.value;
if ((val as any) instanceof FormData) {
val = (val as unknown as FormData).getAll(this.name) as string[];
}
if (!Array.isArray(val)) {
if (val != null && !Array.isArray(val)) {
val = [val];
}
this._value = val;
this._value = val ?? null;
let newValue = this.value;
if (newValue !== oldValue) {
this.valueHasChanged = true;
this.requestUpdate('value', oldValue);
}
}
get value() {
let value = this._value ?? this.defaultValue;
value = Array.isArray(value) ? value : [value];
let optionsChanged = !this.optionValues;
let value = this._value ?? this.defaultValue ?? null;
if (optionsChanged) {
if (value != null) {
value = Array.isArray(value) ? value : [value];
}
if (value == null) {
this.optionValues = new Set(null);
} else {
this.optionValues = new Set(
this.getAllOptions()
.filter(option => !option.disabled)
@@ -184,11 +189,11 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
}
// Drop values not in the DOM
let ret: string | string[] = value.filter(v => this.optionValues!.has(v));
ret = this.multiple ? ret : (ret[0] ?? '');
if (optionsChanged) {
this.requestUpdate('value');
let ret: null | string | string[] = value;
if (value != null) {
ret = value.filter(v => this.optionValues!.has(v));
ret = this.multiple ? ret : ret[0];
ret = ret ?? null;
}
return ret;
@@ -291,16 +296,17 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Because this is a form control, it shouldn't be opened initially
this.open = false;
}
if (!this._defaultValue) {
const allOptions = this.getAllOptions();
const selectedOptions = allOptions.filter(el => el.selected || el.defaultSelected);
if (selectedOptions.length > 0) {
const selectedValues = selectedOptions.map(el => el.value);
this._defaultValue = this.multiple ? selectedValues : selectedValues[0];
} else if (this.hasAttribute('value')) {
this._defaultValue = this.getAttribute('value') || '';
}
private updateDefaultValue() {
const allOptions = this.getAllOptions();
const defaultSelectedOptions = allOptions.filter(el => el.hasAttribute('selected') || el.defaultSelected);
if (defaultSelectedOptions.length > 0) {
const selectedValues = defaultSelectedOptions.map(el => el.value);
this._defaultValue = this.multiple ? selectedValues : selectedValues[0];
}
if (this.hasAttribute('value')) {
this._defaultValue = this.getAttribute('value') || null;
}
}
@@ -375,6 +381,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// If it is open, update the value based on the current selection and close it
if (this.currentOption && !this.currentOption.disabled) {
this.valueHasChanged = true;
this.hasInteracted = true;
if (this.multiple) {
this.toggleOptionSelection(this.currentOption);
} else {
@@ -506,7 +513,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
private handleClearClick(event: MouseEvent) {
event.stopPropagation();
if (this.value !== '') {
if (this.value !== null) {
this.setSelectedOptions([]);
this.displayInput.focus({ preventScroll: true });
@@ -528,10 +535,11 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
private handleOptionClick(event: MouseEvent) {
const target = event.target as HTMLElement;
const option = target.closest('wa-option');
const oldValue = this.value;
if (option && !option.disabled) {
this.hasInteracted = true;
this.valueHasChanged = true;
if (this.multiple) {
this.toggleOptionSelection(option);
} else {
@@ -541,13 +549,13 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Set focus after updating so the value is announced by screen readers
this.updateComplete.then(() => this.displayInput.focus({ preventScroll: true }));
if (this.value !== oldValue) {
// Emit after updating
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
this.requestUpdate('value');
// Emit after updating
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
if (!this.multiple) {
this.hide();
@@ -566,18 +574,22 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
this.optionValues = undefined; // dirty the value so it gets recalculated
// Update defaultValue if it hasn't been explicitly set and we have selected options
if (!this._defaultValue && !this.hasUpdated) {
const selectedOptions = allOptions.filter(el => el.selected || el.defaultSelected);
if (selectedOptions.length > 0) {
const selectedValues = selectedOptions.map(el => el.value);
this._defaultValue = this.multiple ? selectedValues : selectedValues[0];
}
this.updateDefaultValue();
let value = this.value;
if (value == null || (!this.valueHasChanged && !this.hasInteracted)) {
this.selectionChanged();
return;
}
const value = this.value;
if (!Array.isArray(value)) {
value = [value];
}
// Select only the options that match the new value
this.setSelectedOptions(allOptions.filter(el => value.includes(el.value) || el.selected));
const selectedOptions = allOptions.filter(el => value.includes(el.value));
this.setSelectedOptions(selectedOptions);
}
private handleTagRemove(event: WaRemoveEvent, directOption?: WaOption) {
@@ -690,29 +702,36 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Update selected options cache
this.selectedOptions = options.filter(el => {
if (!this.hasInteracted && !this.valueHasChanged) {
const defaultValue = this.defaultValue;
const defaultValues = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
return el.hasAttribute('selected') || el.defaultSelected || el.selected || defaultValues?.includes(el.value);
}
return el.selected;
});
let selectedValues = new Set(this.selectedOptions.map(el => el.value));
// Toggle values present in the DOM from this.value, while preserving options NOT present in the DOM (for lazy loading)
// Note that options NOT present in the DOM will be moved to the end after this
if (selectedValues.size > 0 || this._value) {
const oldValue = this._value;
if (!this._value) {
if (this._value == null) {
// First time it's set
let value = this.defaultValue ?? [];
this._value = Array.isArray(value) ? value : [value];
}
// Filter out values that are in the DOM
this._value = this._value.filter(value => !this.optionValues?.has(value));
this._value.unshift(...selectedValues);
this._value = this._value?.filter(value => !this.optionValues?.has(value)) ?? null;
this._value?.unshift(...selectedValues);
this.requestUpdate('value', oldValue);
}
// Update the value and display label
if (this.multiple) {
if (this.placeholder && this.value.length === 0) {
if (this.placeholder && !this.value?.length) {
// When no items are selected, keep the value empty so the placeholder shows
this.displayLabel = '';
} else {
@@ -776,7 +795,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
const value = Array.isArray(this.value) ? this.value : [this.value];
// Select only the options that match the new value
this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));
const selectedOptions = allOptions.filter(el => value.includes(el.value));
this.setSelectedOptions(selectedOptions);
this.updateValidity();
}

View File

@@ -5,7 +5,7 @@ import styles from './skeleton.css';
/**
* @summary Skeletons are used to provide a visual representation of where content will eventually be drawn.
* @documentation https://backers.webawesome.com/docs/components/skeleton
* @documentation https://webawesome.com/docs/components/skeleton
* @status stable
* @since 2.0
*

View File

@@ -19,7 +19,7 @@ import styles from './slider.css';
* <wa-slider>
*
* @summary Ranges allow the user to select a single value within a given range using a slider.
* @documentation https://backers.webawesome.com/docs/components/range
* @documentation https://webawesome.com/docs/components/range
* @status stable
* @since 2.0
*
@@ -769,8 +769,10 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
}
render() {
const hasLabel = this.hasSlotController.test('label');
const hasHint = this.hasSlotController.test('hint');
const hasLabelSlot = this.hasSlotController.test('label');
const hasHintSlot = this.hasSlotController.test('hint');
const hasLabel = this.label ? true : !!hasLabelSlot;
const hasHint = this.hint ? true : !!hasHintSlot;
const hasReference = this.hasSlotController.test('reference');
const sliderClasses = classMap({
@@ -791,7 +793,7 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
}
// Common UI fragments
const labelAndHint = html`
const label = html`
<label
id="label"
part="label"
@@ -801,8 +803,16 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
>
<slot name="label">${this.label}</slot>
</label>
`;
<div id="hint" part="hint" class=${classMap({ vh: !hasHint })}>
const hint = html`
<div
id="hint"
part="hint"
class=${classMap({
'has-slotted': hasHint,
})}
>
<slot name="hint">${this.hint}</slot>
</div>
`;
@@ -856,7 +866,7 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
const maxThumbPosition = clamp(this.getPercentageFromValue(this.maxValue), 0, 100);
return html`
${labelAndHint}
${label}
<div id="slider" part="slider" class=${sliderClasses}>
<div id="track" part="track">
@@ -914,7 +924,7 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
></span>
</div>
${referencesTemplate}
${referencesTemplate} ${hint}
</div>
${createTooltip('thumb-min', this.minValue)} ${createTooltip('thumb-max', this.maxValue)}
@@ -929,7 +939,7 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
);
return html`
${labelAndHint}
${label}
<div
id="slider"
@@ -963,7 +973,7 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
<span id="thumb" part="thumb" style="--position: ${thumbPosition}%"></span>
</div>
${referencesTemplate}
${referencesTemplate} ${hint}
</div>
${createTooltip('thumb', this.value)}

View File

@@ -6,7 +6,7 @@ import styles from './spinner.css';
/**
* @summary Spinners are used to show the progress of an indeterminate operation.
* @documentation https://backers.webawesome.com/docs/components/spinner
* @documentation https://webawesome.com/docs/components/spinner
* @status stable
* @since 2.0
*

View File

@@ -11,7 +11,7 @@ import styles from './split-panel.css';
/**
* @summary Split panels display two adjacent panels, allowing the user to reposition them.
* @documentation https://backers.webawesome.com/docs/components/split-panel
* @documentation https://webawesome.com/docs/components/split-panel
* @status stable
* @since 2.0
*

View File

@@ -14,7 +14,7 @@ import styles from './switch.css';
/**
* @summary Switches allow the user to toggle an option on or off.
* @documentation https://backers.webawesome.com/docs/components/switch
* @documentation https://webawesome.com/docs/components/switch
* @status stable
* @since 2.0
*

View File

@@ -16,7 +16,7 @@ import styles from './tab-group.css';
/**
* @summary Tab groups organize content into a container that shows one section at a time.
* @documentation https://backers.webawesome.com/docs/components/tab-group
* @documentation https://webawesome.com/docs/components/tab-group
* @status stable
* @since 2.0
*

View File

@@ -9,7 +9,7 @@ let id = 0;
/**
* @summary Tab panels are used inside [tab groups](/docs/components/tab-group) to display tabbed content.
* @documentation https://backers.webawesome.com/docs/components/tab-panel
* @documentation https://webawesome.com/docs/components/tab-panel
* @status stable
* @since 2.0
*

View File

@@ -9,7 +9,7 @@ let id = 0;
/**
* @summary Tabs are used inside [tab groups](/docs/components/tab-group) to represent and activate [tab panels](/docs/components/tab-panel).
* @documentation https://backers.webawesome.com/docs/components/tab
* @documentation https://webawesome.com/docs/components/tab
* @status stable
* @since 2.0
*

View File

@@ -10,7 +10,7 @@ import styles from './tag.css';
/**
* @summary Tags are used as labels to organize things or to indicate a selection.
* @documentation https://backers.webawesome.com/docs/components/tag
* @documentation https://webawesome.com/docs/components/tag
* @status stable
* @since 2.0
*

View File

@@ -2,10 +2,9 @@
border-width: 0;
}
/* Shared textarea and size-adjuster positioning */
.textarea,
.size-adjuster {
grid-area: 1 / 1 / 2 / 2;
.textarea {
display: grid;
align-items: center;
margin: 0;
border: none;
outline: none;
@@ -72,6 +71,12 @@ textarea {
}
}
/* Shared textarea and size-adjuster positioning */
.control,
.size-adjuster {
grid-area: 1 / 1 / 2 / 2;
}
.size-adjuster {
visibility: hidden;
pointer-events: none;

View File

@@ -14,7 +14,7 @@ import styles from './textarea.css';
/**
* @summary Textareas collect data from the user and allow multiple lines of text.
* @documentation https://backers.webawesome.com/docs/components/textarea
* @documentation https://webawesome.com/docs/components/textarea
* @status stable
* @since 2.0
*

View File

@@ -15,7 +15,7 @@ import styles from './tooltip.css';
/**
* @summary Tooltips display additional information based on a specific action.
* @documentation https://backers.webawesome.com/docs/components/tooltip
* @documentation https://webawesome.com/docs/components/tooltip
* @status stable
* @since 2.0
*

View File

@@ -21,7 +21,7 @@ import styles from './tree-item.css';
/**
* @summary A tree item serves as a hierarchical node that lives inside a [tree](/docs/components/tree).
* @documentation https://backers.webawesome.com/docs/components/tree-item
* @documentation https://webawesome.com/docs/components/tree-item
* @status stable
* @since 2.0
*

Some files were not shown because too many files have changed in this diff Show More