Merge branch 'eslint-ts-a11y' of https://github.com/CreativeTechGuy/shoelace into CreativeTechGuy-eslint-ts-a11y

This commit is contained in:
Cory LaViska
2022-01-18 08:58:14 -05:00
179 changed files with 17295 additions and 1600 deletions

View File

@@ -1,3 +1,5 @@
/* global Prism */
(() => {
const reactVersion = '17.0.2';
let flavor = getFlavor();
@@ -61,14 +63,9 @@
document.body.classList.toggle('flavor-react', flavor === 'react');
}
function wrap(el, wrapper) {
el.parentNode.insertBefore(wrapper, el);
wrapper.appendChild(el);
}
window.$docsify.plugins.push((hook, vm) => {
window.$docsify.plugins.push(hook => {
// Convert code blocks to previews
hook.afterEach(function (html, next) {
hook.afterEach((html, next) => {
const domParser = new DOMParser();
const doc = domParser.parseFromString(html, 'text/html');
@@ -111,7 +108,7 @@
</button>
`;
[...doc.querySelectorAll('code[class^="lang-"]')].map(code => {
[...doc.querySelectorAll('code[class^="lang-"]')].forEach(code => {
if (code.classList.contains('preview')) {
const isExpanded = code.classList.contains('expanded');
const pre = code.closest('pre');
@@ -119,12 +116,6 @@
const toggleId = `code-block-toggle-${count}`;
const reactPre = getAdjacentExample('react', pre);
const hasReact = reactPre !== null;
const examples = [
{
name: 'HTML',
codeBlock: pre
}
];
pre.setAttribute('data-lang', pre.getAttribute('data-lang').replace(/ preview$/, ''));
pre.setAttribute('aria-labelledby', toggleId);
@@ -182,7 +173,7 @@
`;
pre.replaceWith(domParser.parseFromString(codeBlock, 'text/html').body);
if (reactPre) reactPre.remove();
reactPre?.remove();
count++;
}
@@ -201,12 +192,12 @@
// Horizontal resizing
hook.doneEach(() => {
[...document.querySelectorAll('.code-block__preview')].map(preview => {
[...document.querySelectorAll('.code-block__preview')].forEach(preview => {
const resizer = preview.querySelector('.code-block__resizer');
let startX;
let startWidth;
const dragStart = event => {
function dragStart(event) {
startX = event.changedTouches ? event.changedTouches[0].pageX : event.clientX;
startWidth = parseInt(document.defaultView.getComputedStyle(preview).width, 10);
preview.classList.add('code-block__preview--dragging');
@@ -215,21 +206,23 @@
document.documentElement.addEventListener('touchmove', dragMove, false);
document.documentElement.addEventListener('mouseup', dragStop, false);
document.documentElement.addEventListener('touchend', dragStop, false);
};
}
const dragMove = event => {
function dragMove(event) {
setWidth(startWidth + (event.changedTouches ? event.changedTouches[0].pageX : event.pageX) - startX);
};
}
const dragStop = event => {
function dragStop() {
preview.classList.remove('code-block__preview--dragging');
document.documentElement.removeEventListener('mousemove', dragMove, false);
document.documentElement.removeEventListener('touchmove', dragMove, false);
document.documentElement.removeEventListener('mouseup', dragStop, false);
document.documentElement.removeEventListener('touchend', dragStop, false);
};
}
const setWidth = width => (preview.style.width = width + 'px');
function setWidth(width) {
preview.style.width = `${width}px`;
}
resizer.addEventListener('mousedown', dragStart);
resizer.addEventListener('touchstart', dragStart);
@@ -240,7 +233,6 @@
// Toggle source mode
document.addEventListener('click', event => {
const button = event.target.closest('button');
const codeBlock = button?.closest('.code-block');
if (button?.classList.contains('code-block__button--html')) {
setFlavor('html');
@@ -251,7 +243,7 @@
}
// Update flavor buttons
[...document.querySelectorAll('.code-block')].map(codeBlock => {
[...document.querySelectorAll('.code-block')].forEach(codeBlock => {
codeBlock
.querySelector('.code-block__button--html')
?.classList.toggle('code-block__button--selected', flavor === 'html');
@@ -310,8 +302,7 @@
if (!isReact) {
htmlTemplate =
`<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${version}/dist/shoelace.js"></script>\n` +
'\n' +
htmlExample;
`\n${htmlExample}`;
jsTemplate = '';
}
@@ -322,13 +313,11 @@
`import React from 'https://cdn.skypack.dev/react@${reactVersion}';\n` +
`import ReactDOM from 'https://cdn.skypack.dev/react-dom@${reactVersion}';\n` +
`import { setBasePath } from 'https://cdn.skypack.dev/@shoelace-style/shoelace@${version}/dist/utilities/base-path';\n` +
'\n' +
`\n` +
`// Set the base path for Shoelace assets\n` +
`setBasePath('https://cdn.skypack.dev/@shoelace-style/shoelace@${version}/dist/')\n` +
'\n' +
convertModuleLinks(reactExample) +
'\n' +
'\n' +
`\n${convertModuleLinks(reactExample)}\n` +
`\n` +
`ReactDOM.render(<App />, document.getElementById('root'));`;
}

View File

@@ -20,7 +20,7 @@
<tbody>
${props
.map(prop => {
const hasAttribute = !!prop.attribute;
const hasAttribute = typeof prop.attribute !== 'undefined';
const isAttributeDifferent = prop.attribute !== prop.name;
let attributeInfo = '';
@@ -244,19 +244,19 @@
function getDependencies(tag) {
const component = allComponents.find(c => c.tagName === tag);
if (!component || !Array.isArray(component.dependencies)) {
return [];
return;
}
component.dependencies?.map(tag => {
if (!dependencies.includes(tag)) {
dependencies.push(tag);
component.dependencies?.forEach(dependentTag => {
if (!dependencies.includes(dependentTag)) {
dependencies.push(dependentTag);
}
getDependencies(tag);
getDependencies(dependentTag);
});
}
getDependencies(targetComponent);
dependencies.sort().map(tag => {
dependencies.sort().forEach(tag => {
const li = document.createElement('li');
li.innerHTML = `<code>&lt;${tag}&gt;</code>`;
ul.appendChild(li);
@@ -266,7 +266,11 @@
}
function escapeHtml(html) {
return (html + '')
if (typeof html === 'undefined') {
return '';
}
return html
.toString()
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
@@ -277,8 +281,8 @@
function getAllComponents(metadata) {
const allComponents = [];
metadata.modules?.map(module => {
module.declarations?.map(declaration => {
metadata.modules?.forEach(module => {
module.declarations?.forEach(declaration => {
if (declaration.customElement) {
// Generate the dist path based on the src path and attach it to the component
declaration.path = module.path.replace(/^src\//, 'dist/').replace(/\.ts$/, '.js');
@@ -299,8 +303,8 @@
throw new Error('Docsify must be loaded before installing this plugin.');
}
window.$docsify.plugins.push((hook, vm) => {
hook.mounted(async function () {
window.$docsify.plugins.push(hook => {
hook.mounted(async () => {
const metadata = await customElements;
const target = document.querySelector('.app-name');
@@ -330,7 +334,7 @@
target.appendChild(buttons);
});
hook.beforeEach(async function (content, next) {
hook.beforeEach(async (content, next) => {
const metadata = await customElements;
// Replace %VERSION% placeholders
@@ -342,15 +346,23 @@
let result = '';
if (!component) {
console.error('Component not found in metadata: ' + tag);
console.error(`Component not found in metadata: ${tag}`);
return next(content);
}
let badgeType = 'neutral';
if (component.status === 'stable') badgeType = 'primary';
if (component.status === 'experimental') badgeType = 'warning';
if (component.status === 'planned') badgeType = 'neutral';
if (component.status === 'deprecated') badgeType = 'danger';
if (component.status === 'stable') {
badgeType = 'primary';
}
if (component.status === 'experimental') {
badgeType = 'warning';
}
if (component.status === 'planned') {
badgeType = 'neutral';
}
if (component.status === 'deprecated') {
badgeType = 'danger';
}
result += `
<div class="component-header">
@@ -379,7 +391,7 @@
let result = '';
if (!component) {
console.error('Component not found in metadata: ' + tag);
console.error(`Component not found in metadata: ${tag}`);
return next(content);
}
@@ -521,11 +533,11 @@
});
// Wrap tables so we can scroll them horizontally when needed
hook.doneEach(function () {
hook.doneEach(() => {
const content = document.querySelector('.content');
const tables = [...content.querySelectorAll('table')];
tables.map(table => {
tables.forEach(table => {
table.outerHTML = `
<div class="table-wrapper">
${table.outerHTML}

View File

@@ -7,7 +7,7 @@
// Docsify generates pages dynamically and asynchronously, so when a reload happens, the scroll position can't be
// be restored immediately. This plugin waits until Docsify loads the page and then restores it.
//
window.$docsify.plugins.push((hook, vm) => {
window.$docsify.plugins.push(hook => {
hook.ready(() => {
// Restore
const scrollTop = sessionStorage.getItem('bs-scroll');
@@ -16,7 +16,7 @@
}
// Remember
document.addEventListener('scroll', event => {
document.addEventListener('scroll', () => {
sessionStorage.setItem('bs-scroll', document.documentElement.scrollTop);
});
});

View File

@@ -1,11 +1,12 @@
/* global lunr */
(() => {
if (!window.$docsify) {
throw new Error('Docsify must be loaded before installing this plugin.');
}
window.$docsify.plugins.push((hook, vm) => {
window.$docsify.plugins.push(hook => {
// Append the search box to the sidebar
hook.mounted(function () {
hook.mounted(() => {
const appName = document.querySelector('.sidebar .app-name');
const searchBox = document.createElement('div');
searchBox.classList.add('search-box');
@@ -76,7 +77,6 @@
`;
document.body.append(siteSearch);
const searchButtons = [...document.querySelectorAll('[data-site-search]')];
const overlay = siteSearch.querySelector('.site-search__overlay');
const panel = siteSearch.querySelector('.site-search__panel');
const input = siteSearch.querySelector('.site-search__input');
@@ -89,7 +89,7 @@
let map;
// Load search data
const searchData = fetch('../../../search.json')
fetch('../../../search.json')
.then(res => res.json())
.then(data => {
searchIndex = lunr.Index.load(data.searchIndex);
@@ -203,7 +203,7 @@
}
// Update the selected item
items.map(item => {
items.forEach(item => {
if (item === nextEl) {
item.setAttribute('aria-selected', 'true');
nextEl.scrollIntoView({ block: 'nearest' });
@@ -211,8 +211,6 @@
item.setAttribute('aria-selected', 'false');
}
});
return;
}
}
@@ -228,27 +226,39 @@
matches = searchIndex.search(`${query}~2`);
}
let hasResults = hasQuery && matches.length > 0;
const hasResults = hasQuery && matches.length > 0;
siteSearch.classList.toggle('site-search--has-results', hasQuery && hasResults);
siteSearch.classList.toggle('site-search--no-results', hasQuery && !hasResults);
panel.setAttribute('aria-expanded', hasQuery && hasResults ? 'true' : 'false');
results.innerHTML = '';
matches.map((match, index) => {
matches.forEach((match, index) => {
const page = map[match.ref];
const li = document.createElement('li');
const a = document.createElement('a');
let icon = 'file-text';
if (page.url.includes('getting-started/')) icon = 'lightbulb';
if (page.url.includes('resources/')) icon = 'book';
if (page.url.includes('components/')) icon = 'puzzle';
if (page.url.includes('tokens/')) icon = 'palette2';
if (page.url.includes('utilities/')) icon = 'wrench';
if (page.url.includes('tutorials/')) icon = 'joystick';
if (page.url.includes('getting-started/')) {
icon = 'lightbulb';
}
if (page.url.includes('resources/')) {
icon = 'book';
}
if (page.url.includes('components/')) {
icon = 'puzzle';
}
if (page.url.includes('tokens/')) {
icon = 'palette2';
}
if (page.url.includes('utilities/')) {
icon = 'wrench';
}
if (page.url.includes('tutorials/')) {
icon = 'joystick';
}
a.href = $docsify.routerMode === 'hash' ? `/#/${page.url}` : `/${page.url}`;
a.href = window.$docsify.routerMode === 'hash' ? `/#/${page.url}` : `/${page.url}`;
a.innerHTML = `
<div class="site-search__result-icon">
<sl-icon name="${icon}" aria-hidden="true"></sl-icon>

View File

@@ -3,8 +3,8 @@
throw new Error('Docsify must be loaded before installing this plugin.');
}
window.$docsify.plugins.push((hook, vm) => {
hook.mounted(function () {
window.$docsify.plugins.push(hook => {
hook.mounted(() => {
function getTheme() {
return localStorage.getItem('theme') || 'auto';
}
@@ -12,9 +12,8 @@
function isDark() {
if (theme === 'auto') {
return window.matchMedia('(prefers-color-scheme: dark)').matches;
} else {
return theme === 'dark';
}
return theme === 'dark';
}
function setTheme(newTheme) {
@@ -62,7 +61,7 @@
menu.addEventListener('sl-select', event => setTheme(event.detail.item.value));
// Update the theme when the preference changes
window.matchMedia('(prefers-color-scheme: dark)').addListener(event => setTheme(theme));
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => setTheme(theme));
// Toggle themes when pressing backslash
document.addEventListener('keydown', event => {

View File

@@ -85,7 +85,7 @@ npm install
Once you've cloned the repo, run the following command to spin up the Shoelace dev server.
```bash
npm start
npm run start
```
After the initial build, a browser will open automatically to a local version of the docs. The documentation is powered by Docsify, which uses raw markdown files to generate pages on the fly.
@@ -113,7 +113,7 @@ For more information about running and building the project locally, refer to `R
Shoelace uses [Web Test Runner](https://modern-web.dev/guides/test-runner/getting-started/) for testing. To launch the test runner during development, open a terminal and launch the dev server.
```bash
npm start
npm run start
```
In a second terminal window, launch the test runner.
@@ -124,13 +124,20 @@ npm run test:watch
Follow the on-screen instructions to work with the test runner. Tests will automatically re-run as you make changes.
To run tests only once, make sure to build the project first.
To run all tests only once:
```bash
npm run build
npm run test
```
To run just one test file:
If the test file is `src/components/breadcrumb-item.test.ts`, then `<nameOfTestFile>` would be `breadcrumb-item`.
```bash
npm run test -- --group <nameOfTestFile>
```
## Best Practices
The following is a non-exhaustive list of conventions, patterns, and best practices we try to follow. As a contributor, we ask that you make a good faith effort to follow them as well. This ensures consistency and maintainability throughout the project.