mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-19 07:29:14 +00:00
Compare commits
96 Commits
new-docs
...
docs-searc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cf1171a65 | ||
|
|
414197acc9 | ||
|
|
8fd01e1eda | ||
|
|
e1ca7d1f59 | ||
|
|
f84d6939bd | ||
|
|
82446e2114 | ||
|
|
a4f0ae9088 | ||
|
|
fe3906f766 | ||
|
|
c9e644f3fc | ||
|
|
8ffbd02db7 | ||
|
|
e88d57d17d | ||
|
|
5f4de6d9f5 | ||
|
|
2cce87deeb | ||
|
|
630b5b19a0 | ||
|
|
2ce1451a9f | ||
|
|
2d1badba96 | ||
|
|
1b5db078a7 | ||
|
|
91095bd63a | ||
|
|
d9703a64fd | ||
|
|
4c22e72390 | ||
|
|
d05b8fca20 | ||
|
|
afca2ad2e0 | ||
|
|
b2aa854d98 | ||
|
|
287fff7cf1 | ||
|
|
136ecae4a6 | ||
|
|
cac772d5e6 | ||
|
|
e1dedcb1b5 | ||
|
|
c4901eca68 | ||
|
|
a001c2d12b | ||
|
|
c4c622eabd | ||
|
|
1ae018bedd | ||
|
|
24929e27c1 | ||
|
|
33a8d92aec | ||
|
|
32d21fa560 | ||
|
|
347d8b7f79 | ||
|
|
8f9c15913b | ||
|
|
60d7f688eb | ||
|
|
15f914914c | ||
|
|
2914475821 | ||
|
|
8e831aa3e7 | ||
|
|
985d4585c4 | ||
|
|
854db13bd7 | ||
|
|
4ddf80459a | ||
|
|
89fc2ff643 | ||
|
|
d7145f1f84 | ||
|
|
441a957432 | ||
|
|
67cbb85682 | ||
|
|
0005d16a06 | ||
|
|
ca5ab03cd4 | ||
|
|
c9e30022df | ||
|
|
c167bdd80f | ||
|
|
b9f62bb1bc | ||
|
|
a01b2cf8a2 | ||
|
|
f4b2623c8f | ||
|
|
af8426579e | ||
|
|
6b9ba9becf | ||
|
|
c6cc7b6983 | ||
|
|
0e869ec18d | ||
|
|
1b347874ef | ||
|
|
ff5b1e8573 | ||
|
|
73ad76a2fa | ||
|
|
aadcb486a9 | ||
|
|
4c854d64a7 | ||
|
|
c2e02d34ad | ||
|
|
8c8977549c | ||
|
|
24ef154d42 | ||
|
|
b5a3045bae | ||
|
|
7404e496cb | ||
|
|
5ba2c7eeec | ||
|
|
514a7f3d51 | ||
|
|
15474b83b1 | ||
|
|
a5f1bc6c82 | ||
|
|
834d44e0e4 | ||
|
|
c070149ae6 | ||
|
|
b0b6ea943e | ||
|
|
65b72217ea | ||
|
|
c4c2e8e3a9 | ||
|
|
47018d61cd | ||
|
|
d18db9adfa | ||
|
|
41913c8c58 | ||
|
|
68b982a744 | ||
|
|
a582302a79 | ||
|
|
bd3b2c93ee | ||
|
|
4704d63791 | ||
|
|
415a1477bb | ||
|
|
f363d5e187 | ||
|
|
efb0ee9c48 | ||
|
|
96daee5e1a | ||
|
|
d236206cce | ||
|
|
1ef8e1cf73 | ||
|
|
dc63f858b0 | ||
|
|
b8a3952153 | ||
|
|
4b2a62f660 | ||
|
|
08c074e44b | ||
|
|
d1953b0215 | ||
|
|
7cbb26cbdb |
@@ -92,7 +92,8 @@ module.exports = {
|
||||
'@typescript-eslint/member-delimiter-style': 'warn',
|
||||
'@typescript-eslint/method-signature-style': 'warn',
|
||||
'@typescript-eslint/no-extraneous-class': 'error',
|
||||
'@typescript-eslint/no-parameter-properties': 'error',
|
||||
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
||||
'@typescript-eslint/parameter-properties': 'error',
|
||||
'@typescript-eslint/strict-boolean-expressions': 'off'
|
||||
}
|
||||
},
|
||||
@@ -185,6 +186,17 @@ module.exports = {
|
||||
]
|
||||
}
|
||||
],
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
ignorePackages: true,
|
||||
pattern: {
|
||||
js: 'always',
|
||||
ts: 'never'
|
||||
}
|
||||
}
|
||||
],
|
||||
'import/no-duplicates': 'warn',
|
||||
'sort-imports-es6-autofix/sort-imports-es6': [
|
||||
2,
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ dist
|
||||
docs/assets/images/sprite.svg
|
||||
node_modules
|
||||
src/react
|
||||
cdn
|
||||
|
||||
@@ -9,3 +9,5 @@ src/react/index.ts
|
||||
node_modules
|
||||
package-lock.json
|
||||
tsconfig.json
|
||||
cdn
|
||||
_site
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"CACHEABLE",
|
||||
"callout",
|
||||
"callouts",
|
||||
"cdndir",
|
||||
"chatbubble",
|
||||
"checkmark",
|
||||
"claviska",
|
||||
@@ -27,6 +28,7 @@
|
||||
"colocated",
|
||||
"colour",
|
||||
"combobox",
|
||||
"Commonmark",
|
||||
"Composability",
|
||||
"Consolas",
|
||||
"contenteditable",
|
||||
@@ -43,6 +45,7 @@
|
||||
"dogfood",
|
||||
"dropdowns",
|
||||
"easings",
|
||||
"endraw",
|
||||
"enterkeyhint",
|
||||
"eqeqeq",
|
||||
"erroneou",
|
||||
@@ -102,6 +105,8 @@
|
||||
"noopener",
|
||||
"noreferrer",
|
||||
"novalidate",
|
||||
"npmdir",
|
||||
"Numberish",
|
||||
"outdir",
|
||||
"ParamagicDev",
|
||||
"peta",
|
||||
|
||||
@@ -116,6 +116,7 @@ export default {
|
||||
if (classDoc?.events) {
|
||||
classDoc.events.forEach(event => {
|
||||
event.reactName = `on${pascalCase(event.name)}`;
|
||||
event.eventName = `${pascalCase(event.name)}Event`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,15 +39,15 @@
|
||||
{% if component.summary %}
|
||||
{{ component.summary | markdownInline | safe }}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
</p>
|
||||
|
||||
{# Markdown content #}
|
||||
{{ content | safe }}
|
||||
|
||||
{# Importing #}
|
||||
<h2>Importing</h2>
|
||||
<p>
|
||||
If you're using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use
|
||||
If you're using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use
|
||||
any of the following snippets to <a href="/getting-started/installation#cherry-picking">cherry pick</a> this component.
|
||||
</p>
|
||||
|
||||
@@ -59,32 +59,32 @@
|
||||
|
||||
<sl-tab-panel name="script">
|
||||
<p>
|
||||
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
|
||||
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
|
||||
using a script tag:
|
||||
</p>
|
||||
<pre><code class="language-html"><script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/dist/{{ component.path }}"></script></code></pre>
|
||||
<pre><code class="language-html"><script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/{{ meta.cdndir }}/{{ component.path }}"></script></code></pre>
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="import">
|
||||
<p>
|
||||
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
|
||||
To import this component from <a href="https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace">the CDN</a>
|
||||
using a JavaScript import:
|
||||
</p>
|
||||
<pre><code class="language-js">import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/dist/{{ component.path }}';</code></pre>
|
||||
<pre><code class="language-js">import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@{{ meta.version }}/{{ meta.cdndir }}/{{ component.path }}';</code></pre>
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="bundler">
|
||||
<p>
|
||||
To import this component using <a href="{{ rootUrl('/getting-started/installation#bundling') }}">a bundler</a>:
|
||||
</p>
|
||||
<pre><code class="language-js">import '@shoelace-style/shoelace/dist/{{ component.path }}';</code></pre>
|
||||
<pre><code class="language-js">import '@shoelace-style/shoelace/{{ meta.npmdir }}/{{ component.path }}';</code></pre>
|
||||
</sl-tab-panel>
|
||||
|
||||
<sl-tab-panel name="react">
|
||||
<p>
|
||||
To import this component as a <a href="/frameworks/react">React component</a>:
|
||||
</p>
|
||||
<pre><code class="language-js">import { {{ component.name }} } from '@shoelace-style/shoelace/dist/react';</code></pre>
|
||||
<pre><code class="language-js">import { {{ component.name }} } from '@shoelace-style/shoelace/{{ meta.npmdir }}/react';</code></pre>
|
||||
</sl-tab-panel>
|
||||
</sl-tab-group>
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
<td class="nowrap"><code>updateComplete</code></td>
|
||||
<td>
|
||||
A read-only promise that resolves when the component has
|
||||
<a href="/getting-started/usage?id=component-rendering-and-updating">finished updating</a>.
|
||||
<a href="/getting-started/usage?#component-rendering-and-updating">finished updating</a>.
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
@@ -337,11 +337,11 @@
|
||||
<h2>Dependencies</h2>
|
||||
|
||||
<p>This component automatically imports the following dependencies.</p>
|
||||
|
||||
|
||||
<ul>
|
||||
{% for dependency in component.dependencies %}
|
||||
<li><code><{{ dependency }}></code></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
<script>
|
||||
(() => {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const theme = localStorage.getItem('theme');
|
||||
document.documentElement.classList.toggle('sl-theme-dark', theme === 'dark' || (!theme && prefersDark));
|
||||
const theme = localStorage.getItem('theme') || 'auto';
|
||||
document.documentElement.classList.toggle('sl-theme-dark', theme === 'dark' || (theme === 'auto' && prefersDark));
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -80,16 +80,19 @@
|
||||
<sl-icon name="twitter"></sl-icon>
|
||||
</a>
|
||||
|
||||
{# Theme toggle #}
|
||||
<button
|
||||
id="theme-toggle"
|
||||
type="button"
|
||||
aria-label="Toggle light and dark theme"
|
||||
title="Toggle theme (press backslash)"
|
||||
>
|
||||
<sl-icon class="only-light" name="sun-fill"></sl-icon>
|
||||
<sl-icon class="only-dark" name="moon-fill"></sl-icon>
|
||||
</button>
|
||||
{# Theme selector #}
|
||||
<sl-dropdown id="theme-selector" placement="bottom-end" distance="3">
|
||||
<sl-button slot="trigger" size="small" variant="text" caret title="Press \ to toggle">
|
||||
<sl-icon class="only-light" name="sun-fill"></sl-icon>
|
||||
<sl-icon class="only-dark" name="moon-fill"></sl-icon>
|
||||
</sl-button>
|
||||
<sl-menu>
|
||||
<sl-menu-item type="checkbox" value="light">Light</sl-menu-item>
|
||||
<sl-menu-item type="checkbox" value="dark">Dark</sl-menu-item>
|
||||
<sl-divider></sl-divider>
|
||||
<sl-menu-item type="checkbox" value="auto">System</sl-menu-item>
|
||||
</sl-menu>
|
||||
</sl-dropdown>
|
||||
</div>
|
||||
|
||||
<aside id="sidebar" data-preserve-scroll>
|
||||
@@ -103,12 +106,12 @@
|
||||
</header>
|
||||
|
||||
<div class="sidebar-buttons">
|
||||
<sl-button size="small" class="repo-button repo-button--sponsor" href="https://github.com/sponsors/claviska" target="_blank">
|
||||
<sl-icon slot="prefix" name="heart"></sl-icon> Sponsor
|
||||
</sl-button>
|
||||
<sl-button size="small" class="repo-button repo-button--github" href="https://github.com/shoelace-style/shoelace" target="_blank">
|
||||
<sl-icon slot="prefix" name="github"></sl-icon> Code
|
||||
</sl-button>
|
||||
<sl-button size="small" class="repo-button repo-button--star" href="https://github.com/shoelace-style/shoelace/stargazers" target="_blank">
|
||||
<sl-icon slot="prefix" name="star-fill"></sl-icon> Star
|
||||
</sl-button>
|
||||
<sl-button size="small" class="repo-button repo-button--twitter" href="https://twitter.com/shoelace_style" target="_blank">
|
||||
<sl-icon slot="prefix" name="twitter"></sl-icon> Follow
|
||||
</sl-button>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
</li>
|
||||
<li>
|
||||
<h2>Design Tokens</h2>
|
||||
<ul>
|
||||
@@ -51,7 +51,7 @@
|
||||
<li><a href="/tokens/border-radius">Border Radius</a></li>
|
||||
<li><a href="/tokens/transition">Transition</a></li>
|
||||
<li><a href="/tokens/z-index">Z-index</a></li>
|
||||
<li><a href="/tokens/more">More</a></li>
|
||||
<li><a href="/tokens/more">More Tokens</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -23,4 +23,4 @@ module.exports = function (content, options) {
|
||||
};
|
||||
|
||||
return format(content, options);
|
||||
}
|
||||
};
|
||||
|
||||
19
docs/_utilities/replacer.cjs
Normal file
19
docs/_utilities/replacer.cjs
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @typedef {object} Replacement
|
||||
* @property {string | RegExp} pattern
|
||||
* @property {string} replacement
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Array<Replacement>} Replacements
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Document} content
|
||||
* @param {Replacements} replacements
|
||||
*/
|
||||
module.exports = function (content, replacements) {
|
||||
replacements.forEach(replacement => {
|
||||
content.body.innerHTML = content.body.innerHTML.replaceAll(replacement.pattern, replacement.replacement);
|
||||
});
|
||||
};
|
||||
11
docs/assets/images/awesome.svg
Normal file
11
docs/assets/images/awesome.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="733" xmlns="http://www.w3.org/2000/svg" height="733">
|
||||
<circle cy="366.5" cx="366.5" r="366.5"/>
|
||||
<circle cy="366.5" cx="366.5" r="336.5" fill="#fede58"/>
|
||||
<path d="M325 665c-121-21-194-115-212-233v-8l-25-1-1-18h481c6 13 10 27 13 41 13 94-38 146-114 193-45 23-93 29-142 26z"/>
|
||||
<path d="M372 647c52-6 98-28 138-62 28-25 46-56 51-87 4-20 1-57-5-70l-423-1c-2 56 39 118 74 157 31 34 72 54 116 63 11 2 38 2 49 0z" fill="#871945"/>
|
||||
<path d="M76 342c-13-26-13-57-9-85 6-27 18-52 35-68 21-20 50-23 77-18 15 4 28 12 39 23 18 17 30 40 36 67 4 20 4 41 0 60l-6 21z"/>
|
||||
<path d="M234 323c5-6 6-40 2-58-3-16-4-16-10-10-14 14-38 14-52 0-15-18-12-41 6-55 3-3 5-5 5-6-1-4-22-8-34-7-42 4-57.6 40-66.2 77-3 17-1 53 4 59H234z" fill="#fff"/>
|
||||
<path d="M378 343c-2-3-6-20-7-29-5-28-1-57 11-83 15-30 41-52 72-60 29-7 57 0 82 15 26 17 45 49 50 82 2 12 2 33 0 45-1 10-5 26-8 30z"/>
|
||||
<path d="M565 324c4-5 5-34 4-50-2-14-6-24-8-24-1 0-3 2-6 5-17 17-47 13-58-9-7-16-4-31 8-43 4-4 7-8 7-9 0 0-4-2-8-3-51-17-105 20-115 80-3 15 0 43 3 53z" fill="#fff"/>
|
||||
<path d="M504 590s-46 40-105 53c-66 15-114-7-114-7s14-76 93-95c76-18 126 49 126 49z" fill="#f9bedd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -48,8 +48,26 @@
|
||||
document.documentElement.classList.toggle('flavor-react', flavor === 'react');
|
||||
}
|
||||
|
||||
function syncFlavor() {
|
||||
setFlavor(getFlavor());
|
||||
|
||||
document.querySelectorAll('.code-preview__button--html').forEach(preview => {
|
||||
if (flavor === 'html') {
|
||||
preview.classList.add('code-preview__button--selected');
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.code-preview__button--react').forEach(preview => {
|
||||
if (flavor === 'react') {
|
||||
preview.classList.add('code-preview__button--selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const shoelaceVersion = document.documentElement.getAttribute('data-shoelace-version');
|
||||
const reactVersion = '18.2.0';
|
||||
const cdndir = 'cdn';
|
||||
const npmdir = 'dist';
|
||||
let flavor = getFlavor();
|
||||
let count = 1;
|
||||
|
||||
@@ -59,19 +77,7 @@
|
||||
}
|
||||
|
||||
// Sync flavor UI on page load
|
||||
setFlavor(getFlavor());
|
||||
|
||||
document.querySelectorAll('.code-preview__button--html').forEach(preview => {
|
||||
if (flavor === 'html') {
|
||||
preview.classList.add('code-preview__button--selected');
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.code-preview__button--react').forEach(preview => {
|
||||
if (flavor === 'react') {
|
||||
preview.classList.add('code-preview__button--selected');
|
||||
}
|
||||
});
|
||||
syncFlavor();
|
||||
|
||||
//
|
||||
// Resizing previews
|
||||
@@ -145,12 +151,8 @@
|
||||
});
|
||||
|
||||
function toggleSource(codeBlock, force) {
|
||||
const toggle = codeBlock.querySelector('.code-preview__toggle');
|
||||
|
||||
if (toggle) {
|
||||
codeBlock.classList.toggle('code-preview--expanded', force === undefined ? undefined : force);
|
||||
event.target.setAttribute('aria-expanded', codeBlock.classList.contains('code-preview--expanded'));
|
||||
}
|
||||
codeBlock.classList.toggle('code-preview--expanded', force);
|
||||
event.target.setAttribute('aria-expanded', codeBlock.classList.contains('code-preview--expanded'));
|
||||
}
|
||||
|
||||
//
|
||||
@@ -180,7 +182,7 @@
|
||||
// HTML templates
|
||||
if (!isReact) {
|
||||
htmlTemplate =
|
||||
`<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/dist/shoelace.js"></script>\n` +
|
||||
`<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/shoelace.js"></script>\n` +
|
||||
`\n${htmlExample}`;
|
||||
jsTemplate = '';
|
||||
}
|
||||
@@ -191,10 +193,10 @@
|
||||
jsTemplate =
|
||||
`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@${shoelaceVersion}/dist/utilities/base-path';\n` +
|
||||
`import { setBasePath } from 'https://cdn.skypack.dev/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/utilities/base-path';\n` +
|
||||
`\n` +
|
||||
`// Set the base path for Shoelace assets\n` +
|
||||
`setBasePath('https://cdn.skypack.dev/@shoelace-style/shoelace@${shoelaceVersion}/dist/')\n` +
|
||||
`setBasePath('https://cdn.skypack.dev/@shoelace-style/shoelace@${shoelaceVersion}/${npmdir}/')\n` +
|
||||
`\n${convertModuleLinks(reactExample)}\n` +
|
||||
`\n` +
|
||||
`ReactDOM.render(<App />, document.getElementById('root'));`;
|
||||
@@ -202,7 +204,7 @@
|
||||
|
||||
// CSS templates
|
||||
cssTemplate =
|
||||
`@import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/dist/themes/${
|
||||
`@import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${shoelaceVersion}/${cdndir}/themes/${
|
||||
isDark ? 'dark' : 'light'
|
||||
}.css';\n` +
|
||||
'\n' +
|
||||
@@ -241,4 +243,7 @@
|
||||
form.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Set the initial flavor
|
||||
window.addEventListener('turbo:load', syncFlavor);
|
||||
})();
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
// Toggle the menu
|
||||
document.addEventListener('click', event => {
|
||||
const menuToggle = event.target.closest('#menu-toggle');
|
||||
console.log(event.target, menuToggle);
|
||||
if (!menuToggle) return;
|
||||
toggleSidebar();
|
||||
});
|
||||
@@ -75,22 +74,56 @@
|
||||
})();
|
||||
|
||||
//
|
||||
// Theme switcher
|
||||
// Theme selector
|
||||
//
|
||||
(() => {
|
||||
function toggleTheme() {
|
||||
const isDark = !document.documentElement.classList.contains('sl-theme-dark');
|
||||
document.documentElement.classList.toggle('sl-theme-dark', isDark);
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
function getTheme() {
|
||||
return localStorage.getItem('theme') || 'auto';
|
||||
}
|
||||
|
||||
// Toggle the theme
|
||||
document.addEventListener('click', event => {
|
||||
const themeToggle = event.target.closest('#theme-toggle');
|
||||
if (!themeToggle) return;
|
||||
toggleTheme();
|
||||
function isDark() {
|
||||
if (theme === 'auto') {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
return theme === 'dark';
|
||||
}
|
||||
|
||||
function setTheme(newTheme) {
|
||||
theme = newTheme;
|
||||
localStorage.setItem('theme', theme);
|
||||
|
||||
// Update the UI
|
||||
updateSelection();
|
||||
|
||||
// Toggle the dark mode class
|
||||
document.documentElement.classList.toggle('sl-theme-dark', isDark());
|
||||
}
|
||||
|
||||
function updateSelection() {
|
||||
const menu = document.querySelector('#theme-selector sl-menu');
|
||||
if (!menu) return;
|
||||
[...menu.querySelectorAll('sl-menu-item')].map(item => (item.checked = item.getAttribute('value') === theme));
|
||||
}
|
||||
|
||||
let theme = getTheme();
|
||||
|
||||
// Selection is not preserved when changing page, so update when opening dropdown
|
||||
document.addEventListener('sl-show', event => {
|
||||
const themeSelector = event.target.closest('#theme-selector');
|
||||
if (!themeSelector) return;
|
||||
updateSelection();
|
||||
});
|
||||
|
||||
// Listen for selections
|
||||
document.addEventListener('sl-select', event => {
|
||||
const menu = event.target.closest('#theme-selector sl-menu');
|
||||
if (!menu) return;
|
||||
setTheme(event.detail.item.value);
|
||||
});
|
||||
|
||||
// Update the theme when the preference changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => setTheme(theme));
|
||||
|
||||
// Toggle with backslash
|
||||
document.addEventListener('keydown', event => {
|
||||
if (
|
||||
@@ -98,9 +131,12 @@
|
||||
!event.composedPath().some(el => ['input', 'textarea'].includes(el?.tagName?.toLowerCase()))
|
||||
) {
|
||||
event.preventDefault();
|
||||
toggleTheme();
|
||||
setTheme(isDark() ? 'light' : 'dark');
|
||||
}
|
||||
});
|
||||
|
||||
// Set the initial theme and sync the UI
|
||||
setTheme(theme);
|
||||
})();
|
||||
|
||||
//
|
||||
@@ -211,7 +247,7 @@
|
||||
}
|
||||
|
||||
function updateActiveLinks() {
|
||||
const links = getLinks()
|
||||
const links = getLinks();
|
||||
// Find the first visible target and activate the respective link
|
||||
links.find(link => {
|
||||
const target = linkTargets.get(link);
|
||||
@@ -226,7 +262,7 @@
|
||||
}
|
||||
|
||||
// Observe link targets
|
||||
function observeLinks () {
|
||||
function observeLinks() {
|
||||
getLinks().forEach(link => {
|
||||
const hash = link.hash.slice(1);
|
||||
const target = hash ? document.querySelector(`.content__body #${hash}`) : null;
|
||||
@@ -238,8 +274,25 @@
|
||||
});
|
||||
}
|
||||
|
||||
observeLinks()
|
||||
observeLinks();
|
||||
|
||||
document.addEventListener("turbo:load", updateActiveLinks)
|
||||
document.addEventListener("turbo:load", observeLinks)
|
||||
document.addEventListener('turbo:load', updateActiveLinks);
|
||||
document.addEventListener('turbo:load', observeLinks);
|
||||
})();
|
||||
|
||||
//
|
||||
// Show custom versions in the sidebar
|
||||
//
|
||||
(() => {
|
||||
function updateVersion() {
|
||||
const el = document.querySelector('.sidebar-version');
|
||||
if (!el) return;
|
||||
|
||||
if (location.hostname === 'next.shoelace.style') el.textContent = 'Next';
|
||||
if (location.hostname === 'localhost') el.textContent = 'Development';
|
||||
}
|
||||
|
||||
updateVersion();
|
||||
|
||||
document.addEventListener('turbo:load', updateVersion);
|
||||
})();
|
||||
|
||||
@@ -284,7 +284,7 @@
|
||||
const a = document.createElement('a');
|
||||
const displayTitle = page.title ?? '';
|
||||
const displayDescription = page.description ?? '';
|
||||
const displayUrl = page.url.replace(/^\//, '');
|
||||
const displayUrl = page.url.replace(/^\//, '').replace(/\/$/, '');
|
||||
let icon = 'file-text';
|
||||
|
||||
a.setAttribute('role', 'option');
|
||||
|
||||
@@ -267,7 +267,7 @@ hr {
|
||||
margin: calc(var(--docs-content-vertical-spacing) * 2) 0;
|
||||
}
|
||||
|
||||
/* Blockquotes */
|
||||
/* Block quotes */
|
||||
blockquote {
|
||||
position: relative;
|
||||
font-family: var(--sl-font-serif);
|
||||
@@ -1054,6 +1054,31 @@ html.sidebar-open #menu-toggle {
|
||||
transition: 250ms scale ease;
|
||||
}
|
||||
|
||||
#theme-selector:not(:defined) {
|
||||
/* Hide when not defined to prevent extra wide icon toolbar while loading */
|
||||
display: none;
|
||||
}
|
||||
#theme-selector sl-menu {
|
||||
/* Set an initial size to prevent width being initally too small when first opening on small screen width */
|
||||
width: 140px;
|
||||
}
|
||||
#theme-selector sl-button {
|
||||
transition: 250ms scale ease;
|
||||
}
|
||||
#theme-selector sl-button::part(base) {
|
||||
color: var(--sl-color-neutral-0);
|
||||
}
|
||||
#theme-selector sl-button::part(label) {
|
||||
display: flex;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
#theme-selector sl-icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.sl-theme-dark #theme-selector sl-button::part(base) {
|
||||
color: var(--sl-color-neutral-1000);
|
||||
}
|
||||
|
||||
.sl-theme-dark #icon-toolbar {
|
||||
background: var(--sl-color-neutral-200);
|
||||
}
|
||||
@@ -1064,7 +1089,8 @@ html.sidebar-open #menu-toggle {
|
||||
}
|
||||
|
||||
#icon-toolbar button:hover,
|
||||
#icon-toolbar a:hover {
|
||||
#icon-toolbar a:hover,
|
||||
#theme-selector sl-button:hover {
|
||||
scale: 1.1;
|
||||
}
|
||||
|
||||
@@ -1084,6 +1110,10 @@ html.sidebar-open #menu-toggle {
|
||||
font-size: 1rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
#theme-selector sl-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sidebar addons */
|
||||
@@ -1173,6 +1203,12 @@ html.sidebar-open #menu-toggle {
|
||||
font-weight: var(--sl-font-weight-normal);
|
||||
}
|
||||
|
||||
.splash li img {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
|
||||
.splash-end {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
@@ -1219,19 +1255,6 @@ html.sidebar-open #menu-toggle {
|
||||
}
|
||||
}
|
||||
|
||||
/* Repo buttons */
|
||||
.repo-button--sponsor sl-icon {
|
||||
color: var(--sl-color-pink-600);
|
||||
}
|
||||
|
||||
.repo-button--github sl-icon {
|
||||
color: var(--sl-color-neutral-700);
|
||||
}
|
||||
|
||||
.repo-button--twitter sl-icon {
|
||||
color: var(--sl-color-sky-500);
|
||||
}
|
||||
|
||||
/* Component headers */
|
||||
.component-header h1 {
|
||||
margin-bottom: 0;
|
||||
@@ -1261,14 +1284,24 @@ html.sidebar-open #menu-toggle {
|
||||
}
|
||||
|
||||
/* Repo buttons */
|
||||
.repo-button--sponsor sl-icon {
|
||||
color: var(--sl-color-pink-600);
|
||||
.sidebar-buttons {
|
||||
display: flex;
|
||||
gap: 0.125rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.sidebar-buttons .repo-button {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.repo-button--github sl-icon {
|
||||
color: var(--sl-color-neutral-700);
|
||||
}
|
||||
|
||||
.repo-button--star sl-icon {
|
||||
color: var(--sl-color-yellow-500);
|
||||
}
|
||||
|
||||
.repo-button--twitter sl-icon {
|
||||
color: var(--sl-color-sky-500);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,11 @@ const tableOfContents = require('./_utilities/table-of-contents.cjs');
|
||||
const prettier = require('./_utilities/prettier.cjs');
|
||||
const scrollingTables = require('./_utilities/scrolling-tables.cjs');
|
||||
const typography = require('./_utilities/typography.cjs');
|
||||
const replacer = require('./_utilities/replacer.cjs');
|
||||
|
||||
const assetsDir = 'assets';
|
||||
const cdndir = 'cdn';
|
||||
const npmdir = 'dist';
|
||||
const allComponents = getAllComponents();
|
||||
let hasBuiltSearchIndex = false;
|
||||
|
||||
@@ -33,7 +36,9 @@ module.exports = function (eleventyConfig) {
|
||||
description: 'A forward-thinking library of web components.',
|
||||
image: 'images/og-image.png',
|
||||
version: customElementsManifest.package.version,
|
||||
components: allComponents
|
||||
components: allComponents,
|
||||
cdndir,
|
||||
npmdir
|
||||
});
|
||||
|
||||
//
|
||||
@@ -129,6 +134,11 @@ module.exports = function (eleventyConfig) {
|
||||
scrollingTables(doc);
|
||||
copyCodeButtons(doc); // must be after codePreviews + highlightCodeBlocks
|
||||
typography(doc, '#content');
|
||||
replacer(doc, [
|
||||
{ pattern: '%VERSION%', replacement: customElementsManifest.package.version },
|
||||
{ pattern: '%CDNDIR%', replacement: cdndir },
|
||||
{ pattern: '%NPMDIR%', replacement: npmdir }
|
||||
]);
|
||||
|
||||
// Serialize the Document object to an HTML string and prepend the doctype
|
||||
content = `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
|
||||
@@ -161,7 +171,10 @@ module.exports = function (eleventyConfig) {
|
||||
this.field('c'); // content
|
||||
|
||||
results.forEach((result, index) => {
|
||||
const url = path.join('/', path.relative(eleventyConfig.dir.output, result.outputPath)).replace(/\\/g, '/');
|
||||
const url = path
|
||||
.join('/', path.relative(eleventyConfig.dir.output, result.outputPath))
|
||||
.replace(/\\/g, '/') // convert backslashes to forward slashes
|
||||
.replace(/\/index.html$/, '/'); // convert trailing /index.html to /
|
||||
const doc = new JSDOM(result.content, {
|
||||
// We must set a default URL so links are parsed with a hostname. Let's use a bogus TLD so we can easily
|
||||
// identify which ones are internal and which ones are external.
|
||||
@@ -208,7 +221,7 @@ module.exports = function (eleventyConfig) {
|
||||
eleventyConfig.setServerOptions({
|
||||
domDiff: false, // disable dom diffing so custom elements don't break on reload,
|
||||
port: 4000, // if port 4000 is taken, 11ty will use the next one available
|
||||
watch: ['dist/**/*'] // additional files to watch that will trigger server updates (array of paths or globs)
|
||||
watch: ['cdn/**/*'] // additional files to watch that will trigger server updates (array of paths or globs)
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
@@ -506,7 +506,7 @@ const App = () => {
|
||||
|
||||
### Multiple Slides Per View
|
||||
|
||||
The `slides-per-view` attribute makes it possible to display multiple slides at a time. You can also use the `slides-per-move` attribute to advance more than once slide at a time, if desired.
|
||||
The `slides-per-page` attribute makes it possible to display multiple slides at a time. You can also use the `slides-per-move` attribute to advance more than once slide at a time, if desired.
|
||||
|
||||
```html:preview
|
||||
<sl-carousel navigation pagination slides-per-page="2" slides-per-move="2">
|
||||
|
||||
@@ -289,7 +289,7 @@ const App = () => (
|
||||
|
||||
### Hoisting
|
||||
|
||||
Dropdown panels will be clipped if they're inside a container that has `overflow: auto|hidden`. The `hoist` attribute forces the panel to use a fixed positioning strategy, allowing it to break out of the container. In this case, the panel will be positioned relative to its containing block, which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||
Dropdown panels will be clipped if they're inside a container that has `overflow: auto|hidden`. The `hoist` attribute forces the panel to use a fixed positioning strategy, allowing it to break out of the container. In this case, the panel will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||
|
||||
```html:preview
|
||||
<div class="dropdown-hoist">
|
||||
|
||||
@@ -634,6 +634,39 @@ This example will load the same set of icons from the jsDelivr CDN instead of yo
|
||||
</script>
|
||||
```
|
||||
|
||||
#### Customize the default library to use SVG sprites
|
||||
|
||||
To improve performance you can use a SVG sprites to avoid multiple trips for each SVG. The browser will load the sprite sheet once and then you reference the particular SVG within the sprite sheet using hash selector.
|
||||
|
||||
As always, make sure to benchmark these changes. When using HTTP/2, it may in fact be more bandwidth-friendly to use multiple small requests instead of 1 large sprite sheet.
|
||||
|
||||
:::danger
|
||||
When using sprite sheets, the `sl-load` and `sl-error` events will not fire.
|
||||
:::
|
||||
|
||||
:::danger
|
||||
For security reasons, browsers may apply the same-origin policy on `<use>` elements located in the `<sl-icon>` shadow dom and
|
||||
may refuse to load a cross-origin URL. There is currently no defined way to set a cross-origin policy for `<use>` elements.
|
||||
For this reason, sprite sheets should only be used if you're self-hosting them.
|
||||
:::
|
||||
|
||||
```html:preview
|
||||
<script type="module">
|
||||
import { registerIconLibrary } from '/dist/utilities/icon-library.js';
|
||||
|
||||
registerIconLibrary('sprite', {
|
||||
resolver: name => `/assets/images/sprite.svg#${name}`,
|
||||
mutator: svg => svg.setAttribute('fill', 'currentColor'),
|
||||
spriteSheet: true
|
||||
});
|
||||
</script>
|
||||
|
||||
<div style="font-size: 24px;">
|
||||
<sl-icon library="sprite" name="clock"></sl-icon>
|
||||
<sl-icon library="sprite" name="speedometer"></sl-icon>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Customizing the System Library
|
||||
|
||||
The system library contains only the icons used internally by Shoelace components. Unlike the default icon library, the system library does not rely on physical assets. Instead, its icons are hard-coded as data URIs into the resolver to ensure their availability.
|
||||
@@ -666,7 +699,7 @@ If you want to change the icons Shoelace uses internally, you can register an ic
|
||||
}
|
||||
|
||||
fetch('/dist/assets/icons/icons.json')
|
||||
.then(res => res.json())
|
||||
.then(res => res.json())
|
||||
.then(icons => {
|
||||
const container = document.querySelector('.icon-search');
|
||||
const input = container.querySelector('sl-input');
|
||||
@@ -685,12 +718,12 @@ If you want to change the icons Shoelace uses internally, you can register an ic
|
||||
item.setAttribute('data-terms', [i.name, i.title, ...(i.tags || []), ...(i.categories || [])].join(' '));
|
||||
item.innerHTML = `
|
||||
<svg width="1em" height="1em" fill="currentColor">
|
||||
<use xlink:href="/assets/images/sprite.svg#${i.name}"></use>
|
||||
</svg>
|
||||
<use href="/assets/images/sprite.svg#${i.name}"></use>
|
||||
</svg>
|
||||
`;
|
||||
list.appendChild(item);
|
||||
|
||||
// Wrap it with a tooltip the first time the mouse lands on it. We do this instead of baking them into the DOM
|
||||
// Wrap it with a tooltip the first time the mouse lands on it. We do this instead of baking them into the DOM
|
||||
// to improve this page's performance. See: https://github.com/shoelace-style/shoelace/issues/1122
|
||||
item.addEventListener('mouseover', () => wrapWithTooltip(item), { once: true });
|
||||
|
||||
@@ -833,6 +866,6 @@ If you want to change the icons Shoelace uses internally, you can register an ic
|
||||
@media screen and (max-width: 500px) {
|
||||
.icon-list {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -984,7 +984,7 @@ const App = () => {
|
||||
|
||||
By default, the popup is positioned using an absolute positioning strategy. However, if your anchor is fixed or exists within a container that has `overflow: auto|hidden`, the popup risks being clipped. To work around this, you can use a fixed positioning strategy by setting the `strategy` attribute to `fixed`.
|
||||
|
||||
The fixed positioning strategy reduces jumpiness when the anchor is fixed and allows the popup to break out containers that clip. When using this strategy, it's important to note that the content will be positioned _relative to its containing block_, which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||
The fixed positioning strategy reduces jumpiness when the anchor is fixed and allows the popup to break out containers that clip. When using this strategy, it's important to note that the content will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||
|
||||
In this example, you can see how the popup breaks out of the overflow container when it's fixed. The fixed positioning strategy tends to be less performant than absolute, so avoid using it unnecessarily.
|
||||
|
||||
|
||||
@@ -378,7 +378,7 @@ const App = () => (
|
||||
|
||||
### Hoisting
|
||||
|
||||
Tooltips will be clipped if they're inside a container that has `overflow: auto|hidden|scroll`. The `hoist` attribute forces the tooltip to use a fixed positioning strategy, allowing it to break out of the container. In this case, the tooltip will be positioned relative to its containing block, which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||
Tooltips will be clipped if they're inside a container that has `overflow: auto|hidden|scroll`. The `hoist` attribute forces the tooltip to use a fixed positioning strategy, allowing it to break out of the container. In this case, the tooltip will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||
|
||||
```html:preview
|
||||
<div class="tooltip-hoist">
|
||||
|
||||
@@ -7,7 +7,7 @@ layout: component
|
||||
|
||||
According to [The A11Y Project](https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/), "there are real world situations where visually hiding content may be appropriate, while the content should remain available to assistive technologies, such as screen readers. For instance, hiding a search field's label as a common magnifying glass icon is used in its stead."
|
||||
|
||||
Since visually hidden content can receive focus when tabbing, the element will become visible when something inside receives focus. This behavior is intentional, as sighted keyboards user won't be able to determine where the focus indicator is without it.
|
||||
Since visually hidden content can receive focus when tabbing, the element will become visible when something inside receives focus. This behavior is intentional, as sighted keyboard user won't be able to determine where the focus indicator is without it.
|
||||
|
||||
```html:preview
|
||||
<div style="min-height: 1.875rem;">
|
||||
|
||||
@@ -19,14 +19,14 @@ npm install @shoelace-style/shoelace
|
||||
Next, [include a theme](/getting-started/themes) and set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets. In this example, we'll import the light theme and use the CDN as a base path.
|
||||
|
||||
```jsx
|
||||
import '@shoelace-style/shoelace/dist/themes/light.css';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';
|
||||
import '@shoelace-style/shoelace/%NPMDIR%/themes/light.css';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path';
|
||||
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/');
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/');
|
||||
```
|
||||
|
||||
:::tip
|
||||
If you'd rather not use the CDN for assets, you can create a build task that copies `node_modules/@shoelace-style/shoelace/dist/assets` into a public folder in your app. Then you can point the base path to that folder instead.
|
||||
If you'd rather not use the CDN for assets, you can create a build task that copies `node_modules/@shoelace-style/shoelace/%NPMDIR%/assets` into a public folder in your app. Then you can point the base path to that folder instead.
|
||||
:::
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -20,14 +20,14 @@ Next, [include a theme](/getting-started/themes) and set the [base path](/gettin
|
||||
|
||||
```jsx
|
||||
// App.jsx
|
||||
import '@shoelace-style/shoelace/dist/themes/light.css';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';
|
||||
import '@shoelace-style/shoelace/%NPMDIR%/themes/light.css';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path';
|
||||
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/');
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/');
|
||||
```
|
||||
|
||||
:::tip
|
||||
If you'd rather not use the CDN for assets, you can create a [build task](https://webpack.js.org/plugins/copy-webpack-plugin/) that copies `node_modules/@shoelace-style/shoelace/dist/assets` into your app's `public` directory. Then you can point the base path to that folder instead.
|
||||
If you'd rather not use the CDN for assets, you can create a [build task](https://webpack.js.org/plugins/copy-webpack-plugin/) that copies `node_modules/@shoelace-style/shoelace/%NPMDIR%/assets` into your app's `public` directory. Then you can point the base path to that folder instead.
|
||||
:::
|
||||
|
||||
Now you can start using components!
|
||||
@@ -39,7 +39,7 @@ Now you can start using components!
|
||||
Every Shoelace component is available to import as a React component. Note that we're importing the `<SlButton>` _React component_ instead of the `<sl-button>` _custom element_ in the example below.
|
||||
|
||||
```jsx
|
||||
import { SlButton } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlButton } from '@shoelace-style/shoelace/%NPMDIR%/react';
|
||||
|
||||
const MyComponent = () => <SlButton variant="primary">Click me</SlButton>;
|
||||
|
||||
@@ -56,7 +56,7 @@ Here's how you can bind the input's value to a state variable.
|
||||
|
||||
```jsx
|
||||
import { useState } from 'react';
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
import { SlInput } from '@shoelace-style/shoelace/%NPMDIR%/react';
|
||||
|
||||
function MyComponent() {
|
||||
const [value, setValue] = useState('');
|
||||
@@ -71,8 +71,8 @@ If you're using TypeScript, it's important to note that `event.target` will be a
|
||||
|
||||
```tsx
|
||||
import { useState } from 'react';
|
||||
import { SlInput } from '@shoelace-style/shoelace/dist/react';
|
||||
import type SlInputElement from '@shoelace-style/shoelace/dist/components/input/input';
|
||||
import { SlInput } from '@shoelace-style/shoelace/%NPMDIR%/react';
|
||||
import type SlInputElement from '@shoelace-style/shoelace/%NPMDIR%/components/input/input';
|
||||
|
||||
function MyComponent() {
|
||||
const [value, setValue] = useState('');
|
||||
@@ -83,6 +83,25 @@ function MyComponent() {
|
||||
export default MyComponent;
|
||||
```
|
||||
|
||||
You can also import the event type for use in your callbacks, shown below.
|
||||
|
||||
```tsx
|
||||
import { useCallback, useState } from 'react';
|
||||
import { SlInput, SlInputEvent } from '@shoelace-style/shoelace/%NPMDIR%/react';
|
||||
import type SlInputElement from '@shoelace-style/shoelace/%NPMDIR%/components/input/input';
|
||||
|
||||
function MyComponent() {
|
||||
const [value, setValue] = useState('');
|
||||
const onInput = useCallback((event: SlInputEvent) => {
|
||||
setValue(event.detail);
|
||||
}, []);
|
||||
|
||||
return <SlInput value={value} onSlInput={event => setValue((event.target as SlInputElement).value)} />;
|
||||
}
|
||||
|
||||
export default MyComponent;
|
||||
```
|
||||
|
||||
## Testing with Jest
|
||||
|
||||
Testing with web components can be challenging if your test environment runs in a Node environment (i.e. it doesn't run in a real browser). Fortunately, [Jest](https://jestjs.io/) has made a number of strides to support web components and provide additional browser APIs. However, it's still not a complete replication of a browser environment.
|
||||
|
||||
@@ -23,10 +23,10 @@ npm install @shoelace-style/shoelace
|
||||
Next, [include a theme](/getting-started/themes) and set the [base path](/getting-started/installation#setting-the-base-path) for icons and other assets. In this example, we'll import the light theme and use the CDN as a base path.
|
||||
|
||||
```jsx
|
||||
import '@shoelace-style/shoelace/dist/themes/light.css';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';
|
||||
import '@shoelace-style/shoelace/%NPMDIR%/themes/light.css';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path';
|
||||
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/');
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/');
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
@@ -26,7 +26,7 @@ Next, [include a theme](/getting-started/themes) and set the [base path](/gettin
|
||||
import '@shoelace-style/shoelace/dist/themes/light.css';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path';
|
||||
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/');
|
||||
setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/');
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
@@ -6,13 +6,13 @@ meta:
|
||||
|
||||
# Customizing
|
||||
|
||||
Shoelace components can be customized at a high level through design tokens. This gives you control over theme colors and general styling. For more advanced customizations, you can make use of component parts and custom properties to target individual components.
|
||||
Shoelace components can be customized at a high level through design tokens. This gives you control over theme colors and general styling. For more advanced customizations, you can make use of CSS parts and custom properties to target individual components.
|
||||
|
||||
## Design Tokens
|
||||
|
||||
Shoelace makes use of several design tokens to provide a consistent appearance across components. You can customize them and use them in your own application with pure CSS — no preprocessor required.
|
||||
|
||||
Design tokens offer a high-level way to customize the library with minimal effort. There are no component-specific variables, however, as design tokens are intended to be generic and highly reusable. To customize an individual component, refer to the section entitled [Component Parts](#component-parts).
|
||||
Design tokens offer a high-level way to customize the library with minimal effort. There are no component-specific variables, however, as design tokens are intended to be generic and highly reusable. To customize an individual component, refer to the section entitled [CSS Parts](#css-parts).
|
||||
|
||||
Design tokens are accessed through CSS custom properties that are defined in your theme. Because design tokens live at the page level, they're prefixed with `--sl-` to avoid collisions with other libraries.
|
||||
|
||||
@@ -37,9 +37,9 @@ To customize a design token, simply override it in your stylesheet using a `:roo
|
||||
|
||||
Many design tokens are described further along in this documentation. For a complete list, refer to `src/themes/light.css` in the project's [source code](https://github.com/shoelace-style/shoelace/blob/current/src/themes/light.css).
|
||||
|
||||
## Component Parts
|
||||
## CSS Parts
|
||||
|
||||
Whereas design tokens offer a high-level way to customize the library, component parts offer a low-level way to customize individual components. Again, this is done with pure CSS — no preprocessor required.
|
||||
Whereas design tokens offer a high-level way to customize the library, CSS parts offer a low-level way to customize individual components. Again, this is done with pure CSS — no preprocessor required.
|
||||
|
||||
Shoelace components use a [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) to encapsulate their styles and behaviors. As a result, you can't simply target their internals with the usual CSS selectors. Instead, components expose "parts" that can be targeted with the [CSS part selector](https://developer.mozilla.org/en-US/docs/Web/CSS/::part), or `::part()`.
|
||||
|
||||
@@ -76,7 +76,7 @@ At first glance, this approach might seem a bit verbose or even limiting, but it
|
||||
|
||||
- Customizations can be made to components with explicit selectors, such as `::part(icon)`, rather than implicit selectors, such as `.button > div > span + .icon`, that are much more fragile.
|
||||
|
||||
- The internal structure of a component will likely change as it evolves. By exposing component parts through an API, the internals can be reworked without fear of breaking customizations as long as its parts remain intact.
|
||||
- The internal structure of a component will likely change as it evolves. By exposing CSS parts through an API, the internals can be reworked without fear of breaking customizations as long as its parts remain intact.
|
||||
|
||||
- It encourages us to think more about how components are designed and how customizations should be allowed before users can take advantage of them. Once we opt a part into the component's API, it's guaranteed to be supported and can't be removed until a major version of the library is released.
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ While convenient, autoloading may lead to a [Flash of Undefined Custom Elements]
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/light.css" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/shoelace-autoloader.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/light.css" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/shoelace-autoloader.js"></script>
|
||||
```
|
||||
|
||||
</sl-tab-panel>
|
||||
@@ -32,9 +32,10 @@ While convenient, autoloading may lead to a [Flash of Undefined Custom Elements]
|
||||
|
||||
The traditional CDN loader registers all Shoelace elements up front. Note that, if you're only using a handful of components, it will be much more efficient to stick with the autoloader. However, you can also [cherry pick](#cherry-picking) components if you want to load specific ones up front.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/light.css" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/shoelace.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/light.css" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/shoelace.js" ></script>
|
||||
```
|
||||
|
||||
</sl-tab-panel>
|
||||
@@ -44,8 +45,9 @@ The traditional CDN loader registers all Shoelace elements up front. Note that,
|
||||
|
||||
The code above will load the light theme. If you want to use the [dark theme](/getting-started/themes#dark-theme) instead, update the stylesheet as shown below and add `<html class="sl-theme-dark">` to your page.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/dark.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/dark.css" />
|
||||
```
|
||||
|
||||
### Light & Dark Theme
|
||||
@@ -56,21 +58,21 @@ If you want to load the light or dark theme based on the user's `prefers-color-s
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="(prefers-color-scheme:light)"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/light.css"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/light.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="(prefers-color-scheme:dark)"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/dark.css"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/dark.css"
|
||||
onload="document.documentElement.classList.add('sl-theme-dark');"
|
||||
/>
|
||||
```
|
||||
|
||||
Now you can [start using Shoelace!](/getting-started/usage)
|
||||
|
||||
## Local Installation
|
||||
## npm installation
|
||||
|
||||
If you don't want to use the CDN, you can install Shoelace locally with the following command.
|
||||
If you don't want to use the CDN, you can install Shoelace from npm with the following command.
|
||||
|
||||
```bash
|
||||
npm install @shoelace-style/shoelace
|
||||
@@ -81,8 +83,8 @@ It's up to you to make the source files available to your app. One way to do thi
|
||||
Once you've done that, add the following tags to your page. Make sure to update `href` and `src` so they point to the route you created.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/shoelace/dist/themes/light.css" />
|
||||
<script type="module" src="/shoelace/dist/shoelace.js"></script>
|
||||
<link rel="stylesheet" href="/shoelace/%NPMDIR%/themes/light.css" />
|
||||
<script type="module" src="/shoelace/%NPMDIR%/shoelace.js"></script>
|
||||
```
|
||||
|
||||
Alternatively, [you can use a bundler](#bundling).
|
||||
@@ -99,31 +101,37 @@ However, if you're [cherry picking](#cherry-picking) or [bundling](#bundling) Sh
|
||||
|
||||
```html
|
||||
<!-- Option 1: the data-shoelace attribute -->
|
||||
<script src="bundle.js" data-shoelace="/path/to/shoelace/dist"></script>
|
||||
<script src="bundle.js" data-shoelace="/path/to/shoelace/%NPMDIR%"></script>
|
||||
|
||||
<!-- Option 2: the setBasePath() method -->
|
||||
<script src="bundle.js"></script>
|
||||
<script type="module">
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';
|
||||
setBasePath('/path/to/shoelace/dist');
|
||||
import { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path.js';
|
||||
setBasePath('/path/to/shoelace/%NPMDIR%');
|
||||
</script>
|
||||
```
|
||||
|
||||
:::tip
|
||||
The library also exports a `getBasePath()` method you can use to reference assets.
|
||||
When setting a basePath, and easy way to check if it was down properly is by checking if an icon exists.
|
||||
|
||||
For example, if I set the basePath to `/dist`, I should be able to go to:
|
||||
|
||||
`https://<my-site>/dist/assets/icons/arrow-left.svg` and the browser should show me the SVG.
|
||||
|
||||
Shoelace also exports a `getBasePath()` method you can use to reference assets.
|
||||
:::
|
||||
|
||||
## Cherry Picking
|
||||
|
||||
Cherry picking can be done from [the CDN](#cdn-installation-easiest) or your [local installation](#local-installation). This approach will load only the components you need up front, while limiting the number of files the browser has to download. The disadvantage is that you need to import each individual component.
|
||||
Cherry picking can be done from [the CDN](#cdn-installation-easiest) or from [npm](#npm-installation). This approach will load only the components you need up front, while limiting the number of files the browser has to download. The disadvantage is that you need to import each individual component.
|
||||
|
||||
Here's an example that loads only the button component. Again, if you're not using a module resolver, you'll need to adjust the path to point to the folder Shoelace is in.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="/path/to/shoelace/dist/themes/light.css" />
|
||||
<link rel="stylesheet" href="/path/to/shoelace/%NPMDIR%/themes/light.css" />
|
||||
|
||||
<script type="module" data-shoelace="/path/to/shoelace/dist">
|
||||
import '@shoelace-style/shoelace/dist/components/button/button.js';
|
||||
<script type="module" data-shoelace="/path/to/shoelace/%NPMDIR%">
|
||||
import '@shoelace-style/shoelace/%NPMDIR%/components/button/button.js';
|
||||
|
||||
// <sl-button> is ready to use!
|
||||
</script>
|
||||
@@ -157,15 +165,15 @@ Now it's time to configure your bundler. Configurations vary for each tool, but
|
||||
Once your bundler is configured, you'll be able to import Shoelace components and utilities.
|
||||
|
||||
```js
|
||||
import '@shoelace-style/shoelace/dist/themes/light.css';
|
||||
import '@shoelace-style/shoelace/dist/components/button/button.js';
|
||||
import '@shoelace-style/shoelace/dist/components/icon/icon.js';
|
||||
import '@shoelace-style/shoelace/dist/components/input/input.js';
|
||||
import '@shoelace-style/shoelace/dist/components/rating/rating.js';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/dist/utilities/base-path.js';
|
||||
import '@shoelace-style/shoelace/%NPMDIR%/themes/light.css';
|
||||
import '@shoelace-style/shoelace/%NPMDIR%/components/button/button.js';
|
||||
import '@shoelace-style/shoelace/%NPMDIR%/components/icon/icon.js';
|
||||
import '@shoelace-style/shoelace/%NPMDIR%/components/input/input.js';
|
||||
import '@shoelace-style/shoelace/%NPMDIR%/components/rating/rating.js';
|
||||
import { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path.js';
|
||||
|
||||
// Set the base path to the folder you copied Shoelace's assets to
|
||||
setBasePath('/path/to/shoelace/dist');
|
||||
setBasePath('/path/to/shoelace/%NPMDIR%
|
||||
|
||||
// <sl-button>, <sl-icon>, <sl-input>, and <sl-rating> are ready to use!
|
||||
```
|
||||
@@ -173,3 +181,14 @@ setBasePath('/path/to/shoelace/dist');
|
||||
:::warning
|
||||
Component modules include side effects for registration purposes. Because of this, importing directly from `@shoelace-style/shoelace` may result in a larger bundle size than necessary. For optimal tree shaking, always cherry pick, i.e. import components and utilities from their respective files, as shown above.
|
||||
:::
|
||||
|
||||
## The difference between CDN and npm
|
||||
|
||||
You'll notice that the CDN links all start with `/%CDNDIR%/<path>` and npm imports use `/%NPMDIR%/<path>`. The `/%CDNDIR%` files are bundled separately from the `/%NPMDIR%` files. The `/%CDNDIR%` files come pre-bundled, which means all dependencies are inlined so you do not need to worry about loading additional libraries. The `/%NPMDIR%` files **DO NOT** come pre-bundled, allowing your bundler of choice to more efficiently deduplicate dependencies, resulting in smaller bundles and optimal code sharing.
|
||||
|
||||
TL;DR:
|
||||
|
||||
- `@shoelace-style/shoelace/%CDNDIR%` is for CDN users
|
||||
- `@shoelace-style/shoelace/%NPMDIR%` is for npm users
|
||||
|
||||
This change was introduced in `v2.5.0` to address issues around installations from npm loading multiple versions of libraries (such as the Lit) that Shoelace uses internally.
|
||||
|
||||
@@ -28,8 +28,8 @@ Shoelace ships with a number of translations. The default is English (US), which
|
||||
|
||||
The location of translations depends on how you're consuming Shoelace.
|
||||
|
||||
- If you're using the CDN, [import them from the CDN](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace?path=dist%2Ftranslations)
|
||||
- If you're using a bundler, import them from `@shoelace-style/shoelace/dist/translations/[lang].js`
|
||||
- If you're using the CDN, [import them from the CDN](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace?path=%CDNDIR%%2Ftranslations)
|
||||
- If you're using a bundler, import them from `@shoelace-style/shoelace/%NPMDIR%/translations/[lang].js`
|
||||
|
||||
You do not need to load translations up front. You can import them dynamically even after updating the `lang` attribute. Once a translation is registered, localized components will update automatically.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Shoelace is designed to be highly customizable through pure CSS. Out of the box,
|
||||
A theme is nothing more than a stylesheet that uses the Shoelace API to define design tokens and apply custom styles to components. To create a theme, you will need a decent understanding of CSS, including [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*) and the [`::part` selector](https://developer.mozilla.org/en-US/docs/Web/CSS/::part).
|
||||
|
||||
:::tip
|
||||
For component developers, built-in themes are also available as JavaScript modules that export [Lit CSSResult](https://lit.dev/docs/api/styles/#CSSResult) objects. You can find them in `dist/themes/*.styles.js`.
|
||||
For component developers, built-in themes are also available as JavaScript modules that export [Lit CSSResult](https://lit.dev/docs/api/styles/#CSSResult) objects. You can find them in `%NPMDIR%/themes/*.styles.js`.
|
||||
:::
|
||||
|
||||
## Theme Basics
|
||||
@@ -34,7 +34,7 @@ To activate a theme, import it and apply the theme's class to the `<html>` eleme
|
||||
```html
|
||||
<html class="sl-theme-dark">
|
||||
<head>
|
||||
<link rel="stylesheet" href="path/to/shoelace/dist/themes/dark.css" />
|
||||
<link rel="stylesheet" href="path/to/shoelace/%NPMDIR%/themes/dark.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -54,8 +54,8 @@ You can activate themes on various containers throughout the page. This example
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="path/to/shoelace/dist/themes/light.css" />
|
||||
<link rel="stylesheet" href="path/to/shoelace/dist/themes/dark.css" />
|
||||
<link rel="stylesheet" href="path/to/shoelace/%NPMDIR%/themes/light.css" />
|
||||
<link rel="stylesheet" href="path/to/shoelace/%NPMDIR%/themes/dark.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -127,7 +127,10 @@ The dark theme works by taking the light theme's [color tokens](/tokens/color) a
|
||||
To install the dark theme, add the following to the `<head>` section of your page.
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/dark.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/dark.css"
|
||||
/>
|
||||
```
|
||||
|
||||
To activate the theme, apply the `sl-theme-dark` class to the `<html>` element.
|
||||
|
||||
@@ -108,9 +108,9 @@ Custom elements cannot have self-closing tags. Similar to `<script>` and `<texta
|
||||
|
||||
## Differences from Native Elements
|
||||
|
||||
You might expect similarly named elements to share the same API as native HTML elements. This is not always the case. Shoelace components **are not** designed to be one-to-one replacements for their HTML counterparts.
|
||||
You might expect similarly named elements to share the same API as native HTML elements, but this is not always the case. Shoelace components **are not** designed to be one-to-one replacements for their HTML counterparts. While they usually share the same API, there may be subtle differences.
|
||||
|
||||
For example, `<button>` and `<sl-button>` both have a `type` attribute, but it does different things. The former controls whether the button submits a form and the latter controls the button's appearance.
|
||||
For example, `<button>` and `<sl-button>` both have a `type` attribute, but the native one defaults to `submit` while the Shoelace one defaults to `button` since this is a better default for most users.
|
||||
|
||||
:::tip
|
||||
**Don't make assumptions about a component's API!** To prevent unexpected behaviors, please take the time to review the documentation and make sure you understand what each attribute, property, method, and event is intended to do.
|
||||
|
||||
@@ -19,8 +19,7 @@ toc: false
|
||||
- First-class [React support](/frameworks/react) ⚛️
|
||||
- Built-in localization 💬
|
||||
- Open source 😸
|
||||
|
||||
Designed in New Hampshire by [Cory LaViska](https://twitter.com/claviska).
|
||||
- [More awesome than ever](https://blog.fontawesome.com/shoelace-joins-font-awesome/) 
|
||||
|
||||
</div>
|
||||
<div class="splash-end">
|
||||
@@ -41,8 +40,8 @@ Add the following code to your page.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/themes/light.css" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/dist/shoelace-autoloader.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/light.css" />
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/shoelace-autoloader.js"></script>
|
||||
```
|
||||
|
||||
Now you have access to all of Shoelace's components! Try adding a button:
|
||||
@@ -105,23 +104,7 @@ If you need to support IE11 or pre-Chromium Edge, this library isn't for you. Al
|
||||
|
||||
## License
|
||||
|
||||
Shoelace is designed in New Hampshire by [Cory LaViska](https://twitter.com/claviska). It's available under the terms of the MIT license.
|
||||
|
||||
Designing, developing, and supporting this library requires a lot of time, effort, and skill. If you're using this software to make a profit, I respectfully ask that you help [fund its development](https://github.com/sponsors/claviska) by becoming a sponsor.
|
||||
|
||||
👇 Your support is very much appreciated! 👇
|
||||
|
||||
<sl-button class="repo-button repo-button--sponsor" href="https://github.com/sponsors/claviska" target="_blank">
|
||||
<sl-icon slot="prefix" name="heart"></sl-icon> Become a sponsor
|
||||
</sl-button>
|
||||
|
||||
<sl-button class="repo-button repo-button--github" href="https://github.com/shoelace-style/shoelace/stargazers" target="_blank">
|
||||
<sl-icon slot="prefix" name="github"></sl-icon> Star
|
||||
</sl-button>
|
||||
|
||||
<sl-button class="repo-button repo-button--twitter" href="https://twitter.com/shoelace_style" target="_blank">
|
||||
<sl-icon slot="prefix" name="twitter"></sl-icon> Follow
|
||||
</sl-button>
|
||||
Shoelace was created in New Hampshire by [Cory LaViska](https://twitter.com/claviska). It's available under the terms of the [MIT license](https://github.com/shoelace-style/shoelace/blob/next/LICENSE.md).
|
||||
|
||||
## Attribution
|
||||
|
||||
|
||||
@@ -14,18 +14,60 @@ New versions of Shoelace are released as-needed and generally occur when a criti
|
||||
|
||||
## Next
|
||||
|
||||
- Added the `checkbox` part and related exported parts to `<sl-tree-item>` so you can target it with CSS [#1318](https://github.com/shoelace-style/shoelace/discussions/1318)
|
||||
- Added tests for `<sl-qr-code>` [#1416]
|
||||
- Added support for pressing [[Space]] to select/toggle selected `<sl-menu-item>` elements [#1429]
|
||||
- Fixed a bug in focus trapping of modal elements like `<sl-dialog>`. We now manually handle focus ordering as well as added `offsetParent()` check for tabbable boundaries in Safari. Test cases added for `<sl-dialog>` inside a shadowRoot [#1403]
|
||||
- Fixed a bug in `valueAsDate` on `<sl-input>` where it would always set `type="date"` for the underlying `<input>` element. It now falls back to the native browser implementation for the in-memory input. This may cause unexpected behavior if you're using `valueAsDate` on any input elements that aren't `type="date"`. [#1399]
|
||||
- Fixed a bug in `<sl-qr-code>` where the `background` attribute was never passed to the QR code [#1416]
|
||||
- Fixed a bug in `<sl-dropdown>` where aria attributes were incorrectly applied to the default `<slot>` causing Lighthouse errors [#1417]
|
||||
- Fixed a bug in `<sl-carousel>` that caused navigation to work incorrectly in some case [#1420]
|
||||
- Fixed a number of slots that incorrectly had aria- and/or role attributes directly on them [#1422]
|
||||
- Fixed a bug in `<sl-tree>` that caused focus to be stolen when removing focused tree items [#1430]
|
||||
- Updated ESLint and related plugins to the latest versions
|
||||
|
||||
## 2.5.2
|
||||
|
||||
- Fixed broken source buttons in the docs [#1401]
|
||||
|
||||
## 2.5.1
|
||||
|
||||
- Fixed missing extensions from imports that broke with TypeScript 5 [#1391]
|
||||
- Fixed a regression that caused slotted styles to not work in `<sl-select>` [#1387]
|
||||
- Reimplemented the theme switcher so it supports light, dark, and system (auto) in the docs [#1395]
|
||||
|
||||
## 2.5.0
|
||||
|
||||
This release [unbundles Lit](https://github.com/shoelace-style/shoelace/issues/559) (and other dependencies) from Shoelace. There are now two distributions for the project:
|
||||
|
||||
1. `cdn/` – a bundled, CDN-ready distribution
|
||||
2. `dist/` – an unbundled, npm-ready distribution
|
||||
|
||||
:::warning
|
||||
If you're a CDN user, you must update your path to point to `cdn/` instead of `dist/`. You can copy and paste the latest paths from the [installation page](/getting-started/installation).
|
||||
:::
|
||||
|
||||
- Added a `cdn/` distribution for bundled dependencies (imports for npm users remain the same) [#1369]
|
||||
- Added the `checkbox` part and related exported parts to `<sl-tree-item>` so you can target it with CSS [#1318]
|
||||
- Added the `submenu-icon` part to `<sl-menu-item>` (submenus have not been implemented yet, but this part is required to allow customizations)
|
||||
- Added tests for `<sl-split-panel>` [#1343](https://github.com/shoelace-style/shoelace/pull/1343)
|
||||
- Added the ability to use Sprite Sheets when using `<sl-icon>` via a custom resolver.
|
||||
- Added tests for `<sl-split-panel>` [#1343]
|
||||
- Fixed a bug where changing the size of `<sl-radio-group>` wouldn't update the size of child elements
|
||||
- Fixed a bug in `<sl-select>` and `<sl-color-picker>` where the `size` attribute wasn't being reflected [#1318](https://github.com/shoelace-style/shoelace/issues/1348)
|
||||
- Improved `<sl-button>` so it can accept children of variable heights [#1317](https://github.com/shoelace-style/shoelace/pull/1317)
|
||||
- Fixed a bug in `<sl-select>` and `<sl-color-picker>` where the `size` attribute wasn't being reflected [#1318]
|
||||
- Fixed a bug in `<sl-radio-group>` where `<sl-radio>` would not get checked if `<sl-radio-group>` was defined first. [#1364]
|
||||
- Fixed a bug in `<sl-input>` that caused date pickers to look filled in even when empty in Safari [#1341]
|
||||
- Fixed a bug in `<sl-radio-group>` that sometimes caused dual scrollbars in containers that overflowed [#1380]
|
||||
- Fixed a bug in `<sl-carousel>` not loading the English language pack automatically. [#1384]
|
||||
- Improved `<sl-button>` so it can accept children of variable heights [#1317]
|
||||
- Improved the docs to more clearly explain sizing radios and radio buttons
|
||||
- Improved the performance of `<sl-rating>` by partially rendering unseen icons [#1310](https://github.com/shoelace-style/shoelace/pull/1310)
|
||||
- Improved the Portuguese translation [#1336](https://github.com/shoelace-style/shoelace/pull/1336)
|
||||
- Improved the German translation [#1339](https://github.com/shoelace-style/shoelace/pull/1339)
|
||||
- Improved the autoloader so it watches `<html>` instead of `<body>` since the latter gets replaced by some frameworks [#1338](https://github.com/shoelace-style/shoelace/pull/1338)
|
||||
- Improved the Rails documentation [#1258](https://github.com/shoelace-style/shoelace/pull/1258)
|
||||
- Improved the performance of `<sl-rating>` by partially rendering unseen icons [#1310]
|
||||
- Improved the Portuguese translation [#1336]
|
||||
- Improved the German translation [#1339]
|
||||
- Improved the autoloader so it watches `<html>` instead of `<body>` since the latter gets replaced by some frameworks [#1338]
|
||||
- Improved the Rails documentation [#1258]
|
||||
- Replaced Docsify with Eleventy to generate a static HTML version of the docs
|
||||
- Updated esbuild to 0.18.2
|
||||
- Updated Lit to 2.7.5
|
||||
- Updated TypeScript to 5.1.3
|
||||
|
||||
## 2.4.0
|
||||
|
||||
@@ -1111,12 +1153,12 @@ The most elegant solution I found was to use the [Web Animations API](https://de
|
||||
|
||||
## 2.0.0-beta.34
|
||||
|
||||
This release changes the way components are registered if you're [cherry picking](/getting-started/installation?id=cherry-picking) or [using a bundler](/getting-started/installation?id=bundling). This recommendation came from the LitElement team and simplifies Shoelace's dependency graph. It also eliminates the need to call a `register()` function before using each component.
|
||||
This release changes the way components are registered if you're [cherry picking](/getting-started/installation#cherry-picking) or [using a bundler](/getting-started/installation#bundling). This recommendation came from the LitElement team and simplifies Shoelace's dependency graph. It also eliminates the need to call a `register()` function before using each component.
|
||||
|
||||
From now on, importing a component will register it automatically. The caveat is that bundlers may not tree shake the library properly if you import from `@shoelace-style/shoelace`, so the recommendation is to import components and utilities from their corresponding files instead.
|
||||
|
||||
- 🚨 BREAKING: removed `all.shoelace.js` (use `shoelace.js` instead)
|
||||
- 🚨 BREAKING: component modules now have a side effect, so bundlers may not tree shake properly when importing from `@shoelace-style/shoelace` (see the [installation page](/getting-started/installation?id=bundling) for more details and how to update)
|
||||
- 🚨 BREAKING: component modules now have a side effect, so bundlers may not tree shake properly when importing from `@shoelace-style/shoelace` (see the [installation page](/getting-started/installation#bundling) for more details and how to update)
|
||||
- Added `sl-clear` event to `<sl-select>`
|
||||
- Fixed a bug where dynamically changing menu items in `<sl-select>` would cause the display label to be blank [#374]
|
||||
- Fixed a bug where setting the `value` attribute or property on `<sl-input>` and `<sl-textarea>` would trigger validation too soon
|
||||
|
||||
@@ -18,146 +18,146 @@ Currently, the source of design tokens is considered to be [`light.css`](https:/
|
||||
|
||||
Focus ring tokens control the appearance of focus rings. Note that form inputs use `--sl-input-focus-ring-*` tokens instead.
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| `--sl-focus-ring-color` | var(--sl-color-primary-600) (light theme)<br>var(--sl-color-primary-700) (dark theme) |
|
||||
| `--sl-focus-ring-style` | solid |
|
||||
| `--sl-focus-ring-width` | 3px |
|
||||
| `--sl-focus-ring` | var(--sl-focus-ring-style) var(--sl-focus-ring-width) var(--sl-focus-ring-color) |
|
||||
| `--sl-focus-ring-offset` | 1px |
|
||||
| Token | Value |
|
||||
| ------------------------ | ----------------------------------------------------------------------------------------- |
|
||||
| `--sl-focus-ring-color` | `var(--sl-color-primary-600)` (light theme)<br>`var(--sl-color-primary-700)` (dark theme) |
|
||||
| `--sl-focus-ring-style` | `solid` |
|
||||
| `--sl-focus-ring-width` | `3px` |
|
||||
| `--sl-focus-ring` | `var(--sl-focus-ring-style) var(--sl-focus-ring-width) var(--sl-focus-ring-color)` |
|
||||
| `--sl-focus-ring-offset` | `1px` |
|
||||
|
||||
## Buttons
|
||||
|
||||
Button tokens control the appearance of buttons. In addition, buttons also currently use some form input tokens such as `--sl-input-height-*` and `--sl-input-border-*`. More button tokens may be added in the future to make it easier to style them more independently.
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------------ | --------------------------- |
|
||||
| `--sl-button-font-size-small` | var(--sl-font-size-x-small) |
|
||||
| `--sl-button-font-size-medium` | var(--sl-font-size-small) |
|
||||
| `--sl-button-font-size-large` | var(--sl-font-size-medium) |
|
||||
| Token | Value |
|
||||
| ------------------------------ | ----------------------------- |
|
||||
| `--sl-button-font-size-small` | `var(--sl-font-size-x-small)` |
|
||||
| `--sl-button-font-size-medium` | `var(--sl-font-size-small)` |
|
||||
| `--sl-button-font-size-large` | `var(--sl-font-size-medium)` |
|
||||
|
||||
## Form Inputs
|
||||
|
||||
Form input tokens control the appearance of form controls such as [input](/components/input), [select](/components/select), [textarea](/components/textarea), etc.
|
||||
|
||||
| Token | Value |
|
||||
| --------------------------------------- | -------------------------------- |
|
||||
| `--sl-input-height-small` | 1.875rem; (30px @ 16px base) |
|
||||
| `--sl-input-height-medium` | 2.5rem; (40px @ 16px base) |
|
||||
| `--sl-input-height-large` | 3.125rem; (50px @ 16px base) |
|
||||
| `--sl-input-background-color` | var(--sl-color-neutral-0) |
|
||||
| `--sl-input-background-color-hover` | var(--sl-input-background-color) |
|
||||
| `--sl-input-background-color-focus` | var(--sl-input-background-color) |
|
||||
| `--sl-input-background-color-disabled` | var(--sl-color-neutral-100) |
|
||||
| `--sl-input-border-color` | var(--sl-color-neutral-300) |
|
||||
| `--sl-input-border-color-hover` | var(--sl-color-neutral-400) |
|
||||
| `--sl-input-border-color-focus` | var(--sl-color-primary-500) |
|
||||
| `--sl-input-border-color-disabled` | var(--sl-color-neutral-300) |
|
||||
| `--sl-input-border-width` | 1px |
|
||||
| `--sl-input-required-content` | "\*" |
|
||||
| `--sl-input-required-content-offset` | -2px |
|
||||
| `--sl-input-required-content-color` | var(--sl-input-label-color) |
|
||||
| `--sl-input-border-radius-small` | var(--sl-border-radius-medium) |
|
||||
| `--sl-input-border-radius-medium` | var(--sl-border-radius-medium) |
|
||||
| `--sl-input-border-radius-large` | var(--sl-border-radius-medium) |
|
||||
| `--sl-input-font-family` | var(--sl-font-sans) |
|
||||
| `--sl-input-font-weight` | var(--sl-font-weight-normal) |
|
||||
| `--sl-input-font-size-small` | var(--sl-font-size-small) |
|
||||
| `--sl-input-font-size-medium` | var(--sl-font-size-medium) |
|
||||
| `--sl-input-font-size-large` | var(--sl-font-size-large) |
|
||||
| `--sl-input-letter-spacing` | var(--sl-letter-spacing-normal) |
|
||||
| `--sl-input-color` | var(--sl-color-neutral-700) |
|
||||
| `--sl-input-color-hover` | var(--sl-color-neutral-700) |
|
||||
| `--sl-input-color-focus` | var(--sl-color-neutral-700) |
|
||||
| `--sl-input-color-disabled` | var(--sl-color-neutral-900) |
|
||||
| `--sl-input-icon-color` | var(--sl-color-neutral-500) |
|
||||
| `--sl-input-icon-color-hover` | var(--sl-color-neutral-600) |
|
||||
| `--sl-input-icon-color-focus` | var(--sl-color-neutral-600) |
|
||||
| `--sl-input-placeholder-color` | var(--sl-color-neutral-500) |
|
||||
| `--sl-input-placeholder-color-disabled` | var(--sl-color-neutral-600) |
|
||||
| `--sl-input-spacing-small` | var(--sl-spacing-small) |
|
||||
| `--sl-input-spacing-medium` | var(--sl-spacing-medium) |
|
||||
| `--sl-input-spacing-large` | var(--sl-spacing-large) |
|
||||
| `--sl-input-focus-ring-color` | hsl(198.6 88.7% 48.4% / 40%) |
|
||||
| `--sl-input-focus-ring-offset` | 0 |
|
||||
| Token | Value |
|
||||
| --------------------------------------- | ---------------------------------- |
|
||||
| `--sl-input-height-small` | `1.875rem` (30px @ 16px base) |
|
||||
| `--sl-input-height-medium` | `2.5rem` (40px @ 16px base) |
|
||||
| `--sl-input-height-large` | `3.125rem` (50px @ 16px base) |
|
||||
| `--sl-input-background-color` | `var(--sl-color-neutral-0)` |
|
||||
| `--sl-input-background-color-hover` | `var(--sl-input-background-color)` |
|
||||
| `--sl-input-background-color-focus` | `var(--sl-input-background-color)` |
|
||||
| `--sl-input-background-color-disabled` | `var(--sl-color-neutral-100)` |
|
||||
| `--sl-input-border-color` | `var(--sl-color-neutral-300)` |
|
||||
| `--sl-input-border-color-hover` | `var(--sl-color-neutral-400)` |
|
||||
| `--sl-input-border-color-focus` | `var(--sl-color-primary-500)` |
|
||||
| `--sl-input-border-color-disabled` | `var(--sl-color-neutral-300)` |
|
||||
| `--sl-input-border-width` | `1px` |
|
||||
| `--sl-input-required-content` | `*` |
|
||||
| `--sl-input-required-content-offset` | `-2px` |
|
||||
| `--sl-input-required-content-color` | `var(--sl-input-label-color)` |
|
||||
| `--sl-input-border-radius-small` | `var(--sl-border-radius-medium)` |
|
||||
| `--sl-input-border-radius-medium` | `var(--sl-border-radius-medium)` |
|
||||
| `--sl-input-border-radius-large` | `var(--sl-border-radius-medium)` |
|
||||
| `--sl-input-font-family` | `var(--sl-font-sans)` |
|
||||
| `--sl-input-font-weight` | `var(--sl-font-weight-normal)` |
|
||||
| `--sl-input-font-size-small` | `var(--sl-font-size-small)` |
|
||||
| `--sl-input-font-size-medium` | `var(--sl-font-size-medium)` |
|
||||
| `--sl-input-font-size-large` | `var(--sl-font-size-large)` |
|
||||
| `--sl-input-letter-spacing` | `var(--sl-letter-spacing-normal)` |
|
||||
| `--sl-input-color` | `var(--sl-color-neutral-700)` |
|
||||
| `--sl-input-color-hover` | `var(--sl-color-neutral-700)` |
|
||||
| `--sl-input-color-focus` | `var(--sl-color-neutral-700)` |
|
||||
| `--sl-input-color-disabled` | `var(--sl-color-neutral-900)` |
|
||||
| `--sl-input-icon-color` | `var(--sl-color-neutral-500)` |
|
||||
| `--sl-input-icon-color-hover` | `var(--sl-color-neutral-600)` |
|
||||
| `--sl-input-icon-color-focus` | `var(--sl-color-neutral-600)` |
|
||||
| `--sl-input-placeholder-color` | `var(--sl-color-neutral-500)` |
|
||||
| `--sl-input-placeholder-color-disabled` | `var(--sl-color-neutral-600)` |
|
||||
| `--sl-input-spacing-small` | `var(--sl-spacing-small)` |
|
||||
| `--sl-input-spacing-medium` | `var(--sl-spacing-medium)` |
|
||||
| `--sl-input-spacing-large` | `var(--sl-spacing-large)` |
|
||||
| `--sl-input-focus-ring-color` | `hsl(198.6 88.7% 48.4% / 40%)` |
|
||||
| `--sl-input-focus-ring-offset` | `0` |
|
||||
|
||||
## Filled Form Inputs
|
||||
|
||||
Filled form input tokens control the appearance of form controls using the `filled` variant.
|
||||
|
||||
| Token | Value |
|
||||
| --------------------------------------------- | --------------------------- |
|
||||
| `--sl-input-filled-background-color` | var(--sl-color-neutral-100) |
|
||||
| `--sl-input-filled-background-color-hover` | var(--sl-color-neutral-100) |
|
||||
| `--sl-input-filled-background-color-focus` | var(--sl-color-neutral-100) |
|
||||
| `--sl-input-filled-background-color-disabled` | var(--sl-color-neutral-100) |
|
||||
| `--sl-input-filled-color` | var(--sl-color-neutral-800) |
|
||||
| `--sl-input-filled-color-hover` | var(--sl-color-neutral-800) |
|
||||
| `--sl-input-filled-color-focus` | var(--sl-color-neutral-700) |
|
||||
| `--sl-input-filled-color-disabled` | var(--sl-color-neutral-800) |
|
||||
| Token | Value |
|
||||
| --------------------------------------------- | ----------------------------- |
|
||||
| `--sl-input-filled-background-color` | `var(--sl-color-neutral-100)` |
|
||||
| `--sl-input-filled-background-color-hover` | `var(--sl-color-neutral-100)` |
|
||||
| `--sl-input-filled-background-color-focus` | `var(--sl-color-neutral-100)` |
|
||||
| `--sl-input-filled-background-color-disabled` | `var(--sl-color-neutral-100)` |
|
||||
| `--sl-input-filled-color` | `var(--sl-color-neutral-800)` |
|
||||
| `--sl-input-filled-color-hover` | `var(--sl-color-neutral-800)` |
|
||||
| `--sl-input-filled-color-focus` | `var(--sl-color-neutral-700)` |
|
||||
| `--sl-input-filled-color-disabled` | `var(--sl-color-neutral-800)` |
|
||||
|
||||
## Form Labels
|
||||
|
||||
Form label tokens control the appearance of labels in form controls.
|
||||
|
||||
| Token | Value |
|
||||
| ----------------------------------- | -------------------------- |
|
||||
| `--sl-input-label-font-size-small` | var(--sl-font-size-small) |
|
||||
| `--sl-input-label-font-size-medium` | var(--sl-font-size-medium) |
|
||||
| `--sl-input-label-font-size-large` | var(--sl-font-size-large) |
|
||||
| `--sl-input-label-color` | inherit |
|
||||
| Token | Value |
|
||||
| ----------------------------------- | ---------------------------- |
|
||||
| `--sl-input-label-font-size-small` | `var(--sl-font-size-small)` |
|
||||
| `--sl-input-label-font-size-medium` | `var(--sl-font-size-medium`) |
|
||||
| `--sl-input-label-font-size-large` | `var(--sl-font-size-large)` |
|
||||
| `--sl-input-label-color` | `inherit` |
|
||||
|
||||
## Help Text
|
||||
|
||||
Help text tokens control the appearance of help text in form controls.
|
||||
|
||||
| Token | Value |
|
||||
| --------------------------------------- | --------------------------- |
|
||||
| `--sl-input-help-text-font-size-small` | var(--sl-font-size-x-small) |
|
||||
| `--sl-input-help-text-font-size-medium` | var(--sl-font-size-small) |
|
||||
| `--sl-input-help-text-font-size-large` | var(--sl-font-size-medium) |
|
||||
| `--sl-input-help-text-color` | var(--sl-color-neutral-500) |
|
||||
| Token | Value |
|
||||
| --------------------------------------- | ----------------------------- |
|
||||
| `--sl-input-help-text-font-size-small` | `var(--sl-font-size-x-small)` |
|
||||
| `--sl-input-help-text-font-size-medium` | `var(--sl-font-size-small)` |
|
||||
| `--sl-input-help-text-font-size-large` | `var(--sl-font-size-medium)` |
|
||||
| `--sl-input-help-text-color` | `var(--sl-color-neutral-500)` |
|
||||
|
||||
## Toggles
|
||||
|
||||
Toggle tokens control the appearance of toggles such as [checkbox](/components/checkbox), [radio](/components/radio), [switch](/components/switch), etc.
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------- | --------------------------- |
|
||||
| `--sl-toggle-size-small` | 0.875rem (14px @ 16px base) |
|
||||
| `--sl-toggle-size-medium` | 1.125rem (18px @ 16px base) |
|
||||
| `--sl-toggle-size-large` | 1.375rem (22px @ 16px base) |
|
||||
| Token | Value |
|
||||
| ------------------------- | ----------------------------- |
|
||||
| `--sl-toggle-size-small` | `0.875rem` (14px @ 16px base) |
|
||||
| `--sl-toggle-size-medium` | `1.125rem` (18px @ 16px base) |
|
||||
| `--sl-toggle-size-large` | `1.375rem` (22px @ 16px base) |
|
||||
|
||||
## Overlays
|
||||
|
||||
Overlay tokens control the appearance of overlays as used in [dialog](/components/dialog), [drawer](/components/drawer), etc.
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------------- | ------------------------- |
|
||||
| `--sl-overlay-background-color` | hsl(240 3.8% 46.1% / 33%) |
|
||||
| Token | Value |
|
||||
| ------------------------------- | --------------------------- |
|
||||
| `--sl-overlay-background-color` | `hsl(240 3.8% 46.1% / 33%)` |
|
||||
|
||||
## Panels
|
||||
|
||||
Panel tokens control the appearance of panels such as those used in [dialog](/components/dialog), [drawer](/components/drawer), [menu](/components/menu), etc.
|
||||
|
||||
| Token | Value |
|
||||
| ----------------------------- | --------------------------- |
|
||||
| `--sl-panel-background-color` | var(--sl-color-neutral-0) |
|
||||
| `--sl-panel-border-color` | var(--sl-color-neutral-200) |
|
||||
| `--sl-panel-border-width` | 1px |
|
||||
| Token | Value |
|
||||
| ----------------------------- | ----------------------------- |
|
||||
| `--sl-panel-background-color` | `var(--sl-color-neutral-0)` |
|
||||
| `--sl-panel-border-color` | `var(--sl-color-neutral-200)` |
|
||||
| `--sl-panel-border-width` | `1px` |
|
||||
|
||||
## Tooltips
|
||||
|
||||
Tooltip tokens control the appearance of tooltips. This includes the [tooltip](/components/tooltip) component as well as other implementations, such [range tooltips](/components/range).
|
||||
|
||||
| Token | Value |
|
||||
| ------------------------------- | ---------------------------------------------------- |
|
||||
| `--sl-tooltip-border-radius` | var(--sl-border-radius-medium) |
|
||||
| `--sl-tooltip-background-color` | var(--sl-color-neutral-800) |
|
||||
| `--sl-tooltip-color` | var(--sl-color-neutral-0) |
|
||||
| `--sl-tooltip-font-family` | var(--sl-font-sans) |
|
||||
| `--sl-tooltip-font-weight` | var(--sl-font-weight-normal) |
|
||||
| `--sl-tooltip-font-size` | var(--sl-font-size-small) |
|
||||
| `--sl-tooltip-line-height` | var(--sl-line-height-dense) |
|
||||
| `--sl-tooltip-padding` | var(--sl-spacing-2x-small) var(--sl-spacing-x-small) |
|
||||
| `--sl-tooltip-arrow-size` | 6px |
|
||||
| Token | Value |
|
||||
| ------------------------------- | ------------------------------------------------------ |
|
||||
| `--sl-tooltip-border-radius` | `var(--sl-border-radius-medium)` |
|
||||
| `--sl-tooltip-background-color` | `var(--sl-color-neutral-800)` |
|
||||
| `--sl-tooltip-color` | `var(--sl-color-neutral-0)` |
|
||||
| `--sl-tooltip-font-family` | `var(--sl-font-sans)` |
|
||||
| `--sl-tooltip-font-weight` | `var(--sl-font-weight-normal)` |
|
||||
| `--sl-tooltip-font-size` | `var(--sl-font-size-small)` |
|
||||
| `--sl-tooltip-line-height` | `var(--sl-line-height-dense)` |
|
||||
| `--sl-tooltip-padding` | `var(--sl-spacing-2x-small) var(--sl-spacing-x-small)` |
|
||||
| `--sl-tooltip-arrow-size` | `6px` |
|
||||
|
||||
@@ -40,7 +40,7 @@ Import the Shoelace default theme (stylesheet) in `/resources/css/app.css`:
|
||||
|
||||
### Import Your Shoelace Components
|
||||
|
||||
Import each Shoelace component you plan to use in `/resources/js/bootstrap.js`. Use the full path to each component (as outlined in the [Cherry Picking instructions](https://shoelace.style/getting-started/installation?id=cherry-picking)). You can find the full import statement for a component in the _Importing_ section of the component's documentation (use the _Bundler_ import). Your imports should look similar to:
|
||||
Import each Shoelace component you plan to use in `/resources/js/bootstrap.js`. Use the full path to each component (as outlined in the [Cherry Picking instructions](https://shoelace.style/getting-started/installation#cherry-picking)). You can find the full import statement for a component in the _Importing_ section of the component's documentation (use the _Bundler_ import). Your imports should look similar to:
|
||||
|
||||
```js
|
||||
import '@shoelace-style/shoelace/dist/components/button/button.js';
|
||||
|
||||
@@ -67,7 +67,7 @@ function CustomEls({ URL }) {
|
||||
setBasePath(`${URL}/static/static`);
|
||||
|
||||
// This imports all components
|
||||
import('@shoelace-style/shoelace/dist/shoelace');
|
||||
import('@shoelace-style/shoelace/dist/react');
|
||||
// If you're wanting to selectively import components, replace this line with your own definitions
|
||||
|
||||
// import("@shoelace-style/shoelace/dist/components/button/button");
|
||||
@@ -84,7 +84,7 @@ If we use `useEffect` instead of `useLayoutEffect`, the initial render will occu
|
||||
:::
|
||||
|
||||
:::tip
|
||||
This will import all Shoelace components for convenience. To selectively import components, refer to the [Using webpack](/getting-started/installation?id=using-webpack) section of the docs.
|
||||
This will import all Shoelace components for convenience. To selectively import components, refer to the [Using webpack](/getting-started/installation#using-webpack) section of the docs.
|
||||
:::
|
||||
|
||||
You may be wondering where the `URL` property is coming from. We'll address that in the next few sections.
|
||||
|
||||
@@ -37,7 +37,7 @@ The next step is to import Shoelace's default theme (stylesheet) in `app/javascr
|
||||
@import '@shoelace-style/shoelace/dist/themes/dark'; // Optional dark theme
|
||||
```
|
||||
|
||||
Fore more details about themes, please refer to [Theme Basics](/getting-started/themes?id=theme-basics).
|
||||
Fore more details about themes, please refer to [Theme Basics](/getting-started/themes#theme-basics).
|
||||
|
||||
### Importing Required Scripts
|
||||
|
||||
|
||||
1302
package-lock.json
generated
1302
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@shoelace-style/shoelace",
|
||||
"description": "A forward-thinking library of web components.",
|
||||
"version": "2.4.0",
|
||||
"version": "2.5.2",
|
||||
"homepage": "https://github.com/shoelace-style/shoelace",
|
||||
"author": "Cory LaViska",
|
||||
"license": "MIT",
|
||||
@@ -23,7 +23,8 @@
|
||||
"./dist/translations/*": "./dist/translations/*"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"cdn"
|
||||
],
|
||||
"keywords": [
|
||||
"web components",
|
||||
@@ -42,8 +43,8 @@
|
||||
"url": "https://github.com/sponsors/claviska"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node scripts/build.js --bundle --serve",
|
||||
"build": "node scripts/build.js --bundle --types --copydir \"_site/dist\"",
|
||||
"start": "node scripts/build.js --serve",
|
||||
"build": "node scripts/build.js",
|
||||
"verify": "npm run prettier:check && npm run lint && npm run build && npm run test",
|
||||
"prepublishOnly": "npm run verify",
|
||||
"prettier": "prettier --write --loglevel warn .",
|
||||
@@ -67,9 +68,9 @@
|
||||
"@floating-ui/dom": "^1.2.1",
|
||||
"@lit-labs/react": "^1.1.1",
|
||||
"@shoelace-style/animations": "^1.1.0",
|
||||
"@shoelace-style/localize": "^3.1.0",
|
||||
"@shoelace-style/localize": "^3.1.1",
|
||||
"composed-offset-position": "^0.0.4",
|
||||
"lit": "^2.6.1",
|
||||
"lit": "^2.7.5",
|
||||
"qr-creator": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -78,8 +79,8 @@
|
||||
"@open-wc/testing": "^3.1.7",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/react": "^18.0.26",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.48.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@web/dev-server-esbuild": "^0.3.3",
|
||||
"@web/test-runner": "^0.15.0",
|
||||
"@web/test-runner-commands": "^0.6.5",
|
||||
@@ -94,16 +95,16 @@
|
||||
"cspell": "^6.18.1",
|
||||
"del": "^7.0.0",
|
||||
"download": "^8.0.0",
|
||||
"esbuild": "^0.16.17",
|
||||
"eslint": "^8.31.0",
|
||||
"esbuild": "^0.18.2",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-plugin-chai-expect": "^3.0.0",
|
||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||
"eslint-plugin-import": "^2.27.4",
|
||||
"eslint-plugin-lit": "^1.8.2",
|
||||
"eslint-plugin-lit-a11y": "^2.3.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-lit": "^1.8.3",
|
||||
"eslint-plugin-lit-a11y": "^4.1.0",
|
||||
"eslint-plugin-markdown": "^3.0.0",
|
||||
"eslint-plugin-sort-imports-es6-autofix": "^0.6.0",
|
||||
"eslint-plugin-wc": "^1.4.0",
|
||||
"eslint-plugin-wc": "^1.5.0",
|
||||
"front-matter": "^4.0.2",
|
||||
"get-port": "^7.0.0",
|
||||
"globby": "^13.1.3",
|
||||
@@ -130,7 +131,7 @@
|
||||
"source-map": "^0.7.4",
|
||||
"strip-css-comments": "^5.0.0",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "4.9.4",
|
||||
"typescript": "^5.1.3",
|
||||
"user-agent-data-types": "^0.3.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
@@ -10,19 +10,18 @@ import fs from 'fs/promises';
|
||||
import getPort, { portNumbers } from 'get-port';
|
||||
import ora from 'ora';
|
||||
import util from 'util';
|
||||
import * as path from 'path';
|
||||
|
||||
const { bundle, copydir, dir, serve, types } = commandLineArgs([
|
||||
{ name: 'bundle', type: Boolean },
|
||||
{ name: 'copydir', type: String },
|
||||
{ name: 'serve', type: Boolean },
|
||||
{ name: 'types', type: Boolean }
|
||||
]);
|
||||
const { serve } = commandLineArgs([{ name: 'serve', type: Boolean }]);
|
||||
const outdir = 'dist';
|
||||
const cdndir = 'cdn';
|
||||
const sitedir = '_site';
|
||||
const spinner = ora({ hideCursor: false }).start();
|
||||
const execPromise = util.promisify(exec);
|
||||
let childProcess;
|
||||
let buildResult;
|
||||
let buildResults;
|
||||
|
||||
const bundleDirectories = [cdndir, outdir];
|
||||
|
||||
//
|
||||
// Runs 11ty and builds the docs. The returned promise resolves after the initial publish has completed. The child
|
||||
@@ -72,7 +71,7 @@ async function buildTheDocs(watch = false) {
|
||||
async function buildTheSource() {
|
||||
const alwaysExternal = ['@lit-labs/react', 'react'];
|
||||
|
||||
return await esbuild.build({
|
||||
const cdnConfig = {
|
||||
format: 'esm',
|
||||
target: 'es2017',
|
||||
entryPoints: [
|
||||
@@ -94,9 +93,8 @@ async function buildTheSource() {
|
||||
// React wrappers
|
||||
...(await globby('./src/react/**/*.ts'))
|
||||
],
|
||||
outdir,
|
||||
outdir: cdndir,
|
||||
chunkNames: 'chunks/[name].[hash]',
|
||||
incremental: serve,
|
||||
define: {
|
||||
// Floating UI requires this to be set
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
@@ -108,19 +106,34 @@ async function buildTheSource() {
|
||||
//
|
||||
// We never bundle React or @lit-labs/react though!
|
||||
//
|
||||
external: bundle
|
||||
? alwaysExternal
|
||||
: [...alwaysExternal, '@floating-ui/dom', '@shoelace-style/animations', 'lit', 'qr-creator'],
|
||||
external: alwaysExternal,
|
||||
splitting: true,
|
||||
plugins: []
|
||||
});
|
||||
};
|
||||
|
||||
const npmConfig = {
|
||||
...cdnConfig,
|
||||
bundle: false,
|
||||
external: undefined,
|
||||
outdir
|
||||
};
|
||||
|
||||
if (serve) {
|
||||
// Use the context API to allow incremental dev builds
|
||||
const contexts = await Promise.all([esbuild.context(cdnConfig), esbuild.context(npmConfig)]);
|
||||
await Promise.all(contexts.map(context => context.rebuild()));
|
||||
return contexts;
|
||||
} else {
|
||||
// Use the standard API for production builds
|
||||
return await Promise.all([esbuild.build(cdnConfig), esbuild.build(npmConfig)]);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Called on SIGINT or SIGTERM to cleanup the build and child processes.
|
||||
//
|
||||
function handleCleanup() {
|
||||
buildResult.rebuild.dispose();
|
||||
buildResults.forEach(result => result.dispose());
|
||||
|
||||
if (childProcess) {
|
||||
childProcess.kill('SIGINT');
|
||||
@@ -150,12 +163,16 @@ async function nextTask(label, action) {
|
||||
}
|
||||
|
||||
await nextTask('Cleaning up the previous build', async () => {
|
||||
await Promise.all([deleteAsync(outdir), deleteAsync(sitedir)]);
|
||||
await Promise.all([deleteAsync(sitedir), ...bundleDirectories.map(dir => deleteAsync(dir))]);
|
||||
await fs.mkdir(outdir, { recursive: true });
|
||||
});
|
||||
|
||||
await nextTask('Generating component metadata', () => {
|
||||
return execPromise(`node scripts/make-metadata.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
return Promise.all(
|
||||
bundleDirectories.map(dir => {
|
||||
return execPromise(`node scripts/make-metadata.js --outdir "${dir}"`, { stdio: 'inherit' });
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
await nextTask('Wrapping components for React', () => {
|
||||
@@ -178,15 +195,24 @@ await nextTask('Running the TypeScript compiler', () => {
|
||||
return execPromise(`tsc --project ./tsconfig.prod.json --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
});
|
||||
|
||||
await nextTask('Building source files', async () => {
|
||||
buildResult = await buildTheSource();
|
||||
// Copy the above steps to the CDN directory directly so we don't need to twice the work for nothing.
|
||||
await nextTask(`Copying Web Types, Themes, Icons, and TS Types to "${cdndir}"`, async () => {
|
||||
await deleteAsync(cdndir);
|
||||
await copy(outdir, cdndir);
|
||||
});
|
||||
|
||||
if (copydir) {
|
||||
// Copy the build output to an additional directory
|
||||
await nextTask(`Copying the build to "${copydir}"`, async () => {
|
||||
await deleteAsync(copydir);
|
||||
await copy(outdir, copydir);
|
||||
await nextTask('Building source files', async () => {
|
||||
buildResults = await buildTheSource();
|
||||
});
|
||||
|
||||
// Copy the CDN build to the docs (prod only; we use a virtual directory in dev)
|
||||
if (!serve) {
|
||||
await nextTask(`Copying the build to "${sitedir}"`, async () => {
|
||||
await deleteAsync(sitedir);
|
||||
|
||||
// We copy the CDN build because that has everything bundled. Yes this looks weird.
|
||||
// But if we do "/cdn" it requires changes all the docs to do /cdn instead of /dist.
|
||||
await copy(cdndir, path.join(sitedir, 'dist'));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -215,7 +241,7 @@ if (serve) {
|
||||
server: {
|
||||
baseDir: sitedir,
|
||||
routes: {
|
||||
'/dist': './dist'
|
||||
'/dist': './cdn'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -243,16 +269,24 @@ if (serve) {
|
||||
const isStylesheet = /(\.css|\.styles\.ts)$/.test(filename);
|
||||
|
||||
// Rebuild the source
|
||||
await buildResult.rebuild();
|
||||
await Promise.all([buildResults.map(result => result.rebuild())]);
|
||||
|
||||
// Rebuild stylesheets when a theme file changes
|
||||
if (isTheme) {
|
||||
await execPromise(`node scripts/make-themes.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
await Promise.all(
|
||||
bundleDirectories.map(dir => {
|
||||
execPromise(`node scripts/make-themes.js --outdir "${dir}"`, { stdio: 'inherit' });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Rebuild metadata (but not when styles are changed)
|
||||
if (!isStylesheet) {
|
||||
await execPromise(`node scripts/make-metadata.js --outdir "${outdir}"`, { stdio: 'inherit' });
|
||||
await Promise.all(
|
||||
bundleDirectories.map(dir => {
|
||||
return execPromise(`node scripts/make-metadata.js --outdir "${dir}"`, { stdio: 'inherit' });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
bs.reload();
|
||||
|
||||
@@ -23,7 +23,7 @@ const url = `https://github.com/twbs/icons/archive/v${version}.zip`;
|
||||
try {
|
||||
await fs.stat(`${srcPath}/LICENSE.md`);
|
||||
} catch {
|
||||
// Download the source from GitHub (since not everything is published to NPM)
|
||||
// Download the source from GitHub (since not everything is published to npm)
|
||||
await download(url, './.cache/icons', { extract: true });
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,14 @@ components.map(component => {
|
||||
const componentDir = path.join(reactDir, tagWithoutPrefix);
|
||||
const componentFile = path.join(componentDir, 'index.ts');
|
||||
const importPath = component.path;
|
||||
const events = (component.events || []).map(event => `${event.reactName}: '${event.name}'`).join(',\n');
|
||||
const eventImports = (component.events || [])
|
||||
.map(event => `import { ${event.eventName} } from '../../../src/events/events';`)
|
||||
.join('\n');
|
||||
const eventNameImport =
|
||||
(component.events || []).length > 0 ? `import { type EventName } from '@lit-labs/react';` : ``;
|
||||
const events = (component.events || [])
|
||||
.map(event => `${event.reactName}: '${event.name}' as EventName<${event.eventName}>`)
|
||||
.join(',\n');
|
||||
|
||||
fs.mkdirSync(componentDir, { recursive: true });
|
||||
|
||||
@@ -35,6 +42,9 @@ components.map(component => {
|
||||
import { createComponent } from '@lit-labs/react';
|
||||
import Component from '../../${importPath}';
|
||||
|
||||
${eventNameImport}
|
||||
${eventImports}
|
||||
|
||||
export default createComponent({
|
||||
tagName: '${component.tagName}',
|
||||
elementClass: Component,
|
||||
@@ -49,7 +59,7 @@ components.map(component => {
|
||||
})
|
||||
);
|
||||
|
||||
index.push(`export { default as ${component.name} } from './${tagWithoutPrefix}';`);
|
||||
index.push(`export { default as ${component.name} } from './${tagWithoutPrefix}/index.js';`);
|
||||
|
||||
fs.writeFileSync(componentFile, source, 'utf8');
|
||||
});
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function (plop) {
|
||||
type: 'modify',
|
||||
path: '../../src/shoelace.ts',
|
||||
pattern: /\/\* plop:component \*\//,
|
||||
template: `export { default as {{ properCase tag }} } from './components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}';\n/* plop:component */`
|
||||
template: `export { default as {{ properCase tag }} } from './components/{{ tagWithoutPrefix tag }}/{{ tagWithoutPrefix tag }}.js';\n/* plop:component */`
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './{{ tagWithoutPrefix tag }}.styles';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './{{ tagWithoutPrefix tag }}.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
|
||||
describe('<{{ tag }}>', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { clickOnElement, moveMouseOnElement } from '../../internal/test';
|
||||
import { queryByTestId } from '../../internal/test/data-testid-helpers';
|
||||
import { clickOnElement, moveMouseOnElement } from '../../internal/test.js';
|
||||
import { queryByTestId } from '../../internal/test/data-testid-helpers.js';
|
||||
import { resetMouse } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type SlAlert from './alert';
|
||||
import type SlIconButton from '../icon-button/icon-button';
|
||||
import type SlAlert from './alert.js';
|
||||
import type SlIconButton from '../icon-button/icon-button.js';
|
||||
|
||||
const getAlertContainer = (alert: SlAlert): HTMLElement => {
|
||||
return alert.shadowRoot!.querySelector<HTMLElement>('[part="base"]')!;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import '../icon-button/icon-button';
|
||||
import { animateTo, stopAnimations } from '../../internal/animate';
|
||||
import '../icon-button/icon-button.js';
|
||||
import { animateTo, stopAnimations } from '../../internal/animate.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
|
||||
import { HasSlotController } from '../../internal/slot';
|
||||
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize';
|
||||
import { waitForEvent } from '../../internal/event';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './alert.styles';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { waitForEvent } from '../../internal/event.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './alert.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
const toastStack = Object.assign(document.createElement('div'), { className: 'sl-toast-stack' });
|
||||
@@ -199,9 +199,13 @@ export default class SlAlert extends ShoelaceElement {
|
||||
aria-hidden=${this.open ? 'false' : 'true'}
|
||||
@mousemove=${this.handleMouseMove}
|
||||
>
|
||||
<slot name="icon" part="icon" class="alert__icon"></slot>
|
||||
<div part="icon" class="alert__icon">
|
||||
<slot name="icon"></slot>
|
||||
</div>
|
||||
|
||||
<slot part="message" class="alert__message" aria-live="polite"></slot>
|
||||
<div part="message" class="alert__message" aria-live="polite">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
${this.closable
|
||||
? html`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { clickOnElement } from '../../internal/test';
|
||||
import '../../../dist/shoelace.js';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import type SlAnimatedImage from './animated-image';
|
||||
import type SlAnimatedImage from './animated-image.js';
|
||||
|
||||
describe('<sl-animated-image>', () => {
|
||||
it('should render a component', async () => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import '../icon/icon';
|
||||
import '../icon/icon.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './animated-image.styles';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './animated-image.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import type SlAnimation from './animation';
|
||||
import type SlAnimation from './animation.js';
|
||||
|
||||
describe('<sl-animation>', () => {
|
||||
const boxToAnimate = html`<div style="width: 10px; height: 10px;" data-testid="animated-box"></div>`;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { animations } from './animations';
|
||||
import { animations } from './animations.js';
|
||||
import { customElement, property, queryAsync } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './animation.styles';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './animation.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
@@ -78,7 +78,7 @@ export default class SlAnimation extends ShoelaceElement {
|
||||
@property({ attribute: 'playback-rate', type: Number }) playbackRate = 1;
|
||||
|
||||
/** Gets and sets the current animation time. */
|
||||
get currentTime(): number {
|
||||
get currentTime(): CSSNumberish {
|
||||
return this.animation?.currentTime ?? 0;
|
||||
}
|
||||
|
||||
@@ -91,8 +91,6 @@ export default class SlAnimation extends ShoelaceElement {
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.createAnimation();
|
||||
this.handleAnimationCancel = this.handleAnimationCancel.bind(this);
|
||||
this.handleAnimationFinish = this.handleAnimationFinish.bind(this);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@@ -100,17 +98,17 @@ export default class SlAnimation extends ShoelaceElement {
|
||||
this.destroyAnimation();
|
||||
}
|
||||
|
||||
private handleAnimationFinish() {
|
||||
private handleAnimationFinish = () => {
|
||||
this.play = false;
|
||||
this.hasStarted = false;
|
||||
this.emit('sl-finish');
|
||||
}
|
||||
};
|
||||
|
||||
private handleAnimationCancel() {
|
||||
private handleAnimationCancel = () => {
|
||||
this.play = false;
|
||||
this.hasStarted = false;
|
||||
this.emit('sl-cancel');
|
||||
}
|
||||
};
|
||||
|
||||
private handleSlotChange() {
|
||||
this.destroyAnimation();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { aTimeout, expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import type SlAvatar from './avatar';
|
||||
import type SlAvatar from './avatar.js';
|
||||
|
||||
// The default avatar background just misses AA contrast, but the next step up is way too dark. Since avatars aren't
|
||||
// used to display text, we're going to relax this rule.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import '../icon/icon';
|
||||
import '../icon/icon.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './avatar.styles';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './avatar.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
@@ -69,9 +69,11 @@ export default class SlAvatar extends ShoelaceElement {
|
||||
avatarWithoutImage = html`<div part="initials" class="avatar__initials">${this.initials}</div>`;
|
||||
} else {
|
||||
avatarWithoutImage = html`
|
||||
<slot name="icon" part="icon" class="avatar__icon" aria-hidden="true">
|
||||
<sl-icon name="person-fill" library="system"></sl-icon>
|
||||
</slot>
|
||||
<div part="icon" class="avatar__icon" aria-hidden="true">
|
||||
<slot name="icon">
|
||||
<sl-icon name="person-fill" library="system"></sl-icon>
|
||||
</slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import type SlBadge from './badge';
|
||||
import type SlBadge from './badge.js';
|
||||
|
||||
// The default badge background just misses AA contrast, but the next step up is way too dark. We're going to relax this
|
||||
// rule for now.
|
||||
const ignoredRules = ['color-contrast'];
|
||||
|
||||
describe('<sl-badge>', () => {
|
||||
let el: SlBadge;
|
||||
@@ -10,7 +15,7 @@ describe('<sl-badge>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests with a role of status on the base part.', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.getAttribute('role')).to.eq('status');
|
||||
@@ -32,7 +37,7 @@ describe('<sl-badge>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should append the pill class to the classlist to render a pill', () => {
|
||||
@@ -47,7 +52,7 @@ describe('<sl-badge>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should append the pulse class to the classlist to render a pulse', () => {
|
||||
@@ -63,7 +68,7 @@ describe('<sl-badge>', () => {
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should default to square styling, with the primary color', () => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './badge.styles';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './badge.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
@@ -30,7 +30,7 @@ export default class SlBadge extends ShoelaceElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<slot
|
||||
<span
|
||||
part="base"
|
||||
class=${classMap({
|
||||
badge: true,
|
||||
@@ -43,7 +43,9 @@ export default class SlBadge extends ShoelaceElement {
|
||||
'badge--pulse': this.pulse
|
||||
})}
|
||||
role="status"
|
||||
></slot>
|
||||
>
|
||||
<slot></slot>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import type SlBreadcrumbItem from './breadcrumb-item';
|
||||
import type SlBreadcrumbItem from './breadcrumb-item.js';
|
||||
|
||||
describe('<sl-breadcrumb-item>', () => {
|
||||
let el: SlBreadcrumbItem;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { HasSlotController } from '../../internal/slot';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './breadcrumb-item.styles';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './breadcrumb-item.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
@@ -55,7 +55,9 @@ export default class SlBreadcrumbItem extends ShoelaceElement {
|
||||
'breadcrumb-item--has-suffix': this.hasSlotController.test('suffix')
|
||||
})}
|
||||
>
|
||||
<slot name="prefix" part="prefix" class="breadcrumb-item__prefix"></slot>
|
||||
<span part="prefix" class="breadcrumb-item__prefix">
|
||||
<slot name="prefix"></slot>
|
||||
</span>
|
||||
|
||||
${isLink
|
||||
? html`
|
||||
@@ -75,9 +77,13 @@ export default class SlBreadcrumbItem extends ShoelaceElement {
|
||||
</button>
|
||||
`}
|
||||
|
||||
<slot name="suffix" part="suffix" class="breadcrumb-item__suffix"></slot>
|
||||
<span part="suffix" class="breadcrumb-item__suffix">
|
||||
<slot name="suffix"></slot>
|
||||
</span>
|
||||
|
||||
<slot name="separator" part="separator" class="breadcrumb-item__separator" aria-hidden="true"></slot>
|
||||
<span part="separator" class="breadcrumb-item__separator" aria-hidden="true">
|
||||
<slot name="separator"></slot>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import type SlBreadcrumb from './breadcrumb';
|
||||
import type SlBreadcrumb from './breadcrumb.js';
|
||||
|
||||
// The default link color just misses AA contrast, but the next step up is way too dark. Maybe we can solve this in the
|
||||
// future with a prefers-contrast media query.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import '../icon/icon';
|
||||
import '../icon/icon.js';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './breadcrumb.styles';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './breadcrumb.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
import type SlBreadcrumbItem from '../breadcrumb-item/breadcrumb-item';
|
||||
import type SlBreadcrumbItem from '../breadcrumb-item/breadcrumb-item.js';
|
||||
|
||||
/**
|
||||
* @summary Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.
|
||||
@@ -90,9 +90,11 @@ export default class SlBreadcrumb extends ShoelaceElement {
|
||||
<slot @slotchange=${this.handleSlotChange}></slot>
|
||||
</nav>
|
||||
|
||||
<slot name="separator" hidden aria-hidden="true">
|
||||
<sl-icon name=${this.localize.dir() === 'rtl' ? 'chevron-left' : 'chevron-right'} library="system"></sl-icon>
|
||||
</slot>
|
||||
<span hidden aria-hidden="true">
|
||||
<slot name="separator">
|
||||
<sl-icon name=${this.localize.dir() === 'rtl' ? 'chevron-left' : 'chevron-right'} library="system"></sl-icon>
|
||||
</slot>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
|
||||
import type SlButtonGroup from './button-group';
|
||||
import type SlButtonGroup from './button-group.js';
|
||||
|
||||
describe('<sl-button-group>', () => {
|
||||
describe('defaults ', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './button-group.styles';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './button-group.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
@@ -68,7 +68,7 @@ export default class SlButtonGroup extends ShoelaceElement {
|
||||
render() {
|
||||
// eslint-disable-next-line lit-a11y/mouse-events-have-key-events
|
||||
return html`
|
||||
<slot
|
||||
<div
|
||||
part="base"
|
||||
class="button-group"
|
||||
role="${this.disableRole ? 'presentation' : 'group'}"
|
||||
@@ -77,8 +77,9 @@ export default class SlButtonGroup extends ShoelaceElement {
|
||||
@focusin=${this.handleFocus}
|
||||
@mouseover=${this.handleMouseOver}
|
||||
@mouseout=${this.handleMouseOut}
|
||||
@slotchange=${this.handleSlotChange}
|
||||
></slot>
|
||||
>
|
||||
<slot @slotchange=${this.handleSlotChange}></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import sinon from 'sinon';
|
||||
import type SlButton from './button';
|
||||
import type SlButton from './button.js';
|
||||
|
||||
const variants = ['default', 'primary', 'success', 'neutral', 'warning', 'danger'];
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import '../icon/icon';
|
||||
import '../spinner/spinner';
|
||||
import '../icon/icon.js';
|
||||
import '../spinner/spinner.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { FormControlController, validValidityState } from '../../internal/form';
|
||||
import { HasSlotController } from '../../internal/slot';
|
||||
import { FormControlController, validValidityState } from '../../internal/form.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html, literal } from 'lit/static-html.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { LocalizeController } from '../../utilities/localize';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './button.styles';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './button.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
import type { ShoelaceFormControl } from '../../internal/shoelace-element';
|
||||
import type { ShoelaceFormControl } from '../../internal/shoelace-element.js';
|
||||
|
||||
/**
|
||||
* @summary Buttons represent actions that are available to the user.
|
||||
@@ -167,17 +167,11 @@ export default class SlButton extends ShoelaceElement implements ShoelaceFormCon
|
||||
return '';
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.handleHostClick = this.handleHostClick.bind(this);
|
||||
constructor() {
|
||||
super();
|
||||
this.addEventListener('click', this.handleHostClick);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener('click', this.handleHostClick);
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
if (this.isButton()) {
|
||||
this.formControlController.updateValidity();
|
||||
@@ -204,13 +198,13 @@ export default class SlButton extends ShoelaceElement implements ShoelaceFormCon
|
||||
}
|
||||
}
|
||||
|
||||
private handleHostClick(event: MouseEvent) {
|
||||
private handleHostClick = (event: MouseEvent) => {
|
||||
// Prevent the click event from being emitted when the button is disabled or loading
|
||||
if (this.disabled || this.loading) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleInvalid(event: Event) {
|
||||
this.formControlController.setValidity(false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import type SlCard from './card';
|
||||
import type SlCard from './card.js';
|
||||
|
||||
describe('<sl-card>', () => {
|
||||
let el: SlCard;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { HasSlotController } from '../../internal/slot';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './card.styles';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './card.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
|
||||
describe('<sl-carousel-item>', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './carousel-item.styles';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './carousel-item.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { clickOnElement } from '../../internal/test';
|
||||
import '../../../dist/shoelace.js';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import type SlCarousel from './carousel';
|
||||
import type SlCarousel from './carousel.js';
|
||||
|
||||
describe('<sl-carousel>', () => {
|
||||
it('should render a carousel with default configuration', async () => {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import '../icon/icon';
|
||||
import { AutoplayController } from './autoplay-controller';
|
||||
import { clamp } from 'src/internal/math';
|
||||
import '../icon/icon.js';
|
||||
import { AutoplayController } from './autoplay-controller.js';
|
||||
import { clamp } from '../../internal/math.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { LocalizeController } from '@shoelace-style/localize';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { map } from 'lit/directives/map.js';
|
||||
import { prefersReducedMotion } from '../../internal/animate';
|
||||
import { prefersReducedMotion } from '../../internal/animate.js';
|
||||
import { range } from 'lit/directives/range.js';
|
||||
import { ScrollController } from './scroll-controller';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import SlCarouselItem from '../carousel-item/carousel-item';
|
||||
import styles from './carousel.styles';
|
||||
import { ScrollController } from './scroll-controller.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import SlCarouselItem from '../carousel-item/carousel-item.js';
|
||||
import styles from './carousel.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
@@ -132,7 +132,7 @@ export default class SlCarousel extends ShoelaceElement {
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this.initializeSlides();
|
||||
this.mutationObserver = new MutationObserver(this.handleSlotChange.bind(this));
|
||||
this.mutationObserver = new MutationObserver(this.handleSlotChange);
|
||||
this.mutationObserver.observe(this, { childList: true, subtree: false });
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ export default class SlCarousel extends ShoelaceElement {
|
||||
}
|
||||
|
||||
private getCurrentPage() {
|
||||
return Math.floor(this.activeSlide / this.slidesPerPage);
|
||||
return Math.ceil(this.activeSlide / this.slidesPerPage);
|
||||
}
|
||||
|
||||
private getSlides({ excludeClones = true }: { excludeClones?: boolean } = {}) {
|
||||
@@ -211,7 +211,7 @@ export default class SlCarousel extends ShoelaceElement {
|
||||
}
|
||||
}
|
||||
|
||||
private handleSlotChange(mutations: MutationRecord[]) {
|
||||
private handleSlotChange = (mutations: MutationRecord[]) => {
|
||||
const needsInitialization = mutations.some(mutation =>
|
||||
[...mutation.addedNodes, ...mutation.removedNodes].some(
|
||||
node => SlCarouselItem.isCarouselItem(node) && !(node as HTMLElement).hasAttribute('data-clone')
|
||||
@@ -223,7 +223,7 @@ export default class SlCarousel extends ShoelaceElement {
|
||||
this.initializeSlides();
|
||||
}
|
||||
this.requestUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
@watch('loop', { waitUntilFirstUpdate: true })
|
||||
@watch('slidesPerPage', { waitUntilFirstUpdate: true })
|
||||
@@ -325,7 +325,15 @@ export default class SlCarousel extends ShoelaceElement {
|
||||
* @param behavior - The behavior used for scrolling.
|
||||
*/
|
||||
previous(behavior: ScrollBehavior = 'smooth') {
|
||||
this.goToSlide(this.activeSlide - this.slidesPerMove, behavior);
|
||||
let previousIndex = this.activeSlide || this.activeSlide - this.slidesPerMove;
|
||||
let canSnap = false;
|
||||
|
||||
while (!canSnap && previousIndex > 0) {
|
||||
previousIndex -= 1;
|
||||
canSnap = Math.abs(previousIndex - this.slidesPerMove) % this.slidesPerMove === 0;
|
||||
}
|
||||
|
||||
this.goToSlide(previousIndex, behavior);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { debounce } from 'src/internal/debounce';
|
||||
import { prefersReducedMotion } from 'src/internal/animate';
|
||||
import { waitForEvent } from 'src/internal/event';
|
||||
import { debounce } from '../../internal/debounce.js';
|
||||
import { prefersReducedMotion } from '../../internal/animate.js';
|
||||
import { waitForEvent } from '../../internal/event.js';
|
||||
import type { ReactiveController, ReactiveElement } from 'lit';
|
||||
|
||||
interface ScrollHost extends ReactiveElement {
|
||||
@@ -20,16 +20,7 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
|
||||
|
||||
constructor(host: T) {
|
||||
this.host = host;
|
||||
|
||||
host.addController(this);
|
||||
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
this.handlePointerDown = this.handlePointerDown.bind(this);
|
||||
this.handlePointerMove = this.handlePointerMove.bind(this);
|
||||
this.handlePointerUp = this.handlePointerUp.bind(this);
|
||||
this.handlePointerUp = this.handlePointerUp.bind(this);
|
||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
||||
}
|
||||
|
||||
async hostConnected() {
|
||||
@@ -58,13 +49,13 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
|
||||
scrollContainer.removeEventListener('touchend', this.handleTouchEnd);
|
||||
}
|
||||
|
||||
handleScroll() {
|
||||
handleScroll = () => {
|
||||
if (!this.scrolling) {
|
||||
this.scrolling = true;
|
||||
this.host.requestUpdate();
|
||||
}
|
||||
this.handleScrollEnd();
|
||||
}
|
||||
};
|
||||
|
||||
@debounce(100)
|
||||
handleScrollEnd() {
|
||||
@@ -84,7 +75,7 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerDown(event: PointerEvent) {
|
||||
handlePointerDown = (event: PointerEvent) => {
|
||||
if (event.pointerType === 'touch') {
|
||||
return;
|
||||
}
|
||||
@@ -97,9 +88,9 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
|
||||
|
||||
this.host.scrollContainer.addEventListener('pointermove', this.handlePointerMove);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handlePointerMove(event: PointerEvent) {
|
||||
handlePointerMove = (event: PointerEvent) => {
|
||||
const scrollContainer = this.host.scrollContainer;
|
||||
|
||||
const hasMoved = !!event.movementX || !!event.movementY;
|
||||
@@ -111,28 +102,28 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
|
||||
// Ignore pointers that we are not tracking
|
||||
this.handleDrag(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handlePointerUp(event: PointerEvent) {
|
||||
handlePointerUp = (event: PointerEvent) => {
|
||||
this.pointers.delete(event.pointerId);
|
||||
this.host.scrollContainer.releasePointerCapture(event.pointerId);
|
||||
|
||||
if (this.pointers.size === 0) {
|
||||
this.handleDragEnd();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleTouchEnd(event: TouchEvent) {
|
||||
handleTouchEnd = (event: TouchEvent) => {
|
||||
for (const touch of event.changedTouches) {
|
||||
this.pointers.delete(touch.identifier);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleTouchStart(event: TouchEvent) {
|
||||
handleTouchStart = (event: TouchEvent) => {
|
||||
for (const touch of event.touches) {
|
||||
this.pointers.add(touch.identifier);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleDragStart() {
|
||||
const host = this.host;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { aTimeout, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { clickOnElement } from '../../internal/test';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type SlCheckbox from './checkbox';
|
||||
import type SlCheckbox from './checkbox.js';
|
||||
|
||||
describe('<sl-checkbox>', () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import '../icon/icon';
|
||||
import '../icon/icon.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { defaultValue } from '../../internal/default-value';
|
||||
import { FormControlController } from '../../internal/form';
|
||||
import { defaultValue } from '../../internal/default-value.js';
|
||||
import { FormControlController } from '../../internal/form.js';
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { live } from 'lit/directives/live.js';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './checkbox.styles';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './checkbox.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
import type { ShoelaceFormControl } from '../../internal/shoelace-element';
|
||||
import type { ShoelaceFormControl } from '../../internal/shoelace-element.js';
|
||||
|
||||
/**
|
||||
* @summary Checkboxes allow the user to toggle an option on or off.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { clickOnElement } from '../../internal/test';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import { serialize } from '../../utilities/form';
|
||||
import { serialize } from '../../utilities/form.js';
|
||||
import sinon from 'sinon';
|
||||
import type SlColorPicker from './color-picker';
|
||||
import type SlColorPicker from './color-picker.js';
|
||||
|
||||
describe('<sl-color-picker>', () => {
|
||||
describe('when the value changes', () => {
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import '../button-group/button-group';
|
||||
import '../button/button';
|
||||
import '../dropdown/dropdown';
|
||||
import '../icon/icon';
|
||||
import '../input/input';
|
||||
import '../visually-hidden/visually-hidden';
|
||||
import { clamp } from '../../internal/math';
|
||||
import '../button-group/button-group.js';
|
||||
import '../button/button.js';
|
||||
import '../dropdown/dropdown.js';
|
||||
import '../icon/icon.js';
|
||||
import '../input/input.js';
|
||||
import '../visually-hidden/visually-hidden.js';
|
||||
import { clamp } from '../../internal/math.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { defaultValue } from '../../internal/default-value';
|
||||
import { drag } from '../../internal/drag';
|
||||
import { FormControlController } from '../../internal/form';
|
||||
import { defaultValue } from '../../internal/default-value.js';
|
||||
import { drag } from '../../internal/drag.js';
|
||||
import { FormControlController } from '../../internal/form.js';
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { LocalizeController } from '../../utilities/localize';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './color-picker.styles';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './color-picker.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
import type { ShoelaceFormControl } from '../../internal/shoelace-element';
|
||||
import type SlChangeEvent from '../../events/sl-change';
|
||||
import type SlDropdown from '../dropdown/dropdown';
|
||||
import type SlInput from '../input/input';
|
||||
import type SlInputEvent from '../../events/sl-input';
|
||||
import type { ShoelaceFormControl } from '../../internal/shoelace-element.js';
|
||||
import type SlChangeEvent from '../../events/sl-change.js';
|
||||
import type SlDropdown from '../dropdown/dropdown.js';
|
||||
import type SlInput from '../input/input.js';
|
||||
import type SlInputEvent from '../../events/sl-input.js';
|
||||
|
||||
const hasEyeDropper = 'EyeDropper' in window;
|
||||
|
||||
@@ -190,20 +190,12 @@ export default class SlColorPicker extends ShoelaceElement implements ShoelaceFo
|
||||
return this.input.validationMessage;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.handleFocusIn = this.handleFocusIn.bind(this);
|
||||
this.handleFocusOut = this.handleFocusOut.bind(this);
|
||||
constructor() {
|
||||
super();
|
||||
this.addEventListener('focusin', this.handleFocusIn);
|
||||
this.addEventListener('focusout', this.handleFocusOut);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener('focusin', this.handleFocusIn);
|
||||
this.removeEventListener('focusout', this.handleFocusOut);
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.input.updateComplete.then(() => {
|
||||
this.formControlController.updateValidity();
|
||||
@@ -222,15 +214,15 @@ export default class SlColorPicker extends ShoelaceElement implements ShoelaceFo
|
||||
});
|
||||
}
|
||||
|
||||
private handleFocusIn() {
|
||||
private handleFocusIn = () => {
|
||||
this.hasFocus = true;
|
||||
this.emit('sl-focus');
|
||||
}
|
||||
};
|
||||
|
||||
private handleFocusOut() {
|
||||
private handleFocusOut = () => {
|
||||
this.hasFocus = false;
|
||||
this.emit('sl-blur');
|
||||
}
|
||||
};
|
||||
|
||||
private handleFormatToggle() {
|
||||
const formats = ['hex', 'rgb', 'hsl', 'hsv'];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
// cspell:dictionaries lorem-ipsum
|
||||
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import '../icon/icon';
|
||||
import { animateTo, shimKeyframesHeightAuto, stopAnimations } from '../../internal/animate';
|
||||
import '../icon/icon.js';
|
||||
import { animateTo, shimKeyframesHeightAuto, stopAnimations } from '../../internal/animate.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
|
||||
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
|
||||
import { html } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize';
|
||||
import { waitForEvent } from '../../internal/event';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './details.styles';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { waitForEvent } from '../../internal/event.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './details.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
// cspell:dictionaries lorem-ipsum
|
||||
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, elementUpdated, expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { LitElement } from 'lit';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type SlDialog from './dialog';
|
||||
@@ -145,4 +147,124 @@ describe('<sl-dialog>', () => {
|
||||
|
||||
expect(el.open).to.be.false;
|
||||
});
|
||||
|
||||
// https://github.com/shoelace-style/shoelace/issues/1382
|
||||
it('should properly cycle through tabbable elements when sl-dialog is used in a shadowRoot', async () => {
|
||||
class AContainer extends LitElement {
|
||||
get dialog() {
|
||||
return this.shadowRoot?.querySelector('sl-dialog');
|
||||
}
|
||||
|
||||
openDialog() {
|
||||
this.dialog?.show();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<h1>Dialog Example</h1>
|
||||
<sl-dialog label="Dialog" class="dialog-overview">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
<br />
|
||||
<label><input type="checkbox" />A</label>
|
||||
<label><input type="checkbox" />B</label>
|
||||
<button>Button</button>
|
||||
</sl-dialog>
|
||||
|
||||
<sl-button @click=${this.openDialog}>Open Dialog</sl-button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.customElements.get('a-container')) {
|
||||
window.customElements.define('a-container', AContainer);
|
||||
}
|
||||
|
||||
const testCase = await fixture(html`
|
||||
<div>
|
||||
<a-container></a-container>
|
||||
|
||||
<p>
|
||||
Open the dialog, then use <kbd>Tab</kbd> to cycle through the inputs. Focus should be trapped, but it reaches
|
||||
things outside the dialog.
|
||||
</p>
|
||||
</div>
|
||||
`);
|
||||
|
||||
const container = testCase.querySelector('a-container');
|
||||
|
||||
if (!container) {
|
||||
throw Error('Could not find <a-container> element.');
|
||||
}
|
||||
|
||||
await elementUpdated(container);
|
||||
const dialog = container.shadowRoot?.querySelector('sl-dialog');
|
||||
|
||||
if (!dialog) {
|
||||
throw Error('Could not find <sl-dialog> element.');
|
||||
}
|
||||
|
||||
const closeButton = dialog.shadowRoot?.querySelector('sl-icon-button');
|
||||
const checkbox1 = dialog.querySelector("input[type='checkbox']");
|
||||
const checkbox2 = dialog.querySelectorAll("input[type='checkbox']")[1];
|
||||
const button = dialog.querySelector('button');
|
||||
|
||||
// Opens modal.
|
||||
const openModalButton = container.shadowRoot?.querySelector('sl-button');
|
||||
|
||||
if (openModalButton) openModalButton.click();
|
||||
|
||||
// Test tab cycling
|
||||
await pressTab();
|
||||
|
||||
expect(container.shadowRoot?.activeElement).to.equal(dialog);
|
||||
expect(dialog.shadowRoot?.activeElement).to.equal(closeButton);
|
||||
|
||||
await pressTab();
|
||||
expect(container.shadowRoot?.activeElement).to.equal(checkbox1);
|
||||
|
||||
await pressTab();
|
||||
expect(container.shadowRoot?.activeElement).to.equal(checkbox2);
|
||||
|
||||
await pressTab();
|
||||
expect(container.shadowRoot?.activeElement).to.equal(button);
|
||||
|
||||
await pressTab();
|
||||
expect(dialog.shadowRoot?.activeElement).to.equal(closeButton);
|
||||
|
||||
await pressTab();
|
||||
expect(container.shadowRoot?.activeElement).to.equal(checkbox1);
|
||||
|
||||
// Test Shift+Tab cycling
|
||||
|
||||
// I found these timeouts were needed for WebKit locally.
|
||||
await aTimeout(10);
|
||||
await sendKeys({ down: 'Shift' });
|
||||
await aTimeout(10);
|
||||
|
||||
await pressTab();
|
||||
expect(dialog.shadowRoot?.activeElement).to.equal(closeButton);
|
||||
|
||||
await pressTab();
|
||||
expect(container.shadowRoot?.activeElement).to.equal(button);
|
||||
|
||||
await pressTab();
|
||||
expect(container.shadowRoot?.activeElement).to.equal(checkbox2);
|
||||
|
||||
await pressTab();
|
||||
expect(container.shadowRoot?.activeElement).to.equal(checkbox1);
|
||||
|
||||
await pressTab();
|
||||
expect(dialog.shadowRoot?.activeElement).to.equal(closeButton);
|
||||
|
||||
// End shift+tab cycling
|
||||
await sendKeys({ up: 'Shift' });
|
||||
});
|
||||
});
|
||||
|
||||
// We wait 50ms just to give the browser some time to figure out the current focus.
|
||||
// 50 was the magic number I found locally :shrug:
|
||||
async function pressTab() {
|
||||
await aTimeout(50);
|
||||
await sendKeys({ press: 'Tab' });
|
||||
await aTimeout(50);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import '../icon-button/icon-button';
|
||||
import { animateTo, stopAnimations } from '../../internal/animate';
|
||||
import '../icon-button/icon-button.js';
|
||||
import { animateTo, stopAnimations } from '../../internal/animate.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
|
||||
import { HasSlotController } from '../../internal/slot';
|
||||
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { LocalizeController } from '../../utilities/localize';
|
||||
import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll';
|
||||
import { waitForEvent } from '../../internal/event';
|
||||
import { watch } from '../../internal/watch';
|
||||
import Modal from '../../internal/modal';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './dialog.styles';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll.js';
|
||||
import { waitForEvent } from '../../internal/event.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import Modal from '../../internal/modal.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './dialog.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
@@ -67,7 +67,7 @@ export default class SlDialog extends ShoelaceElement {
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'footer');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private modal: Modal;
|
||||
private modal = new Modal(this);
|
||||
private originalTrigger: HTMLElement | null;
|
||||
|
||||
@query('.dialog') dialog: HTMLElement;
|
||||
@@ -92,12 +92,6 @@ export default class SlDialog extends ShoelaceElement {
|
||||
*/
|
||||
@property({ attribute: 'no-header', type: Boolean, reflect: true }) noHeader = false;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this);
|
||||
this.modal = new Modal(this);
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.dialog.hidden = !this.open;
|
||||
|
||||
@@ -110,6 +104,7 @@ export default class SlDialog extends ShoelaceElement {
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.modal.deactivate();
|
||||
unlockBodyScrolling(this);
|
||||
}
|
||||
|
||||
@@ -136,12 +131,12 @@ export default class SlDialog extends ShoelaceElement {
|
||||
document.removeEventListener('keydown', this.handleDocumentKeyDown);
|
||||
}
|
||||
|
||||
private handleDocumentKeyDown(event: KeyboardEvent) {
|
||||
private handleDocumentKeyDown = (event: KeyboardEvent) => {
|
||||
if (this.open && event.key === 'Escape') {
|
||||
event.stopPropagation();
|
||||
this.requestClose('keyboard');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@watch('open', { waitUntilFirstUpdate: true })
|
||||
async handleOpenChange() {
|
||||
@@ -275,7 +270,7 @@ export default class SlDialog extends ShoelaceElement {
|
||||
aria-hidden=${this.open ? 'false' : 'true'}
|
||||
aria-label=${ifDefined(this.noHeader ? this.label : undefined)}
|
||||
aria-labelledby=${ifDefined(!this.noHeader ? 'title' : undefined)}
|
||||
tabindex="0"
|
||||
tabindex="-1"
|
||||
>
|
||||
${!this.noHeader
|
||||
? html`
|
||||
@@ -298,8 +293,10 @@ export default class SlDialog extends ShoelaceElement {
|
||||
</header>
|
||||
`
|
||||
: ''}
|
||||
|
||||
<slot part="body" class="dialog__body"></slot>
|
||||
${
|
||||
'' /* The tabindex="-1" is here because the body is technically scrollable if overflowing. However, if there's no focusable elements inside, you won't actually be able to scroll it via keyboard. */
|
||||
}
|
||||
<slot part="body" class="dialog__body" tabindex="-1"></slot>
|
||||
|
||||
<footer part="footer" class="dialog__footer">
|
||||
<slot name="footer"></slot>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
|
||||
import type SlDivider from './divider';
|
||||
import type SlDivider from './divider.js';
|
||||
|
||||
describe('<sl-divider>', () => {
|
||||
describe('defaults ', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { watch } from '../../internal/watch';
|
||||
import ShoelaceElement from '../../internal/shoelace-element';
|
||||
import styles from './divider.styles';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import ShoelaceElement from '../../internal/shoelace-element.js';
|
||||
import styles from './divider.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import '../../../dist/shoelace.js';
|
||||
// cspell:dictionaries lorem-ipsum
|
||||
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user