mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
Initial SSR implementation (#157)
* continued ssr work * continued ssr work * prettier * all components now rendering * everything finally works * fix type issues * working on breadcrumb * working on breadcrumb * radio group * convert all tests to ssr * prettier * test suite finally passing * add layout stuff * add changelog * fix TS issue * fix tests * fixing deploy stuff * get QR code displaying * fix tests * fix tests * prettier * condense hydration stuff * prettier * comment out range test * fixing issues * use base fixtures * fixing examples * dont vendor * fix import of hydration support * adding notes * add notesg * add ssr loader * fix build * prettier * add notes * add notes * prettier * fixing bundled stuff * remove cdn * remove cdn * prettier * fiixng tests * prettier * split jobs?? * prettier * fix build stuff * add reset mouse and await aTimeout * prettier * fix improper tests * prettier * bail on first * fix linting * only test form with client * redundancy on ssr-loader?? * maybe this will work * prettier * try callout now * fix form.test.ts * fix form.test.ts * prettier * fix forms * fix forms * try again * prettier * add some awaits * prettier * comment out broken SSR tests * prettier * comment out broken SSR tests * prettier * dont skip in CI * upgrade playwright to beta * prettier * try some trickery * try some trickery * await updateComplete * try to fix form.test.ts * import hydrateable elements 1 time * prettier * fix input defaultValue issues * fix form controls to behave like their native counterpartS * add changelog entry * prettier * fix unexpected behavior with range / button
This commit is contained in:
87
.github/workflows/node.js.yml
vendored
87
.github/workflows/node.js.yml
vendored
@@ -1,5 +1,36 @@
|
||||
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
# # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
# name: Node.js CI
|
||||
|
||||
# on:
|
||||
# push:
|
||||
# branches: [next]
|
||||
# pull_request:
|
||||
# branches: [next]
|
||||
|
||||
# jobs:
|
||||
# build:
|
||||
# runs-on: ubuntu-latest
|
||||
|
||||
# strategy:
|
||||
# matrix:
|
||||
# node-version: [20.x]
|
||||
# # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - name: Use Node.js ${{ matrix.node-version }}
|
||||
# uses: actions/setup-node@v4
|
||||
# with:
|
||||
# node-version: ${{ matrix.node-version }}
|
||||
# cache: 'npm'
|
||||
# - run: npm ci
|
||||
# - run: npx playwright install-deps
|
||||
# - run: npm run verify
|
||||
|
||||
# # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Node.js CI
|
||||
|
||||
@@ -10,21 +41,61 @@ on:
|
||||
branches: [next]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x]
|
||||
node-version: [20.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npx playwright install-deps
|
||||
- run: npm ci
|
||||
- run: npm run verify
|
||||
- run: npm run prettier && npm run lint
|
||||
|
||||
test_client:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm ci
|
||||
- run: npx playwright uninstall --all && npx playwright install --force chromium firefox webkit --with-deps
|
||||
- run: npm run build
|
||||
# --bail to fail on first failing test.
|
||||
- run: CSR_ONLY="true" npm run test -- --bail
|
||||
|
||||
test_ssr:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm ci
|
||||
- run: npx playwright uninstall --all && npx playwright install --force chromium firefox webkit --with-deps
|
||||
- run: npm run build
|
||||
- run: SSR_ONLY="true" npm run test -- --bail
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -3,9 +3,10 @@ _site
|
||||
.DS_Store
|
||||
package.json
|
||||
package-lock.json
|
||||
dist
|
||||
dist/
|
||||
dist-cdn/
|
||||
docs/public/pagefind
|
||||
node_modules
|
||||
src/react
|
||||
cdn
|
||||
.astro
|
||||
cdn/
|
||||
|
||||
@@ -7,7 +7,7 @@ import fs from 'fs';
|
||||
|
||||
const packageData = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||
const { name, description, version, author, homepage, license } = packageData;
|
||||
const outdir = 'dist';
|
||||
const outdir = 'dist-cdn';
|
||||
|
||||
function replace(string, terms) {
|
||||
terms.forEach(({ from, to }) => {
|
||||
@@ -162,7 +162,7 @@ export default {
|
||||
}),
|
||||
|
||||
customElementJetBrainsPlugin({
|
||||
outdir: './dist',
|
||||
outdir: './dist-cdn',
|
||||
excludeCss: true,
|
||||
packageJson: false,
|
||||
referencesTemplate: (_, tag) => {
|
||||
|
||||
@@ -11,6 +11,8 @@ import { searchPlugin } from './_utils/search.js';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { outlinePlugin } from './_utils/outline.js';
|
||||
import { getComponents } from './_utils/manifest.js';
|
||||
import litPlugin from '@lit-labs/eleventy-plugin-lit';
|
||||
|
||||
import process from 'process';
|
||||
|
||||
const packageData = JSON.parse(await readFile('./package.json', 'utf-8'));
|
||||
@@ -106,6 +108,26 @@ export default function (eleventyConfig) {
|
||||
])
|
||||
);
|
||||
|
||||
const omittedModules = [];
|
||||
|
||||
// problematic components:
|
||||
// animation (breaks on navigation + ssr with Turbo)
|
||||
// mutation-observer (why SSR this?)
|
||||
// resize-observer (why SSR this?)
|
||||
// tooltip (why SSR this?)
|
||||
|
||||
const componentModules = getComponents()
|
||||
// .filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1]))
|
||||
.map(component => {
|
||||
const name = component.tagName.split(/wa-/)[1];
|
||||
return `./dist/components/${name}/${name}.js`;
|
||||
});
|
||||
|
||||
eleventyConfig.addPlugin(litPlugin, {
|
||||
mode: 'worker',
|
||||
componentModules
|
||||
});
|
||||
|
||||
// Build the search index
|
||||
eleventyConfig.addPlugin(
|
||||
searchPlugin({
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<link rel="apple-touch-icon" href="/assets/images/app-icon.png">
|
||||
|
||||
{# Scripts #}
|
||||
{# Hydration stuff #}
|
||||
<script src="/assets/scripts/hydration-errors.js"></script>
|
||||
<link rel="stylesheet" href="/assets/styles/hydration-errors.css">
|
||||
<link rel="preconnect" href="https://cdn.jsdelivr.net">
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.4/+esm"></script>
|
||||
|
||||
@@ -26,7 +29,7 @@
|
||||
<script defer data-domain="backers.webawesome.com" src="https://plausible.io/js/script.js"></script>
|
||||
|
||||
{# Web Awesome #}
|
||||
<script type="module" src="/dist/webawesome.loader.js"></script>
|
||||
<script type="module" src="/dist/webawesome.ssr-loader.js"></script>
|
||||
<link rel="stylesheet" id="theme-stylesheet" href="/dist/themes/default.css" />
|
||||
<link rel="stylesheet" href="/dist/themes/applied.css" />
|
||||
<link id="color-stylesheet" rel="stylesheet" href="/dist/themes/color_standard.css" />
|
||||
@@ -128,5 +131,6 @@
|
||||
|
||||
{% include 'search.njk' %}
|
||||
</wa-page>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
128
docs/assets/scripts/hydration-errors.js
Normal file
128
docs/assets/scripts/hydration-errors.js
Normal file
@@ -0,0 +1,128 @@
|
||||
/** TODO: This should probably get abstracted into an actual package. This is listens to the "lit-hydration-error" and then will add a button to show a dialog of the diff. */
|
||||
(async () => {
|
||||
const hostname = new URL(document.baseURI).hostname;
|
||||
|
||||
// Only diff on localhost. We dont need to show hydration errors on main site. Only locally.
|
||||
if (hostname !== 'localhost') {
|
||||
return;
|
||||
}
|
||||
|
||||
const { diffLines } = await import('https://cdn.jsdelivr.net/npm/diff@5.2.0/+esm');
|
||||
const { getDiffableHTML } = await import(
|
||||
'https://cdn.jsdelivr.net/npm/@open-wc/semantic-dom-diff@0.20.1/get-diffable-html.js/+esm'
|
||||
);
|
||||
|
||||
function wrap(el, wrapper) {
|
||||
el.parentNode.insertBefore(wrapper, el);
|
||||
wrapper.appendChild(el);
|
||||
}
|
||||
|
||||
function handleLitHydrationError(e) {
|
||||
const element = e.target;
|
||||
const scratch = document.createElement('div');
|
||||
const node = element.cloneNode(true);
|
||||
scratch.append(node);
|
||||
document.body.append(scratch);
|
||||
customElements.upgrade(node);
|
||||
node.updateComplete.then(() => {
|
||||
// Render styles.
|
||||
const elementStyles = element.constructor.elementStyles;
|
||||
const finalStyles = [];
|
||||
if (elementStyles !== undefined && elementStyles.length > 0) {
|
||||
for (const style of elementStyles) {
|
||||
finalStyles.push(style.cssText);
|
||||
}
|
||||
}
|
||||
|
||||
let innerHTML = scratch.firstElementChild?.shadowRoot.innerHTML;
|
||||
|
||||
if (finalStyles?.length) {
|
||||
const styleTag = `<style>${finalStyles.join('\n')}</style>`;
|
||||
innerHTML = styleTag + '\n' + innerHTML;
|
||||
}
|
||||
|
||||
const clientHTML = getDiffableHTML(innerHTML);
|
||||
const serverHTML = getDiffableHTML(element.shadowRoot?.innerHTML);
|
||||
|
||||
const diffDebugger = document.createElement('div');
|
||||
diffDebugger.className = 'diff-debugger';
|
||||
|
||||
diffDebugger.innerHTML = `
|
||||
<button class="diff-dialog-toggle">
|
||||
Show Hydration Mismatch
|
||||
</button>
|
||||
<wa-dialog class="diff-dialog" with-header light-dismiss>
|
||||
<div class="diff-grid">
|
||||
<div>
|
||||
<div>Server</div>
|
||||
<pre class="diff-server"><code></code></pre>
|
||||
</div>
|
||||
<div>
|
||||
<div>Client</div>
|
||||
<pre class="diff-client"><code></code></pre>
|
||||
</div>
|
||||
<div>
|
||||
<div>Diff</div>
|
||||
<pre class="diff-viewer"><code></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</wa-dialog>
|
||||
`;
|
||||
|
||||
element.focus();
|
||||
wrap(element, diffDebugger);
|
||||
|
||||
diffDebugger.querySelector('.diff-server > code').textContent = serverHTML;
|
||||
diffDebugger.querySelector('.diff-client > code').textContent = clientHTML;
|
||||
const diffViewer = diffDebugger.querySelector('.diff-viewer > code');
|
||||
diffViewer.innerHTML = '';
|
||||
diffViewer.appendChild(
|
||||
createDiff({
|
||||
serverHTML,
|
||||
clientHTML
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function createDiff({ serverHTML, clientHTML }) {
|
||||
const diff = diffLines(serverHTML, clientHTML, {
|
||||
ignoreWhitespace: false,
|
||||
newLineIsToken: true
|
||||
});
|
||||
const fragment = document.createDocumentFragment();
|
||||
for (var i = 0; i < diff.length; i++) {
|
||||
if (diff[i].added && diff[i + 1] && diff[i + 1].removed) {
|
||||
var swap = diff[i];
|
||||
diff[i] = diff[i + 1];
|
||||
diff[i + 1] = swap;
|
||||
}
|
||||
|
||||
var node;
|
||||
if (diff[i].removed) {
|
||||
node = document.createElement('del');
|
||||
node.appendChild(document.createTextNode(diff[i].value));
|
||||
} else if (diff[i].added) {
|
||||
node = document.createElement('ins');
|
||||
node.appendChild(document.createTextNode(diff[i].value));
|
||||
} else {
|
||||
node = document.createTextNode(diff[i].value);
|
||||
}
|
||||
fragment.appendChild(node);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
function handleDialogToggle(e) {
|
||||
const button = e.composedPath().find(el => {
|
||||
return el.classList && el.classList.contains('diff-dialog-toggle');
|
||||
});
|
||||
|
||||
if (button) {
|
||||
button.parentElement.querySelector('.diff-dialog').open = true;
|
||||
}
|
||||
}
|
||||
document.addEventListener('lit-hydration-error', handleLitHydrationError);
|
||||
document.addEventListener('click', handleDialogToggle);
|
||||
})();
|
||||
@@ -43,6 +43,30 @@ function restoreScrollPosition(event) {
|
||||
});
|
||||
}
|
||||
|
||||
function fixDSD(e) {
|
||||
const newElement = e.detail.newBody || e.detail.newFrame || e.detail.newStream;
|
||||
if (!newElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill
|
||||
(function attachShadowRoots(root) {
|
||||
root.querySelectorAll('template[shadowrootmode]').forEach(template => {
|
||||
const mode = template.getAttribute('shadowrootmode');
|
||||
const shadowRoot = template.parentNode.attachShadow({ mode });
|
||||
shadowRoot.appendChild(template.content);
|
||||
template.remove();
|
||||
attachShadowRoots(shadowRoot);
|
||||
});
|
||||
})(newElement);
|
||||
}
|
||||
|
||||
// Fixes an issue with DSD keeping the `<template>` elements hanging around in the lightdom.
|
||||
// https://github.com/hotwired/turbo/issues/1292
|
||||
['turbo:before-render', 'turbo:before-stream-render', 'turbo:before-frame-render'].forEach(eventName => {
|
||||
document.addEventListener(eventName, fixDSD);
|
||||
});
|
||||
|
||||
window.addEventListener('turbo:before-cache', saveScrollPosition);
|
||||
window.addEventListener('turbo:before-render', restoreScrollPosition);
|
||||
window.addEventListener('turbo:render', restoreScrollPosition);
|
||||
|
||||
@@ -19,15 +19,6 @@ wa-page {
|
||||
--wa-flow-spacing: var(--wa-space-xl);
|
||||
}
|
||||
|
||||
wa-page[view='desktop'] [data-toggle-nav] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
wa-page[view='desktop'] .only-mobile,
|
||||
wa-page:not([view='desktop']) .only-desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
wa-page::part(header) {
|
||||
background-color: var(--wa-color-surface-default);
|
||||
@@ -92,19 +83,6 @@ wa-page > header {
|
||||
}
|
||||
}
|
||||
|
||||
/* Navigation sidebar */
|
||||
wa-page[view='desktop']::part(navigation) {
|
||||
border-right: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet);
|
||||
}
|
||||
|
||||
wa-page[view='desktop'] > #sidebar {
|
||||
overflow: auto;
|
||||
max-height: 100%;
|
||||
min-width: 300px;
|
||||
padding: var(--wa-space-xl);
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
#sidebar,
|
||||
#outline {
|
||||
h2 {
|
||||
@@ -165,10 +143,6 @@ wa-page > main {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
wa-page[view='desktop'] > main {
|
||||
padding: var(--wa-space-3xl);
|
||||
}
|
||||
|
||||
.component-info {
|
||||
margin-block-end: var(--wa-flow-spacing);
|
||||
}
|
||||
@@ -274,3 +248,35 @@ wa-page[view='desktop'] > main {
|
||||
.table-scroll {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/** mobile */
|
||||
@media screen and (max-width: 768px) {
|
||||
wa-page .only-desktop {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/** desktop */
|
||||
@media screen and not (max-width: 768px) {
|
||||
wa-page [data-toggle-nav],
|
||||
wa-page .only-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Navigation sidebar */
|
||||
wa-page::part(navigation) {
|
||||
border-right: var(--wa-border-style) var(--wa-panel-border-width) var(--wa-color-neutral-border-quiet);
|
||||
}
|
||||
|
||||
wa-page > #sidebar {
|
||||
overflow: auto;
|
||||
max-height: 100%;
|
||||
min-width: 300px;
|
||||
padding: var(--wa-space-xl);
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
wa-page > main {
|
||||
padding: var(--wa-space-3xl);
|
||||
}
|
||||
}
|
||||
|
||||
38
docs/assets/styles/hydration-errors.css
Normal file
38
docs/assets/styles/hydration-errors.css
Normal file
@@ -0,0 +1,38 @@
|
||||
/** TODO: This should probably get abstracted into an actual package. */
|
||||
.diff-dialog-toggle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.diff-debugger {
|
||||
position: relative;
|
||||
border: 4px solid red;
|
||||
}
|
||||
.diff-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-rows: minmax(0, 1fr);
|
||||
max-height: 80vh;
|
||||
gap: 16px;
|
||||
}
|
||||
.diff-grid > * {
|
||||
height: 100%;
|
||||
}
|
||||
.diff-dialog::part(dialog) {
|
||||
max-width: 90vw;
|
||||
width: 90vw;
|
||||
}
|
||||
.diff-dialog ins {
|
||||
text-decoration: none;
|
||||
}
|
||||
.diff-dialog pre {
|
||||
border: 1px solid transparent;
|
||||
height: 100%;
|
||||
max-height: calc(100% - 2em);
|
||||
}
|
||||
.diff-dialog code {
|
||||
height: 100%;
|
||||
}
|
||||
.diff-dialog del {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ layout: component
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
<form><wa-input></wa-input></form>
|
||||
<wa-input></wa-input>
|
||||
```
|
||||
|
||||
:::info
|
||||
|
||||
@@ -52,7 +52,7 @@ Use the `readonly` attribute to display a rating that users can't change.
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disable` attribute to disable the rating.
|
||||
Use the `disabled` attribute to disable the rating.
|
||||
|
||||
```html {.example}
|
||||
<wa-rating label="Rating" disabled value="3"></wa-rating>
|
||||
@@ -110,8 +110,12 @@ You can provide custom icons by passing a function to the `getSymbol` property.
|
||||
```html {.example}
|
||||
<wa-rating label="Rating" class="rating-hearts" style="--symbol-color-active: #ff4136;"></wa-rating>
|
||||
|
||||
<script>
|
||||
<script type="module">
|
||||
const rating = document.querySelector('.rating-hearts');
|
||||
|
||||
await customElements.whenDefined("wa-rating")
|
||||
await rating.updateComplete
|
||||
|
||||
rating.getSymbol = () => '<wa-icon name="heart" variant="solid"></wa-icon>';
|
||||
</script>
|
||||
```
|
||||
@@ -123,9 +127,12 @@ You can also use the `getSymbol` property to render different icons based on val
|
||||
```html {.example}
|
||||
<wa-rating label="Rating" class="rating-emojis"></wa-rating>
|
||||
|
||||
<script>
|
||||
<script type="module">
|
||||
const rating = document.querySelector('.rating-emojis');
|
||||
|
||||
await customElements.whenDefined("wa-rating")
|
||||
await rating.updateComplete
|
||||
|
||||
rating.getSymbol = value => {
|
||||
const icons = ['face-angry', 'face-frown', 'face-meh', 'face-smile', 'face-laugh'];
|
||||
return `<wa-icon name="${icons[value - 1]}"></wa-icon>`;
|
||||
|
||||
111
docs/docs/experimental/ssr.md
Normal file
111
docs/docs/experimental/ssr.md
Normal file
@@ -0,0 +1,111 @@
|
||||
---
|
||||
title: Server Side Rendering
|
||||
description: A document on how to get started with SSR in Web Awesome.
|
||||
layout: page
|
||||
---
|
||||
|
||||
## Caveats
|
||||
|
||||
SSR in Web Awesome is to be considered experimental. There are bugs and timing issues. There are things to iron out. Please bear with us. Part of the experimental status comes from Lit SSR being experimental, and all of our components originally being designed as client only.
|
||||
|
||||
## Adding hydration to the Frontend
|
||||
|
||||
If you're using the `webawesome.loader.js` file which automatically loads , make sure to change it to `webawesome.ssr-loader.js`
|
||||
|
||||
```diff
|
||||
- <script type="module" src="https://early.webawesome.com/webawesome@3.0.0-alpha.2/dist/webawesome.loader.js"></script>
|
||||
+ <script type="module" src="https://early.webawesome.com/webawesome@3.0.0-alpha.2/dist/webawesome.ssr-loader.js"></script>
|
||||
```
|
||||
|
||||
If you're using a bundler:
|
||||
|
||||
```js
|
||||
// Make sure this import is first.
|
||||
import "@lit-labs/ssr-client/lit-element-hydrate-support.js"
|
||||
|
||||
import "webawesome/dist/components/button/button.js"
|
||||
import "webawesome/dist/components/input/input.js"
|
||||
```
|
||||
|
||||
## Server rendering
|
||||
|
||||
SSR on your backend is largely dependent on what backend you're using.
|
||||
|
||||
For docs on how to hook up your backend, checkout this document from Lit:
|
||||
|
||||
<https://lit.dev/docs/ssr/server-usage/>
|
||||
|
||||
For example, here's roughly what the 11ty integration looks like:
|
||||
|
||||
```js
|
||||
// eleventy.config.js
|
||||
|
||||
import litPlugin from '@lit-labs/eleventy-plugin-lit';
|
||||
|
||||
eleventyConfig.addPlugin(litPlugin, {
|
||||
mode: 'worker',
|
||||
componentModules: [
|
||||
"webawesome/dist/components/button/button.js",
|
||||
"webawesome/dist/components/input/input.js"
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## Hydration
|
||||
|
||||
All Web Awesome components that get SSR'ed will have `did-ssr=""` on them.
|
||||
|
||||
For example: `<wa-button did-ssr="">`
|
||||
|
||||
This can help if you need some styling prior to the element connecting.
|
||||
|
||||
## Timing issues
|
||||
|
||||
Before setting any properties on your frontend, it is important to first wait for the element to be defined, and then wait for its update to complete.
|
||||
|
||||
```js
|
||||
const rating = document.querySelector("wa-rating")
|
||||
|
||||
// If we dont want for the component to be defined, and for the initial hydration to finish, we will get a hydration error from Lit.
|
||||
await customElements.whenDefined("wa-rating")
|
||||
await rating.updateComplete
|
||||
|
||||
rating.getSymbol = () => '<wa-icon name="heart" variant="solid"></wa-icon>';
|
||||
```
|
||||
|
||||
## Usage with Turbo
|
||||
|
||||
Turbo, the Hotwire library, has an issue with SSR + declarative shadow dom. To fix this, you can add the following.
|
||||
|
||||
```js
|
||||
function fixDeclarativeShadowDOM(e) {
|
||||
const newElement = e.detail.newBody || e.detail.newFrame || e.detail.newStream;
|
||||
if (!newElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill
|
||||
(function attachShadowRoots(root) {
|
||||
root.querySelectorAll('template[shadowrootmode]').forEach(template => {
|
||||
const mode = template.getAttribute('shadowrootmode');
|
||||
const shadowRoot = template.parentNode.attachShadow({ mode });
|
||||
shadowRoot.appendChild(template.content);
|
||||
template.remove();
|
||||
attachShadowRoots(shadowRoot);
|
||||
});
|
||||
})(newElement);
|
||||
}
|
||||
|
||||
// Fixes an issue with DSD keeping the `<template>` elements hanging around in the lightdom.
|
||||
// https://github.com/hotwired/turbo/issues/1292
|
||||
['turbo:before-render', 'turbo:before-stream-render', 'turbo:before-frame-render'].forEach(eventName => {
|
||||
document.addEventListener(eventName, fixDeclarativeShadowDOM);
|
||||
});
|
||||
```
|
||||
|
||||
## Additional TODOs
|
||||
|
||||
- [ ] - `@shoelace-style/localize` (our localization library) has no way to set a language currently so it always falls back to `en`.
|
||||
- [ ] - `<wa-icon>` has no fallback if there's no JS besides a blank `<svg>`. There's perhaps some backend mechanisms we can use to fetch. But requires altering APIs. Should also have a way to set height / widths, but we dont want to increase pain for SSR users.
|
||||
- [ ] - `<wa-qr-code>` QR Code will not error on the backend and will render a blank canvas at the appropriate size, but will not render the canvas until the client component connects.
|
||||
- [ ] - `setBasePath` and `kit codes` may need reconfiguring to work with SSR.
|
||||
@@ -14,6 +14,12 @@ During the alpha period, things might break! We take breaking changes very serio
|
||||
|
||||
## Next
|
||||
|
||||
- Fixed form controls to behave like their native counterparts for value and defaultValue properties / attributes respectively. [#157]
|
||||
- Fixed a bug in `<wa-input>` around value attributes and properties to behave like native `<input>`. [#157]
|
||||
- Added `scroll-margin-top` to children of `wa-page` [#157]
|
||||
- Added `--scroll-margin-top` css variable `wa-page` [#157]
|
||||
- Added SSR support to all components [#157]
|
||||
- Fixed a bug in `<wa-checkbox>` where unchecking and then checking would "clear" its value. [#157]
|
||||
- Fixed a bug where `<wa-relative-time>` would announce the full time instead of the relative time in screen readers [#22](https://github.com/shoelace-style/webawesome-alpha/issues/22)
|
||||
- Fixed a bug in `<wa-tab-group>` in Firefox where the overflow container would keep focus. [#14](https://github.com/shoelace-style/webawesome-alpha/issues/14)
|
||||
- Fixed a bug in `<wa-input>` where `minlength` and `maxlength` were not being properly validated. [#35](https://github.com/shoelace-style/webawesome-alpha/issues/35)
|
||||
|
||||
@@ -373,3 +373,17 @@ Guidelines for writing tests:
|
||||
- Try keeping the main test readable: Extract more complicated sets of selectors/commands/assertions into separate functions.
|
||||
- Try to aim testing the user facing features of the component instead of the internal workings of the component.
|
||||
- Group multiple tests for one feature into describe blocks.
|
||||
|
||||
### Running tests
|
||||
|
||||
Right now, tests run both "hydrated" (SSR -> client hydrated) and "client only". If you're debugging only one specific kind you can set an environment variable. For example, to run only the client tests, you can do:
|
||||
|
||||
```bash
|
||||
CSR_ONLY="true" npm run test
|
||||
```
|
||||
|
||||
or for hydrated rendering only:
|
||||
|
||||
```bash
|
||||
SSR_ONLY="true" npm run test
|
||||
```
|
||||
@@ -20,9 +20,14 @@ layout: page
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
wa-page:not([view="desktop"]) > main {
|
||||
--content-flow-spacing: 3rem;
|
||||
|
||||
/** this technically relies on insertion order. */
|
||||
@media screen and (max-width: 768px) {
|
||||
wa-page > main {
|
||||
--content-flow-spacing: 3rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.brand-font {
|
||||
font-family: cera-round-pro;
|
||||
}
|
||||
|
||||
0
index.html
Normal file
0
index.html
Normal file
417
package-lock.json
generated
417
package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"@ctrl/tinycolor": "^4.0.2",
|
||||
"@floating-ui/dom": "^1.5.3",
|
||||
"@shoelace-style/animations": "^1.1.0",
|
||||
"@shoelace-style/localize": "^3.1.2",
|
||||
"@shoelace-style/localize": "^3.2.1",
|
||||
"composed-offset-position": "^0.0.4",
|
||||
"lit": "^3.0.0",
|
||||
"qr-creator": "^1.0.0"
|
||||
@@ -20,6 +20,8 @@
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "3.0.0-alpha.5",
|
||||
"@custom-elements-manifest/analyzer": "^0.9.4",
|
||||
"@lit-labs/eleventy-plugin-lit": "^1.0.3",
|
||||
"@lit-labs/testing": "^0.2.4",
|
||||
"@lit/react": "^1.0.0",
|
||||
"@open-wc/testing": "^3.2.0",
|
||||
"@types/mocha": "^10.0.2",
|
||||
@@ -69,7 +71,7 @@
|
||||
"node-html-parser": "^6.1.13",
|
||||
"ora": "^8.0.1",
|
||||
"pascal-case": "^3.1.2",
|
||||
"playwright": "^1.42.0",
|
||||
"playwright": "^1.46.1",
|
||||
"plop": "^4.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prismjs": "^1.29.0",
|
||||
@@ -1571,10 +1573,223 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/eleventy-plugin-lit": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/eleventy-plugin-lit/-/eleventy-plugin-lit-1.0.3.tgz",
|
||||
"integrity": "sha512-24824crd4P0D2Y6fyfO2c0SyfU9x0vpRwxabypmp3bTXrNrTOZflREATL57Qa9EEgirZvA/ervKy5Y0WCkjGag==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr": "^3.1.8",
|
||||
"lit": "^2.7.0 || ^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/ssr": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr/-/ssr-3.2.2.tgz",
|
||||
"integrity": "sha512-He5TzeNPM9ECmVpgXRYmVlz0UA5YnzHlT43kyLi2Lu6mUidskqJVonk9W5K699+2DKhoXp8Ra4EJmHR6KrcW1Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-client": "^1.1.7",
|
||||
"@lit-labs/ssr-dom-shim": "^1.2.0",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"@parse5/tools": "^0.3.0",
|
||||
"@types/node": "^16.0.0",
|
||||
"enhanced-resolve": "^5.10.0",
|
||||
"lit": "^3.1.2",
|
||||
"lit-element": "^4.0.4",
|
||||
"lit-html": "^3.1.2",
|
||||
"node-fetch": "^3.2.8",
|
||||
"parse5": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=13.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/ssr-client": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-client/-/ssr-client-1.1.7.tgz",
|
||||
"integrity": "sha512-VvqhY/iif3FHrlhkzEPsuX/7h/NqnfxLwVf0p8ghNIlKegRyRqgeaJevZ57s/u/LiFyKgqksRP5n+LmNvpxN+A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit": "^3.1.2",
|
||||
"lit-html": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/ssr-dom-shim": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz",
|
||||
"integrity": "sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g=="
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz",
|
||||
"integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g=="
|
||||
},
|
||||
"node_modules/@lit-labs/ssr/node_modules/@types/node": {
|
||||
"version": "16.18.101",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.101.tgz",
|
||||
"integrity": "sha512-AAsx9Rgz2IzG8KJ6tXd6ndNkVcu+GYB6U/SnFAaokSPNx2N7dcIIfnighYUNumvj6YS2q39Dejz5tT0NCV7CWA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@lit-labs/ssr/node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/ssr/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/ssr/node_modules/node-fetch": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/ssr/node_modules/parse5": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
|
||||
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"entities": "^4.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/testing": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/testing/-/testing-0.2.4.tgz",
|
||||
"integrity": "sha512-NasNKbELasyfA1vIcfMwM0H/2mE98uFsyf/yDWtcl9fAEsTpRRWrmPdQDrHDyim5LKnsQutCzBP3Fof83hSCIA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr": "^3.1.8",
|
||||
"@lit-labs/ssr-client": "^1.1.4",
|
||||
"@web/test-runner-commands": "^0.6.1",
|
||||
"@webcomponents/template-shadowroot": "^0.1.0",
|
||||
"lit": "^2.0.0 || ^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/testing/node_modules/@web/browser-logs": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@web/browser-logs/-/browser-logs-0.2.6.tgz",
|
||||
"integrity": "sha512-CNjNVhd4FplRY8PPWIAt02vAowJAVcOoTNrR/NNb/o9pka7yI9qdjpWrWhEbPr2pOXonWb52AeAgdK66B8ZH7w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"errorstacks": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/testing/node_modules/@web/test-runner-commands": {
|
||||
"version": "0.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@web/test-runner-commands/-/test-runner-commands-0.6.6.tgz",
|
||||
"integrity": "sha512-2DcK/+7f8QTicQpGFq/TmvKHDK/6Zald6rn1zqRlmj3pcH8fX6KHNVMU60Za9QgAKdorMBPfd8dJwWba5otzdw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@web/test-runner-core": "^0.10.29",
|
||||
"mkdirp": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/testing/node_modules/@web/test-runner-core": {
|
||||
"version": "0.10.29",
|
||||
"resolved": "https://registry.npmjs.org/@web/test-runner-core/-/test-runner-core-0.10.29.tgz",
|
||||
"integrity": "sha512-0/ZALYaycEWswHhpyvl5yqo0uIfCmZe8q14nGPi1dMmNiqLcHjyFGnuIiLexI224AW74ljHcHllmDlXK9FUKGA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.12.11",
|
||||
"@types/babel__code-frame": "^7.0.2",
|
||||
"@types/co-body": "^6.1.0",
|
||||
"@types/convert-source-map": "^2.0.0",
|
||||
"@types/debounce": "^1.2.0",
|
||||
"@types/istanbul-lib-coverage": "^2.0.3",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@web/browser-logs": "^0.2.6",
|
||||
"@web/dev-server-core": "^0.4.1",
|
||||
"chokidar": "^3.4.3",
|
||||
"cli-cursor": "^3.1.0",
|
||||
"co-body": "^6.1.0",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debounce": "^1.2.0",
|
||||
"dependency-graph": "^0.11.0",
|
||||
"globby": "^11.0.1",
|
||||
"ip": "^1.1.5",
|
||||
"istanbul-lib-coverage": "^3.0.0",
|
||||
"istanbul-lib-report": "^3.0.0",
|
||||
"istanbul-reports": "^3.0.2",
|
||||
"log-update": "^4.0.0",
|
||||
"nanocolors": "^0.2.1",
|
||||
"nanoid": "^3.1.25",
|
||||
"open": "^8.0.2",
|
||||
"picomatch": "^2.2.2",
|
||||
"source-map": "^0.7.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/testing/node_modules/globby": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
|
||||
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array-union": "^2.1.0",
|
||||
"dir-glob": "^3.0.1",
|
||||
"fast-glob": "^3.2.9",
|
||||
"ignore": "^5.2.0",
|
||||
"merge2": "^1.4.1",
|
||||
"slash": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/testing/node_modules/ip": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@lit-labs/testing/node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit/react": {
|
||||
"version": "1.0.2",
|
||||
@@ -1586,11 +1801,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@lit/reactive-element": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.3.tgz",
|
||||
"integrity": "sha512-e067EuTNNgOHm1tZcc0Ia7TCzD/9ZpoPegHKgesrGK6pSDRGkGDAQbYuQclqLPIoJ9eC8Kb9mYtGryWcM5AywA==",
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
|
||||
"integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.1.2"
|
||||
"@lit-labs/ssr-dom-shim": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ljharb/through": {
|
||||
@@ -1697,6 +1912,39 @@
|
||||
"lit-html": "^2.0.0 || ^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@parse5/tools": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@parse5/tools/-/tools-0.3.0.tgz",
|
||||
"integrity": "sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"parse5": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@parse5/tools/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parse5/tools/node_modules/parse5": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
|
||||
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"entities": "^4.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@puppeteer/browsers": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.1.0.tgz",
|
||||
@@ -1976,9 +2224,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shoelace-style/localize": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@shoelace-style/localize/-/localize-3.1.2.tgz",
|
||||
"integrity": "sha512-Hf45HeO+vdQblabpyZOTxJ4ZeZsmIUYXXPmoYrrR4OJ5OKxL+bhMz5mK8JXgl7HsoEowfz7+e248UGi861de9Q=="
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@shoelace-style/localize/-/localize-3.2.1.tgz",
|
||||
"integrity": "sha512-r4C9C/5kSfMBIr0D9imvpRdCNXtUNgyYThc4YlS6K5Hchv1UyxNQ9mxwj+BTRH2i1Neits260sR3OjKMnplsFA=="
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
"version": "0.7.0",
|
||||
@@ -3701,6 +3949,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@webcomponents/template-shadowroot": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@webcomponents/template-shadowroot/-/template-shadowroot-0.1.0.tgz",
|
||||
"integrity": "sha512-ry84Vft6xtRBbd4M/ptRodbOLodV5AD15TYhyRghCRgIcJJKmYmJ2v2BaaWxygENwh6Uq3zTfGPmlckKT/GXsQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/a-sync-waterfall": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
||||
@@ -6189,9 +6443,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
|
||||
"integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
|
||||
"integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
@@ -6591,6 +6845,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.17.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz",
|
||||
"integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
|
||||
@@ -7638,6 +7905,29 @@
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/figures": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz",
|
||||
@@ -7893,6 +8183,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
@@ -10284,29 +10586,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lit": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lit/-/lit-3.1.1.tgz",
|
||||
"integrity": "sha512-hF1y4K58+Gqrz+aAPS0DNBwPqPrg6P04DuWK52eMkt/SM9Qe9keWLcFgRcEKOLuDlRZlDsDbNL37Vr7ew1VCuw==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lit/-/lit-3.2.0.tgz",
|
||||
"integrity": "sha512-s6tI33Lf6VpDu7u4YqsSX78D28bYQulM+VAzsGch4fx2H0eLZnJsUBsPWmGYSGoKDNbjtRv02rio1o+UdPVwvw==",
|
||||
"dependencies": {
|
||||
"@lit/reactive-element": "^2.0.0",
|
||||
"lit-element": "^4.0.0",
|
||||
"lit-html": "^3.1.0"
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit-element": "^4.1.0",
|
||||
"lit-html": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-element": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.3.tgz",
|
||||
"integrity": "sha512-2vhidmC7gGLfnVx41P8UZpzyS0Fb8wYhS5RCm16cMW3oERO0Khd3EsKwtRpOnttuByI5rURjT2dfoA7NlInCNw==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.0.tgz",
|
||||
"integrity": "sha512-gSejRUQJuMQjV2Z59KAS/D4iElUhwKpIyJvZ9w+DIagIQjfJnhR20h2Q5ddpzXGS+fF0tMZ/xEYGMnKmaI/iww==",
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.1.2",
|
||||
"@lit/reactive-element": "^2.0.0",
|
||||
"lit-html": "^3.1.0"
|
||||
"@lit-labs/ssr-dom-shim": "^1.2.0",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit-html": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-html": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.1.tgz",
|
||||
"integrity": "sha512-x/EwfGk2D/f4odSFM40hcGumzqoKv0/SUh6fBO+1Ragez81APrcAMPo1jIrCDd9Sn+Z4CT867HWKViByvkDZUA==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.0.tgz",
|
||||
"integrity": "sha512-pwT/HwoxqI9FggTrYVarkBKFN9MlTUpLrDHubTmW4SrkL3kkqW5gxwbxMMUnbbRHBC0WTZnYHcjDSCM559VyfA==",
|
||||
"dependencies": {
|
||||
"@types/trusted-types": "^2.0.2"
|
||||
}
|
||||
@@ -11442,6 +11744,25 @@
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
@@ -12449,33 +12770,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.42.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.0.tgz",
|
||||
"integrity": "sha512-Ko7YRUgj5xBHbntrgt4EIw/nE//XBHOKVKnBjO1KuZkmkhlbgyggTe5s9hjqQ1LpN+Xg+kHsQyt5Pa0Bw5XpvQ==",
|
||||
"version": "1.46.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.1.tgz",
|
||||
"integrity": "sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0"
|
||||
"playwright-core": "1.46.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.42.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.0.tgz",
|
||||
"integrity": "sha512-0HD9y8qEVlcbsAjdpBaFjmaTHf+1FeIddy8VJLeiqwhcNqGCBe4Wp2e8knpqiYbzxtxarxiXyNDw2cG8sCaNMQ==",
|
||||
"version": "1.46.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.1.tgz",
|
||||
"integrity": "sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright/node_modules/fsevents": {
|
||||
@@ -14480,6 +14801,15 @@
|
||||
"node": ">=12.17"
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
|
||||
@@ -15068,6 +15398,15 @@
|
||||
"defaults": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
"@ctrl/tinycolor": "^4.0.2",
|
||||
"@floating-ui/dom": "^1.5.3",
|
||||
"@shoelace-style/animations": "^1.1.0",
|
||||
"@shoelace-style/localize": "^3.1.2",
|
||||
"@shoelace-style/localize": "^3.2.1",
|
||||
"composed-offset-position": "^0.0.4",
|
||||
"lit": "^3.0.0",
|
||||
"qr-creator": "^1.0.0"
|
||||
@@ -74,6 +74,8 @@
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "3.0.0-alpha.5",
|
||||
"@custom-elements-manifest/analyzer": "^0.9.4",
|
||||
"@lit-labs/eleventy-plugin-lit": "^1.0.3",
|
||||
"@lit-labs/testing": "^0.2.4",
|
||||
"@lit/react": "^1.0.0",
|
||||
"@open-wc/testing": "^3.2.0",
|
||||
"@types/mocha": "^10.0.2",
|
||||
@@ -123,7 +125,7 @@
|
||||
"node-html-parser": "^6.1.13",
|
||||
"ora": "^8.0.1",
|
||||
"pascal-case": "^3.1.2",
|
||||
"playwright": "^1.42.0",
|
||||
"playwright": "^1.46.1",
|
||||
"plop": "^4.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prismjs": "^1.29.0",
|
||||
@@ -136,6 +138,9 @@
|
||||
"user-agent-data-types": "^0.3.1",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"overrides": {
|
||||
"playwright": "^1.46.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,js}": [
|
||||
"eslint --max-warnings 0 --cache --fix",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { deleteAsync } from 'del';
|
||||
import { dirname, join, relative } from 'path';
|
||||
import { distDir, docsDir, rootDir, runScript, siteDir } from './utils.js';
|
||||
import { distDir, docsDir, cdnDir, rootDir, runScript, siteDir } from './utils.js';
|
||||
import { execSync } from 'child_process';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { globby } from 'globby';
|
||||
@@ -14,17 +14,16 @@ import getPort, { portNumbers } from 'get-port';
|
||||
import ora from 'ora';
|
||||
import process from 'process';
|
||||
|
||||
//
|
||||
// TODO - CDN dist and unbundled dist
|
||||
//
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const isDeveloping = process.argv.includes('--develop');
|
||||
const isAlpha = process.argv.includes('--alpha');
|
||||
const spinner = ora({ text: 'Web Awesome', color: 'cyan' }).start();
|
||||
const packageData = JSON.parse(await readFile(join(rootDir, 'package.json'), 'utf-8'));
|
||||
const version = JSON.stringify(packageData.version.toString());
|
||||
let buildContext;
|
||||
let buildContexts = {
|
||||
bundledContext: {},
|
||||
unbundledContext: {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs the full build.
|
||||
@@ -38,6 +37,10 @@ async function buildAll() {
|
||||
await generateReactWrappers();
|
||||
await generateTypes();
|
||||
await generateStyles();
|
||||
|
||||
// copy everything to unbundled before we generate bundles.
|
||||
await copy(cdnDir, distDir, { overwrite: true });
|
||||
|
||||
await generateBundle();
|
||||
await generateDocs();
|
||||
|
||||
@@ -54,7 +57,9 @@ async function cleanup() {
|
||||
spinner.start('Cleaning up dist');
|
||||
|
||||
await deleteAsync(distDir);
|
||||
await deleteAsync(cdnDir);
|
||||
await mkdir(distDir, { recursive: true });
|
||||
await mkdir(cdnDir, { recursive: true });
|
||||
|
||||
spinner.succeed();
|
||||
}
|
||||
@@ -83,7 +88,7 @@ function generateReactWrappers() {
|
||||
spinner.start('Generating React wrappers');
|
||||
|
||||
try {
|
||||
execSync(`node scripts/make-react.js --outdir "${distDir}"`, { stdio: 'inherit' });
|
||||
execSync(`node scripts/make-react.js --outdir "${cdnDir}"`, { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
console.error(`\n\n${error.message}`);
|
||||
}
|
||||
@@ -100,13 +105,13 @@ async function generateStyles() {
|
||||
|
||||
// NOTE - alpha setting omits all stylesheets except for these because we use them in the docs
|
||||
if (isAlpha) {
|
||||
await copy(join(rootDir, 'src/themes/applied.css'), join(distDir, 'themes/applied.css'), { overwrite: true });
|
||||
await copy(join(rootDir, 'src/themes/color_standard.css'), join(distDir, 'themes/color_standard.css'), {
|
||||
await copy(join(rootDir, 'src/themes/applied.css'), join(cdnDir, 'themes/applied.css'), { overwrite: true });
|
||||
await copy(join(rootDir, 'src/themes/color_standard.css'), join(cdnDir, 'themes/color_standard.css'), {
|
||||
overwrite: true
|
||||
});
|
||||
await copy(join(rootDir, 'src/themes/default.css'), join(distDir, 'themes/default.css'), { overwrite: true });
|
||||
await copy(join(rootDir, 'src/themes/default.css'), join(cdnDir, 'themes/default.css'), { overwrite: true });
|
||||
} else {
|
||||
await copy(join(rootDir, 'src/themes'), join(distDir, 'themes'), { overwrite: true });
|
||||
await copy(join(rootDir, 'src/themes'), join(cdnDir, 'themes'), { overwrite: true });
|
||||
}
|
||||
|
||||
spinner.succeed();
|
||||
@@ -121,7 +126,7 @@ async function generateTypes() {
|
||||
spinner.start('Running the TypeScript compiler');
|
||||
|
||||
try {
|
||||
execSync(`tsc --project ./tsconfig.prod.json --outdir "${distDir}"`);
|
||||
execSync(`tsc --project ./tsconfig.prod.json --outdir "${cdnDir}"`);
|
||||
} catch (error) {
|
||||
return Promise.reject(error.stdout);
|
||||
}
|
||||
@@ -137,6 +142,7 @@ async function generateTypes() {
|
||||
async function generateBundle() {
|
||||
spinner.start('Bundling with esbuild');
|
||||
|
||||
// Bundled config
|
||||
const config = {
|
||||
format: 'esm',
|
||||
target: 'es2020',
|
||||
@@ -148,6 +154,7 @@ async function generateBundle() {
|
||||
'./src/webawesome.ts',
|
||||
// Autoloader + utilities
|
||||
'./src/webawesome.loader.ts',
|
||||
'./src/webawesome.ssr-loader.ts',
|
||||
// Individual components
|
||||
...(await globby('./src/components/**/!(*.(style|test)).ts')),
|
||||
// Translations
|
||||
@@ -155,23 +162,41 @@ async function generateBundle() {
|
||||
// React wrappers
|
||||
...(await globby('./src/react/**/*.ts'))
|
||||
],
|
||||
outdir: distDir,
|
||||
outdir: cdnDir,
|
||||
chunkNames: 'chunks/[name].[hash]',
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"' // required by Floating UI
|
||||
},
|
||||
bundle: true,
|
||||
splitting: true,
|
||||
minify: false,
|
||||
plugins: [replace({ __WEBAWESOME_VERSION__: version })]
|
||||
};
|
||||
|
||||
const unbundledConfig = {
|
||||
...config,
|
||||
splitting: true,
|
||||
treeShaking: true,
|
||||
// Don't inline libraries like Lit etc.
|
||||
packages: 'external',
|
||||
outdir: distDir
|
||||
};
|
||||
|
||||
try {
|
||||
if (isDeveloping) {
|
||||
// Incremental builds for dev
|
||||
buildContext = await esbuild.context(config);
|
||||
await buildContext.rebuild();
|
||||
buildContexts.bundledContext = await esbuild.context(config);
|
||||
buildContexts.unbundledContext = await esbuild.context(unbundledConfig);
|
||||
|
||||
await buildContexts.bundledContext.rebuild();
|
||||
await buildContexts.unbundledContext.rebuild();
|
||||
} else {
|
||||
// One-time build for production
|
||||
await esbuild.build(config);
|
||||
await esbuild.build(unbundledConfig);
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail();
|
||||
console.log(chalk.red(`\n${error}`));
|
||||
}
|
||||
|
||||
spinner.succeed();
|
||||
@@ -183,7 +208,8 @@ async function generateBundle() {
|
||||
async function regenerateBundle() {
|
||||
try {
|
||||
spinner.start('Re-bundling with esbuild');
|
||||
await buildContext.rebuild();
|
||||
await buildContexts.bundledContext.rebuild();
|
||||
await buildContexts.unbundledContext.rebuild();
|
||||
} catch (error) {
|
||||
spinner.fail();
|
||||
console.log(chalk.red(`\n${error}`));
|
||||
@@ -216,7 +242,7 @@ async function generateDocs() {
|
||||
|
||||
// Copy dist (production only)
|
||||
if (!isDeveloping) {
|
||||
await copy(distDir, join(siteDir, 'dist'));
|
||||
await copy(cdnDir, join(siteDir, 'dist'));
|
||||
}
|
||||
|
||||
spinner.succeed(`Writing the docs ${chalk.gray(`(${output}`)})`);
|
||||
@@ -256,7 +282,7 @@ if (isDeveloping) {
|
||||
server: {
|
||||
baseDir: siteDir,
|
||||
routes: {
|
||||
'/dist': './dist'
|
||||
'/dist/': './dist-cdn/'
|
||||
}
|
||||
},
|
||||
callbacks: {
|
||||
@@ -330,9 +356,8 @@ if (isDeveloping) {
|
||||
// Cleanup everything when the process terminates
|
||||
//
|
||||
function terminate() {
|
||||
if (buildContext) {
|
||||
buildContext.dispose();
|
||||
}
|
||||
// dispose of contexts.
|
||||
Object.values(buildContexts).forEach(context => context?.dispose?.());
|
||||
|
||||
if (spinner) {
|
||||
spinner.stop();
|
||||
|
||||
@@ -7,6 +7,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
// Helpful directories
|
||||
export const rootDir = dirname(__dirname);
|
||||
export const distDir = join(rootDir, 'dist');
|
||||
export const cdnDir = join(rootDir, 'dist-cdn');
|
||||
export const docsDir = join(rootDir, 'docs');
|
||||
export const siteDir = join(rootDir, '_site');
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { clientFixture } from '../../internal/test/fixture.js';
|
||||
import { expect, oneEvent } from '@open-wc/testing';
|
||||
import { html } from 'lit';
|
||||
import type WaAnimatedImage from './animated-image.js';
|
||||
|
||||
describe('<wa-animated-image>', () => {
|
||||
// @TODO: Figure out why hydrated tests are failing
|
||||
for (const fixture of [clientFixture]) {
|
||||
it('should render a component', async () => {
|
||||
const animatedImage = await fixture(html` <wa-animated-image></wa-animated-image> `);
|
||||
|
||||
@@ -62,6 +66,7 @@ describe('<wa-animated-image>', () => {
|
||||
|
||||
await errorPromise;
|
||||
});
|
||||
}
|
||||
});
|
||||
async function loadImage(animatedImage: WaAnimatedImage, file: string) {
|
||||
const loadingPromise = oneEvent(animatedImage, 'wa-load');
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
import { aTimeout, expect, fixture, oneEvent } from '@open-wc/testing';
|
||||
import { aTimeout, expect, oneEvent } from '@open-wc/testing';
|
||||
import {
|
||||
clientFixture
|
||||
// hydratedFixture
|
||||
} from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
import type WaAnimation from './animation.js';
|
||||
|
||||
describe('<wa-animation>', () => {
|
||||
const boxToAnimate = html`<div style="width: 10px; height: 10px;" data-testid="animated-box"></div>`;
|
||||
// Don't use HTML because its not supported by Lit SSR for WTR.
|
||||
// https://github.com/lit/lit/issues/4739#issuecomment-2299899990
|
||||
const boxToAnimate = `<div style="width: 10px; height: 10px;" data-testid="animated-box"></div>`;
|
||||
|
||||
// Figure out why hydratedFixture fails promises.
|
||||
for (const fixture of [clientFixture]) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('renders', async () => {
|
||||
const animationContainer = await fixture<WaAnimation>(html`<wa-animation>${boxToAnimate}</wa-animation>`);
|
||||
const animationContainer = await fixture<WaAnimation>(
|
||||
html`<wa-animation>${unsafeHTML(boxToAnimate)}</wa-animation>`
|
||||
);
|
||||
|
||||
expect(animationContainer).to.exist;
|
||||
});
|
||||
|
||||
it('is accessible', async () => {
|
||||
const animationContainer = await fixture<WaAnimation>(html`<wa-animation>${boxToAnimate}</wa-animation>`);
|
||||
const animationContainer = await fixture<WaAnimation>(
|
||||
html`<wa-animation>${unsafeHTML(boxToAnimate)}</wa-animation>`
|
||||
);
|
||||
|
||||
await expect(animationContainer).to.be.accessible();
|
||||
});
|
||||
@@ -20,7 +34,9 @@ describe('<wa-animation>', () => {
|
||||
describe('animation start', () => {
|
||||
it('does not start the animation by default', async () => {
|
||||
const animationContainer = await fixture<WaAnimation>(
|
||||
html`<wa-animation name="bounce" easing="ease-in-out" duration="10">${boxToAnimate}</wa-animation>`
|
||||
html`<wa-animation name="bounce" easing="ease-in-out" duration="10"
|
||||
>${unsafeHTML(boxToAnimate)}</wa-animation
|
||||
>`
|
||||
);
|
||||
await aTimeout(0);
|
||||
|
||||
@@ -29,18 +45,21 @@ describe('<wa-animation>', () => {
|
||||
|
||||
it('emits the correct event on animation start', async () => {
|
||||
const animationContainer = await fixture<WaAnimation>(
|
||||
html`<wa-animation name="bounce" easing="ease-in-out" duration="10">${boxToAnimate}</wa-animation>`
|
||||
html`<wa-animation name="bounce" easing="ease-in-out" duration="10"
|
||||
>${unsafeHTML(boxToAnimate)}</wa-animation
|
||||
>`
|
||||
);
|
||||
|
||||
const startPromise = oneEvent(animationContainer, 'wa-start');
|
||||
animationContainer.play = true;
|
||||
return startPromise;
|
||||
const isSettled = (await Promise.allSettled([startPromise]))[0].status === 'fulfilled';
|
||||
expect(isSettled).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('emits the correct event on animation end', async () => {
|
||||
const animationContainer = await fixture<WaAnimation>(
|
||||
html`<wa-animation name="bounce" easing="ease-in-out" duration="1">${boxToAnimate}</wa-animation>`
|
||||
html`<wa-animation name="bounce" easing="ease-in-out" duration="1">${unsafeHTML(boxToAnimate)}</wa-animation>`
|
||||
);
|
||||
|
||||
const endPromise = oneEvent(animationContainer, 'wa-finish');
|
||||
@@ -51,7 +70,9 @@ describe('<wa-animation>', () => {
|
||||
|
||||
it('can be finished by hand', async () => {
|
||||
const animationContainer = await fixture<WaAnimation>(
|
||||
html`<wa-animation name="bounce" easing="ease-in-out" duration="1000">${boxToAnimate}</wa-animation>`
|
||||
html`<wa-animation name="bounce" easing="ease-in-out" duration="1000"
|
||||
>${unsafeHTML(boxToAnimate)}</wa-animation
|
||||
>`
|
||||
);
|
||||
|
||||
const endPromise = oneEvent(animationContainer, 'wa-finish');
|
||||
@@ -66,7 +87,7 @@ describe('<wa-animation>', () => {
|
||||
|
||||
it('can be cancelled', async () => {
|
||||
const animationContainer = await fixture<WaAnimation>(
|
||||
html`<wa-animation name="bounce" easing="ease-in-out" duration="1">${boxToAnimate}</wa-animation>`
|
||||
html`<wa-animation name="bounce" easing="ease-in-out" duration="1">${unsafeHTML(boxToAnimate)}</wa-animation>`
|
||||
);
|
||||
let animationHasFinished = false;
|
||||
oneEvent(animationContainer, 'wa-finish').then(() => (animationHasFinished = true));
|
||||
@@ -79,4 +100,6 @@ describe('<wa-animation>', () => {
|
||||
await cancelPromise;
|
||||
expect(animationHasFinished).to.be.false;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { aTimeout, expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, waitUntil } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaAvatar from './avatar.js';
|
||||
|
||||
// The default avatar background just misses AA contrast, but the next step up is way too dark. Since avatars aren't
|
||||
@@ -8,8 +10,10 @@ const ignoredRules = ['color-contrast'];
|
||||
describe('<wa-avatar>', () => {
|
||||
let el: WaAvatar;
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('when provided no parameters', () => {
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaAvatar>(html` <wa-avatar label="Avatar"></wa-avatar> `);
|
||||
});
|
||||
|
||||
@@ -27,7 +31,7 @@ describe('<wa-avatar>', () => {
|
||||
describe('when provided an image and label parameter', () => {
|
||||
const image = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
const label = 'Small transparent square';
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaAvatar>(html`<wa-avatar image="${image}" label="${label}"></wa-avatar>`);
|
||||
});
|
||||
|
||||
@@ -57,7 +61,7 @@ describe('<wa-avatar>', () => {
|
||||
|
||||
describe('when provided initials parameter', () => {
|
||||
const initials = 'SL';
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaAvatar>(html`<wa-avatar initials="${initials}" label="Avatar"></wa-avatar>`);
|
||||
});
|
||||
|
||||
@@ -76,7 +80,7 @@ describe('<wa-avatar>', () => {
|
||||
const initials = 'SL';
|
||||
const image = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
const label = 'Small transparent square';
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaAvatar>(
|
||||
html`<wa-avatar image="${image}" label="${label}" initials="${initials}"></wa-avatar>`
|
||||
);
|
||||
@@ -114,7 +118,7 @@ describe('<wa-avatar>', () => {
|
||||
|
||||
['square', 'rounded', 'circle'].forEach(shape => {
|
||||
describe(`when passed a shape attribute ${shape}`, () => {
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaAvatar>(html`<wa-avatar shape="${shape}" label="Shaped avatar"></wa-avatar>`);
|
||||
});
|
||||
|
||||
@@ -132,8 +136,10 @@ describe('<wa-avatar>', () => {
|
||||
});
|
||||
|
||||
describe('when passed a <span>, on slot "icon"', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaAvatar>(html`<wa-avatar label="Avatar"><span slot="icon">random content</span></wa-avatar>`);
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaAvatar>(
|
||||
html`<wa-avatar label="Avatar"><span slot="icon">random content</span></wa-avatar>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
@@ -156,6 +162,7 @@ describe('<wa-avatar>', () => {
|
||||
el.image = 'bad_image';
|
||||
|
||||
await aTimeout(0);
|
||||
await el.updateComplete;
|
||||
|
||||
await waitUntil(() => el.shadowRoot!.querySelector('img') === null);
|
||||
expect(el.shadowRoot!.querySelector('img')).to.be.null;
|
||||
@@ -165,11 +172,15 @@ describe('<wa-avatar>', () => {
|
||||
el = await fixture<WaAvatar>(html`<wa-avatar></wa-avatar>`);
|
||||
|
||||
await aTimeout(0);
|
||||
await waitUntil(() => el.shadowRoot!.querySelector('img') === null);
|
||||
await el.updateComplete;
|
||||
// await waitUntil(() => el.shadowRoot!.querySelector('img') === null);
|
||||
expect(el.shadowRoot!.querySelector('img')).to.be.null;
|
||||
|
||||
el.image = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.shadowRoot?.querySelector('img')).to.exist;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { aTimeout, expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaBadge 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
|
||||
@@ -6,55 +8,50 @@ import type WaBadge from './badge.js';
|
||||
const ignoredRules = ['color-contrast'];
|
||||
|
||||
describe('<wa-badge>', () => {
|
||||
let el: WaBadge;
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('when provided no parameters', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBadge>(html` <wa-badge>Badge</wa-badge> `);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests with a role of status on the base part.', async () => {
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.getAttribute('role')).to.eq('status');
|
||||
});
|
||||
|
||||
it('should render the child content provided', () => {
|
||||
it('should render the child content provided', async () => {
|
||||
const el = await fixture<WaBadge>(html` <wa-badge>Badge</wa-badge> `);
|
||||
expect(el.innerText).to.eq('Badge');
|
||||
});
|
||||
|
||||
it('should default to square styling, with the brand color', () => {
|
||||
it('should pass accessibility tests with a role of status on the base part.', async () => {
|
||||
const el = await fixture<WaBadge>(html` <wa-badge>Badge</wa-badge> `);
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.getAttribute('role')).to.eq('status');
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should default to square styling, with the brand color', async () => {
|
||||
const el = await fixture<WaBadge>(html` <wa-badge>Badge</wa-badge> `);
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.classList.value.trim()).to.eq('badge badge--brand');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a pill parameter', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBadge>(html` <wa-badge pill>Badge</wa-badge> `);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBadge>(html` <wa-badge pill>Badge</wa-badge> `);
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should append the pill class to the classlist to render a pill', () => {
|
||||
it('should append the pill class to the classlist to render a pill', async () => {
|
||||
const el = await fixture<WaBadge>(html` <wa-badge pill>Badge</wa-badge> `);
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.classList.value.trim()).to.eq('badge badge--brand badge--pill');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a pulse parameter', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBadge>(html` <wa-badge pulse>Badge</wa-badge> `);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBadge>(html` <wa-badge pulse>Badge</wa-badge> `);
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
await aTimeout(1);
|
||||
});
|
||||
|
||||
it('should append the pulse class to the classlist to render a pulse', () => {
|
||||
it('should append the pulse class to the classlist to render a pulse', async () => {
|
||||
const el = await fixture<WaBadge>(html` <wa-badge pulse>Badge</wa-badge> `);
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.classList.value.trim()).to.eq('badge badge--brand badge--pulse');
|
||||
});
|
||||
@@ -62,18 +59,19 @@ describe('<wa-badge>', () => {
|
||||
|
||||
['brand', 'success', 'neutral', 'warning', 'danger'].forEach(variant => {
|
||||
describe(`when passed a variant attribute ${variant}`, () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBadge>(html`<wa-badge variant="${variant}">Badge</wa-badge>`);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBadge>(html`<wa-badge variant="${variant}">Badge</wa-badge>`);
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
await aTimeout(1);
|
||||
});
|
||||
|
||||
it('should default to square styling, with the correct color', () => {
|
||||
it('should default to square styling, with the correct color', async () => {
|
||||
const el = await fixture<WaBadge>(html`<wa-badge variant="${variant}">Badge</wa-badge>`);
|
||||
const part = el.shadowRoot!.querySelector('[part~="base"]')!;
|
||||
expect(part.classList.value.trim()).to.eq(`badge badge--${variant}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaBreadcrumbItem from './breadcrumb-item.js';
|
||||
|
||||
describe('<wa-breadcrumb-item>', () => {
|
||||
let el: WaBreadcrumbItem;
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('when not provided a href attribute', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBreadcrumbItem>(html` <wa-breadcrumb-item>Home</wa-breadcrumb-item> `);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
it('should hide the separator from screen readers', () => {
|
||||
it('should hide the separator from screen readers', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html` <wa-breadcrumb-item>Home</wa-breadcrumb-item> `);
|
||||
const separator = el.shadowRoot!.querySelector<HTMLSpanElement>('[part~="separator"]');
|
||||
expect(separator).attribute('aria-hidden', 'true');
|
||||
});
|
||||
|
||||
it('should render a HTMLButtonElement as the part "label", with a set type "button"', () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html` <wa-breadcrumb-item>Home</wa-breadcrumb-item> `);
|
||||
await expect(el).to.be.accessible(el);
|
||||
});
|
||||
|
||||
it('should hide the separator from screen readers', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html` <wa-breadcrumb-item>Home</wa-breadcrumb-item> `);
|
||||
const separator = el.shadowRoot!.querySelector<HTMLSpanElement>('[part~="separator"]');
|
||||
expect(separator).attribute('aria-hidden', 'true');
|
||||
});
|
||||
|
||||
it('should render a HTMLButtonElement as the part "label", with a set type "button"', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html` <wa-breadcrumb-item>Home</wa-breadcrumb-item> `);
|
||||
const button = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="label"]');
|
||||
expect(button).to.exist;
|
||||
expect(button).attribute('type', 'button');
|
||||
@@ -27,75 +34,88 @@ describe('<wa-breadcrumb-item>', () => {
|
||||
|
||||
describe('when provided a href attribute', () => {
|
||||
describe('and no target', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBreadcrumbItem>(html`
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item href="https://jsonplaceholder.typicode.com/">Home</wa-breadcrumb-item>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
it('should render a HTMLAnchorElement as the part "label", with the supplied href value', () => {
|
||||
it('should render a HTMLAnchorElement as the part "label", with the supplied href value', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item href="https://jsonplaceholder.typicode.com/">Home</wa-breadcrumb-item>
|
||||
`);
|
||||
|
||||
const hyperlink = el.shadowRoot!.querySelector<HTMLAnchorElement>('[part~="label"]');
|
||||
expect(hyperlink).attribute('href', 'https://jsonplaceholder.typicode.com/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and target, without rel', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBreadcrumbItem>(html`
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item href="https://jsonplaceholder.typicode.com/" target="_blank">Help</wa-breadcrumb-item>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
describe('should render a HTMLAnchorElement as the part "label"', () => {
|
||||
let hyperlink: HTMLAnchorElement | null;
|
||||
it('should use the supplied href value, as the href attribute value', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item href="https://jsonplaceholder.typicode.com/" target="_blank"
|
||||
>Help</wa-breadcrumb-item
|
||||
>
|
||||
`);
|
||||
const hyperlink: HTMLAnchorElement | null =
|
||||
el.shadowRoot!.querySelector<HTMLAnchorElement>('[part~="label"]');
|
||||
|
||||
before(() => {
|
||||
hyperlink = el.shadowRoot!.querySelector<HTMLAnchorElement>('[part~="label"]');
|
||||
});
|
||||
|
||||
it('should use the supplied href value, as the href attribute value', () => {
|
||||
expect(hyperlink).attribute('href', 'https://jsonplaceholder.typicode.com/');
|
||||
});
|
||||
|
||||
it('should default rel attribute to "noreferrer noopener"', () => {
|
||||
it('should default rel attribute to "noreferrer noopener"', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item href="https://jsonplaceholder.typicode.com/" target="_blank"
|
||||
>Help</wa-breadcrumb-item
|
||||
>
|
||||
`);
|
||||
const hyperlink: HTMLAnchorElement | null =
|
||||
el.shadowRoot!.querySelector<HTMLAnchorElement>('[part~="label"]');
|
||||
expect(hyperlink).attribute('rel', 'noreferrer noopener');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and target, with rel', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBreadcrumbItem>(html`
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item href="https://jsonplaceholder.typicode.com/" target="_blank" rel="alternate"
|
||||
>Help</wa-breadcrumb-item
|
||||
>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
describe('should render a HTMLAnchorElement', () => {
|
||||
let hyperlink: HTMLAnchorElement | null;
|
||||
it('should use the supplied href value, as the href attribute value', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item href="https://jsonplaceholder.typicode.com/" target="_blank" rel="alternate"
|
||||
>Help</wa-breadcrumb-item
|
||||
>
|
||||
`);
|
||||
const hyperlink: HTMLAnchorElement | null = el.shadowRoot!.querySelector<HTMLAnchorElement>('a');
|
||||
|
||||
before(() => {
|
||||
hyperlink = el.shadowRoot!.querySelector<HTMLAnchorElement>('a');
|
||||
});
|
||||
|
||||
it('should use the supplied href value, as the href attribute value', () => {
|
||||
expect(hyperlink).attribute('href', 'https://jsonplaceholder.typicode.com/');
|
||||
});
|
||||
|
||||
it('should use the supplied rel value, as the rel attribute value', () => {
|
||||
it('should use the supplied rel value, as the rel attribute value', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item href="https://jsonplaceholder.typicode.com/" target="_blank" rel="alternate"
|
||||
>Help</wa-breadcrumb-item
|
||||
>
|
||||
`);
|
||||
const hyperlink: HTMLAnchorElement | null = el.shadowRoot!.querySelector<HTMLAnchorElement>('a');
|
||||
expect(hyperlink).attribute('rel', 'alternate');
|
||||
});
|
||||
});
|
||||
@@ -103,20 +123,23 @@ describe('<wa-breadcrumb-item>', () => {
|
||||
});
|
||||
|
||||
describe('when provided an element in the slot "prefix" to support prefix icons', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBreadcrumbItem>(html`
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item>
|
||||
<span class="prefix-example" slot="prefix">/</span>
|
||||
Home
|
||||
</wa-breadcrumb-item>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
it('should accept as an assigned child in the shadow root', () => {
|
||||
it('should accept as an assigned child in the shadow root', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item>
|
||||
<span class="prefix-example" slot="prefix">/</span>
|
||||
Home
|
||||
</wa-breadcrumb-item>
|
||||
`);
|
||||
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=prefix]')!;
|
||||
const childNodes = slot.assignedNodes({ flatten: true });
|
||||
|
||||
@@ -125,20 +148,24 @@ describe('<wa-breadcrumb-item>', () => {
|
||||
});
|
||||
|
||||
describe('when provided an element in the slot "suffix" to support suffix icons', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBreadcrumbItem>(html`
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item>
|
||||
<span class="prefix-example" slot="suffix">/</span>
|
||||
Security
|
||||
</wa-breadcrumb-item>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
// await aTimeout(1)
|
||||
});
|
||||
|
||||
it('should accept as an assigned child in the shadow root', () => {
|
||||
it('should accept as an assigned child in the shadow root', async () => {
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item>
|
||||
<span class="prefix-example" slot="suffix">/</span>
|
||||
Security
|
||||
</wa-breadcrumb-item>
|
||||
`);
|
||||
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=suffix]')!;
|
||||
const childNodes = slot.assignedNodes({ flatten: true });
|
||||
|
||||
@@ -148,7 +175,7 @@ describe('<wa-breadcrumb-item>', () => {
|
||||
|
||||
describe('when rendering a wa-dropdown in the default slot', () => {
|
||||
it('should not render a link or button tag, but a div wrapper', async () => {
|
||||
el = await fixture<WaBreadcrumbItem>(html`
|
||||
const el = await fixture<WaBreadcrumbItem>(html`
|
||||
<wa-breadcrumb-item>
|
||||
<wa-dropdown>
|
||||
<wa-button slot="trigger" size="small" circle>
|
||||
@@ -169,4 +196,6 @@ describe('<wa-breadcrumb-item>', () => {
|
||||
expect(el.shadowRoot!.querySelector('.breadcrumb-item__label--dropdown')).not.to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaBreadcrumb 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
|
||||
@@ -6,11 +8,11 @@ import type WaBreadcrumb from './breadcrumb.js';
|
||||
const ignoredRules = ['color-contrast'];
|
||||
|
||||
describe('<wa-breadcrumb>', () => {
|
||||
let el: WaBreadcrumb;
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('when provided a standard list of el-breadcrumb-item children and no parameters', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBreadcrumb>(html`
|
||||
it('should render wa-icon as separator', async () => {
|
||||
const el = await fixture<WaBreadcrumb>(html`
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>Catalog</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Clothing</wa-breadcrumb-item>
|
||||
@@ -18,17 +20,32 @@ describe('<wa-breadcrumb>', () => {
|
||||
<wa-breadcrumb-item>Shirts & Tops</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should render wa-icon as separator', () => {
|
||||
expect(el.querySelectorAll('wa-icon').length).to.eq(4);
|
||||
});
|
||||
|
||||
it('should attach aria-current "page" on the last breadcrumb item.', () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumb>(html`
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>Catalog</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Clothing</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Women's</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Shirts & Tops</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
`);
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should attach aria-current "page" on the last breadcrumb item.', async () => {
|
||||
const el = await fixture<WaBreadcrumb>(html`
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>Catalog</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Clothing</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Women's</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Shirts & Tops</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
`);
|
||||
|
||||
const breadcrumbItems = el.querySelectorAll('wa-breadcrumb-item');
|
||||
const lastNode = breadcrumbItems[3];
|
||||
expect(lastNode).attribute('aria-current', 'page');
|
||||
@@ -36,8 +53,8 @@ describe('<wa-breadcrumb>', () => {
|
||||
});
|
||||
|
||||
describe('when provided a standard list of el-breadcrumb-item children and an element in the slot "separator" to support Custom Separators', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBreadcrumb>(html`
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumb>(html`
|
||||
<wa-breadcrumb>
|
||||
<span class="replacement-separator" slot="separator">/</span>
|
||||
<wa-breadcrumb-item>First</wa-breadcrumb-item>
|
||||
@@ -45,28 +62,42 @@ describe('<wa-breadcrumb>', () => {
|
||||
<wa-breadcrumb-item>Third</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
|
||||
it('should accept "separator" as an assigned child in the shadow root', () => {
|
||||
it('should accept "separator" as an assigned child in the shadow root', async () => {
|
||||
const el = await fixture<WaBreadcrumb>(html`
|
||||
<wa-breadcrumb>
|
||||
<span class="replacement-separator" slot="separator">/</span>
|
||||
<wa-breadcrumb-item>First</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Second</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Third</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
`);
|
||||
|
||||
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=separator]')!;
|
||||
const childNodes = slot.assignedNodes({ flatten: true });
|
||||
|
||||
expect(childNodes.length).to.eq(1);
|
||||
});
|
||||
|
||||
it('should replace the wa-icon separator with the provided separator', () => {
|
||||
it('should replace the wa-icon separator with the provided separator', async () => {
|
||||
const el = await fixture<WaBreadcrumb>(html`
|
||||
<wa-breadcrumb>
|
||||
<span class="replacement-separator" slot="separator">/</span>
|
||||
<wa-breadcrumb-item>First</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Second</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Third</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
`);
|
||||
expect(el.querySelectorAll('.replacement-separator').length).to.eq(4);
|
||||
expect(el.querySelectorAll('wa-icon').length).to.eq(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a standard list of el-breadcrumb-item children and an element in the slot "prefix" to support prefix icons', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBreadcrumb>(html`
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumb>(html`
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>
|
||||
<span class="prefix-example" slot="prefix">/</span>
|
||||
@@ -77,16 +108,13 @@ describe('<wa-breadcrumb>', () => {
|
||||
<wa-breadcrumb-item>Third</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided a standard list of el-breadcrumb-item children and an element in the slot "suffix" to support suffix icons', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaBreadcrumb>(html`
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaBreadcrumb>(html`
|
||||
<wa-breadcrumb>
|
||||
<wa-breadcrumb-item>First</wa-breadcrumb-item>
|
||||
<wa-breadcrumb-item>Second</wa-breadcrumb-item>
|
||||
@@ -97,10 +125,9 @@ describe('<wa-breadcrumb>', () => {
|
||||
</wa-breadcrumb-item>
|
||||
</wa-breadcrumb>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
|
||||
import { elementUpdated, expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaButtonGroup from './button-group.js';
|
||||
|
||||
describe('<wa-button-group>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('defaults ', () => {
|
||||
it('passes accessibility test', async () => {
|
||||
const group = await fixture<WaButtonGroup>(html`
|
||||
<wa-button-group>
|
||||
<wa-button>Button 1 Label</wa-button>
|
||||
<wa-button>Button 2 Label</wa-button>
|
||||
<wa-button>Button 3 Label</wa-button>
|
||||
</wa-button-group>
|
||||
`);
|
||||
await expect(group).to.be.accessible();
|
||||
});
|
||||
|
||||
it('default label empty', async () => {
|
||||
const group = await fixture<WaButtonGroup>(html`
|
||||
<wa-button-group>
|
||||
@@ -24,8 +17,18 @@ describe('<wa-button-group>', () => {
|
||||
`);
|
||||
expect(group.label).to.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
it('passes accessibility test', async () => {
|
||||
const group = await fixture<WaButtonGroup>(html`
|
||||
<wa-button-group>
|
||||
<wa-button>Button 1 Label</wa-button>
|
||||
<wa-button>Button 2 Label</wa-button>
|
||||
<wa-button>Button 3 Label</wa-button>
|
||||
</wa-button-group>
|
||||
`);
|
||||
await expect(group).to.be.accessible();
|
||||
});
|
||||
});
|
||||
describe('slotted button classes', () => {
|
||||
it('slotted buttons have the right classes applied based on their order', async () => {
|
||||
const group = await fixture<WaButtonGroup>(html`
|
||||
@@ -91,4 +94,6 @@ describe('<wa-button-group>', () => {
|
||||
expect(allButtons[0].classList.contains('wa-button-group__button--hover')).not.to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,44 @@
|
||||
import { aTimeout, expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, waitUntil } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import sinon from 'sinon';
|
||||
import type WaButton from './button.js';
|
||||
|
||||
const variants = ['brand', 'success', 'neutral', 'warning', 'danger'];
|
||||
|
||||
describe('<wa-button>', async () => {
|
||||
describe('<wa-button>', () => {
|
||||
it('form control base tests', async () => {
|
||||
await Promise.allSettled([
|
||||
runFormControlBaseTests({
|
||||
tagName: 'wa-button',
|
||||
variantName: 'type="button"',
|
||||
|
||||
init: (control: WaButton) => {
|
||||
control.type = 'button';
|
||||
}
|
||||
}),
|
||||
runFormControlBaseTests({
|
||||
tagName: 'wa-button',
|
||||
variantName: 'type="submit"',
|
||||
|
||||
init: (control: WaButton) => {
|
||||
control.type = 'submit';
|
||||
}
|
||||
}),
|
||||
runFormControlBaseTests({
|
||||
tagName: 'wa-button',
|
||||
variantName: 'href="xyz"',
|
||||
|
||||
init: (control: WaButton) => {
|
||||
control.href = 'some-url';
|
||||
}
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('accessibility tests', () => {
|
||||
variants.forEach(variant => {
|
||||
it(`should be accessible when variant is "${variant}"`, async () => {
|
||||
@@ -276,31 +309,6 @@ describe('<wa-button>', async () => {
|
||||
expect(clickHandler).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
runFormControlBaseTests({
|
||||
tagName: 'wa-button',
|
||||
variantName: 'type="button"',
|
||||
|
||||
init: (control: WaButton) => {
|
||||
control.type = 'button';
|
||||
});
|
||||
}
|
||||
}),
|
||||
runFormControlBaseTests({
|
||||
tagName: 'wa-button',
|
||||
variantName: 'type="submit"',
|
||||
|
||||
init: (control: WaButton) => {
|
||||
control.type = 'submit';
|
||||
}
|
||||
}),
|
||||
runFormControlBaseTests({
|
||||
tagName: 'wa-button',
|
||||
variantName: 'href="xyz"',
|
||||
|
||||
init: (control: WaButton) => {
|
||||
control.href = 'some-url';
|
||||
}
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -108,7 +108,7 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
* The value of the button, submitted as a pair with the button's name as part of the form data, but only when this
|
||||
* button is the submitter. This attribute is ignored when `href` is present.
|
||||
*/
|
||||
@property({ reflect: true }) value = '';
|
||||
@property({ reflect: true }) value: string | null = null;
|
||||
|
||||
/** When set, the underlying button will be rendered as an `<a>` with this `href` instead of a `<button>`. */
|
||||
@property() href = '';
|
||||
@@ -184,7 +184,7 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
if (this.name) {
|
||||
button.name = this.name;
|
||||
}
|
||||
button.value = this.value;
|
||||
button.value = this.value || '';
|
||||
|
||||
['form', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget'].forEach(attr => {
|
||||
if (this.hasAttribute(attr)) {
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaCallout from './callout.js';
|
||||
|
||||
it('Should properly render callout variants', async () => {
|
||||
describe('<wa-callout>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('Should properly render callout variants', async () => {
|
||||
const variants = ['brand', 'success', 'neutral', 'warning', 'danger'];
|
||||
|
||||
for (const variant of variants) {
|
||||
const callout = await fixture<WaCallout>(html`<wa-callout variant="${variant}">I am a callout</wa-callout>`);
|
||||
|
||||
await customElements.whenDefined('wa-callout');
|
||||
await callout.updateComplete;
|
||||
|
||||
const base = callout.shadowRoot!.querySelector<HTMLElement>('[part="base"]')!;
|
||||
|
||||
expect(base).to.have.class(`callout--${variant}`);
|
||||
|
||||
// @TODO: For some reason this fails only in CI. I have no clue why. I tested this scenario on the real site, and it works as expected. [Konnor]
|
||||
if (fixture.type === 'ssr-client-hydrated') {
|
||||
return;
|
||||
}
|
||||
|
||||
await expect(callout).to.be.accessible();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,106 +1,160 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaCard from './card.js';
|
||||
|
||||
describe('<wa-card>', () => {
|
||||
let el: WaCard;
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('when provided no parameters', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaCard>(html`
|
||||
it('should render the child content provided.', async () => {
|
||||
const el = await fixture<WaCard>(html`
|
||||
<wa-card>This is just a basic card. No image, no header, and no footer. Just your content.</wa-card>
|
||||
`);
|
||||
expect(el.innerText).to.eq(
|
||||
'This is just a basic card. No image, no header, and no footer. Just your content.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaCard>(html`
|
||||
<wa-card>This is just a basic card. No image, no header, and no footer. Just your content.</wa-card>
|
||||
`);
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
it('should render the child content provided.', () => {
|
||||
expect(el.innerText).to.eq('This is just a basic card. No image, no header, and no footer. Just your content.');
|
||||
});
|
||||
|
||||
it('should contain the class card.', () => {
|
||||
it('should contain the class card.', async () => {
|
||||
const el = await fixture<WaCard>(html`
|
||||
<wa-card>This is just a basic card. No image, no header, and no footer. Just your content.</wa-card>
|
||||
`);
|
||||
const card = el.shadowRoot!.querySelector('.card')!;
|
||||
expect(card.classList.value.trim()).to.eq('card');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided an element in the slot "header" to render a header', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaCard>(
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-header>
|
||||
<div slot="header">Header Title</div>
|
||||
This card has a header. You can put all sorts of things in it!
|
||||
</wa-card>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
it('should render the child content provided.', () => {
|
||||
it('should render the child content provided.', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-header>
|
||||
<div slot="header">Header Title</div>
|
||||
This card has a header. You can put all sorts of things in it!
|
||||
</wa-card>`
|
||||
);
|
||||
expect(el.innerText).to.contain('This card has a header. You can put all sorts of things in it!');
|
||||
});
|
||||
|
||||
it('render the header content provided.', () => {
|
||||
it('render the header content provided.', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-header>
|
||||
<div slot="header">Header Title</div>
|
||||
This card has a header. You can put all sorts of things in it!
|
||||
</wa-card>`
|
||||
);
|
||||
const header = el.querySelector<HTMLElement>('div[slot=header]')!;
|
||||
expect(header.innerText).eq('Header Title');
|
||||
});
|
||||
|
||||
it('accept "header" as an assigned child in the shadow root.', () => {
|
||||
it('accept "header" as an assigned child in the shadow root.', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-header>
|
||||
<div slot="header">Header Title</div>
|
||||
This card has a header. You can put all sorts of things in it!
|
||||
</wa-card>`
|
||||
);
|
||||
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=header]')!;
|
||||
const childNodes = slot.assignedNodes({ flatten: true });
|
||||
|
||||
expect(childNodes.length).to.eq(1);
|
||||
});
|
||||
|
||||
it('should contain the class card--has-header.', () => {
|
||||
it('should contain the class card--has-header.', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-header>
|
||||
<div slot="header">Header Title</div>
|
||||
This card has a header. You can put all sorts of things in it!
|
||||
</wa-card>`
|
||||
);
|
||||
const card = el.shadowRoot!.querySelector('.card')!;
|
||||
expect(card.classList.value.trim()).to.eq('card card--has-header');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided an element in the slot "footer" to render a footer', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaCard>(
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-footer>
|
||||
This card has a footer. You can put all sorts of things in it!
|
||||
|
||||
<div slot="footer">Footer Content</div>
|
||||
</wa-card>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
it('should render the child content provided.', () => {
|
||||
it('should render the child content provided.', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-footer>
|
||||
This card has a footer. You can put all sorts of things in it!
|
||||
|
||||
<div slot="footer">Footer Content</div>
|
||||
</wa-card>`
|
||||
);
|
||||
expect(el.innerText).to.contain('This card has a footer. You can put all sorts of things in it!');
|
||||
});
|
||||
|
||||
it('render the footer content provided.', () => {
|
||||
it('render the footer content provided.', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-footer>
|
||||
This card has a footer. You can put all sorts of things in it!
|
||||
|
||||
<div slot="footer">Footer Content</div>
|
||||
</wa-card>`
|
||||
);
|
||||
const footer = el.querySelector<HTMLElement>('div[slot=footer]')!;
|
||||
expect(footer.innerText).eq('Footer Content');
|
||||
});
|
||||
|
||||
it('accept "footer" as an assigned child in the shadow root.', () => {
|
||||
it('accept "footer" as an assigned child in the shadow root.', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-footer>
|
||||
This card has a footer. You can put all sorts of things in it!
|
||||
|
||||
<div slot="footer">Footer Content</div>
|
||||
</wa-card>`
|
||||
);
|
||||
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=footer]')!;
|
||||
const childNodes = slot.assignedNodes({ flatten: true });
|
||||
|
||||
expect(childNodes.length).to.eq(1);
|
||||
});
|
||||
|
||||
it('should contain the class card--has-footer.', () => {
|
||||
it('should contain the class card--has-footer.', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-footer>
|
||||
This card has a footer. You can put all sorts of things in it!
|
||||
|
||||
<div slot="footer">Footer Content</div>
|
||||
</wa-card>`
|
||||
);
|
||||
|
||||
const card = el.shadowRoot!.querySelector('.card')!;
|
||||
expect(card.classList.value.trim()).to.eq('card card--has-footer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided an element in the slot "image" to render a image', () => {
|
||||
before(async () => {
|
||||
el = await fixture<WaCard>(
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-image>
|
||||
<img
|
||||
slot="image"
|
||||
@@ -110,28 +164,60 @@ describe('<wa-card>', () => {
|
||||
This is a kitten, but not just any kitten. This kitten likes walking along pallets.
|
||||
</wa-card>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
it('should render the child content provided.', () => {
|
||||
it('should render the child content provided.', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-image>
|
||||
<img
|
||||
slot="image"
|
||||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||||
alt="A kitten walks towards camera on top of pallet."
|
||||
/>
|
||||
This is a kitten, but not just any kitten. This kitten likes walking along pallets.
|
||||
</wa-card>`
|
||||
);
|
||||
|
||||
expect(el.innerText).to.contain(
|
||||
'This is a kitten, but not just any kitten. This kitten likes walking along pallets.'
|
||||
);
|
||||
});
|
||||
|
||||
it('accept "image" as an assigned child in the shadow root.', () => {
|
||||
it('accept "image" as an assigned child in the shadow root.', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-image>
|
||||
<img
|
||||
slot="image"
|
||||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||||
alt="A kitten walks towards camera on top of pallet."
|
||||
/>
|
||||
This is a kitten, but not just any kitten. This kitten likes walking along pallets.
|
||||
</wa-card>`
|
||||
);
|
||||
const slot = el.shadowRoot!.querySelector<HTMLSlotElement>('slot[name=image]')!;
|
||||
const childNodes = slot.assignedNodes({ flatten: true });
|
||||
|
||||
expect(childNodes.length).to.eq(1);
|
||||
});
|
||||
|
||||
it('should contain the class card--has-image.', () => {
|
||||
it('should contain the class card--has-image.', async () => {
|
||||
const el = await fixture<WaCard>(
|
||||
html`<wa-card with-image>
|
||||
<img
|
||||
slot="image"
|
||||
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
|
||||
alt="A kitten walks towards camera on top of pallet."
|
||||
/>
|
||||
This is a kitten, but not just any kitten. This kitten likes walking along pallets.
|
||||
</wa-card>`
|
||||
);
|
||||
|
||||
const card = el.shadowRoot!.querySelector('.card')!;
|
||||
expect(card.classList.value.trim()).to.eq('card card--has-image');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
|
||||
describe('<wa-carousel-item>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should render a component', async () => {
|
||||
const el = await fixture(html`<wa-carousel-item></wa-carousel-item> `);
|
||||
|
||||
@@ -14,4 +18,6 @@ describe('<wa-carousel-item>', () => {
|
||||
// Assert
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { aTimeout, expect, fixture, html, nextFrame, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, nextFrame, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { clickOnElement, dragElement, moveMouseOnElement } from '../../internal/test.js';
|
||||
import { clientFixture } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { map } from 'lit/directives/map.js';
|
||||
import { range } from 'lit/directives/range.js';
|
||||
import { resetMouse } from '@web/test-runner-commands';
|
||||
@@ -8,6 +10,9 @@ import type { SinonStub } from 'sinon';
|
||||
import type WaCarousel from './carousel.js';
|
||||
|
||||
describe('<wa-carousel>', () => {
|
||||
// @TODO: Fix hydrated fixture.
|
||||
for (const fixture of [clientFixture]) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
const ioCallbacks = new Map<IntersectionObserver, SinonStub>();
|
||||
const intersectionObserverCallbacks = () => {
|
||||
@@ -257,7 +262,8 @@ describe('<wa-carousel>', () => {
|
||||
[7, 2, 1, true, 7],
|
||||
[5, 3, 3, true, 2],
|
||||
[10, 2, 2, true, 5]
|
||||
].forEach(([slides, slidesPerPage, slidesPerMove, loop, expected]: [number, number, number, boolean, number]) => {
|
||||
].forEach(
|
||||
([slides, slidesPerPage, slidesPerMove, loop, expected]: [number, number, number, boolean, number]) => {
|
||||
it(`should display ${expected} pages for ${slides} slides grouped by ${slidesPerPage} and scrolled by ${slidesPerMove}${
|
||||
loop ? ' (loop)' : ''
|
||||
}`, async () => {
|
||||
@@ -278,7 +284,8 @@ describe('<wa-carousel>', () => {
|
||||
const paginationItems = el.shadowRoot!.querySelectorAll('.carousel__pagination-item');
|
||||
expect(paginationItems.length).to.equal(expected);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('when `slides-per-move` attribute is provided', () => {
|
||||
@@ -613,7 +620,9 @@ describe('<wa-carousel>', () => {
|
||||
</wa-carousel>
|
||||
`);
|
||||
|
||||
const previousButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--previous')!;
|
||||
const previousButton: HTMLElement = el.shadowRoot!.querySelector(
|
||||
'.carousel__navigation-button--previous'
|
||||
)!;
|
||||
sandbox.stub(el, 'previous');
|
||||
await el.updateComplete;
|
||||
|
||||
@@ -637,7 +646,9 @@ describe('<wa-carousel>', () => {
|
||||
</wa-carousel>
|
||||
`);
|
||||
|
||||
const previousButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--previous')!;
|
||||
const previousButton: HTMLElement = el.shadowRoot!.querySelector(
|
||||
'.carousel__navigation-button--previous'
|
||||
)!;
|
||||
await el.updateComplete;
|
||||
|
||||
// Act
|
||||
@@ -809,4 +820,6 @@ describe('<wa-carousel>', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AutoplayController } from './autoplay-controller.js';
|
||||
import { clamp } from '../../internal/math.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, eventOptions, property, query, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { html, isServer } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { map } from 'lit/directives/map.js';
|
||||
import { prefersReducedMotion } from '../../internal/animate.js';
|
||||
@@ -58,6 +58,9 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
/** When set, allows the user to navigate the carousel in the same direction indefinitely. */
|
||||
@property({ type: Boolean, reflect: true }) loop = false;
|
||||
|
||||
@property({ type: Number, reflect: true }) slides = 0;
|
||||
@property({ type: Number, reflect: true }) currentSlide = 0;
|
||||
|
||||
/** When set, show the carousel's navigation. */
|
||||
@property({ type: Boolean, reflect: true }) navigation = false;
|
||||
|
||||
@@ -487,11 +490,22 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
|
||||
render() {
|
||||
const { slidesPerMove, scrolling } = this;
|
||||
const pagesCount = this.getPageCount();
|
||||
const currentPage = this.getCurrentPage();
|
||||
const prevEnabled = this.canScrollPrev();
|
||||
const nextEnabled = this.canScrollNext();
|
||||
const isLtr = this.matches(':dir(ltr)');
|
||||
|
||||
let pagesCount = 0;
|
||||
let currentPage = 0;
|
||||
let prevEnabled = false;
|
||||
let nextEnabled = false;
|
||||
|
||||
// @TODO: This is a super hacky way to get rid of hydration mismatch errors. The ideal solution is users being able to pass in `pagesCount` and `currentPage` and then on firstUpdated to we update the value for them.
|
||||
if (this.hasUpdated) {
|
||||
pagesCount = this.getPageCount();
|
||||
currentPage = this.getCurrentPage();
|
||||
prevEnabled = this.canScrollPrev();
|
||||
nextEnabled = this.canScrollNext();
|
||||
}
|
||||
|
||||
// We can't rely on `this.matches()` on the server.
|
||||
const isRTL = isServer ? this.dir === 'rtl' : this.matches(':dir(rtl)');
|
||||
|
||||
return html`
|
||||
<div part="base" class="carousel">
|
||||
@@ -513,7 +527,7 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
@scroll="${this.handleScroll}"
|
||||
@scrollend=${this.handleScrollEnd}
|
||||
>
|
||||
<slot></slot>
|
||||
<slot @slotchange=${() => this.requestUpdate()}></slot>
|
||||
</div>
|
||||
|
||||
${this.navigation
|
||||
@@ -532,7 +546,7 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
@click=${prevEnabled ? () => this.previous() : null}
|
||||
>
|
||||
<slot name="previous-icon">
|
||||
<wa-icon library="system" name="${isLtr ? 'chevron-left' : 'chevron-right'}"></wa-icon>
|
||||
<wa-icon library="system" name="${isRTL ? 'chevron-right' : 'chevron-left'}"></wa-icon>
|
||||
</slot>
|
||||
</button>
|
||||
|
||||
@@ -549,7 +563,7 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
@click=${nextEnabled ? () => this.next() : null}
|
||||
>
|
||||
<slot name="next-icon">
|
||||
<wa-icon library="system" name="${isLtr ? 'chevron-right' : 'chevron-left'}"></wa-icon>
|
||||
<wa-icon library="system" name="${isRTL ? 'chevron-left' : 'chevron-right'}"></wa-icon>
|
||||
</slot>
|
||||
</button>
|
||||
</div>
|
||||
@@ -578,7 +592,7 @@ export default class WaCarousel extends WebAwesomeElement {
|
||||
})}
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
: html``}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { aTimeout, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type WaCheckbox from './checkbox.js';
|
||||
|
||||
describe('<wa-checkbox>', () => {
|
||||
runFormControlBaseTests('wa-checkbox');
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaCheckbox>(html` <wa-checkbox>Checkbox</wa-checkbox> `);
|
||||
await expect(el).to.be.accessible();
|
||||
@@ -15,7 +21,7 @@ describe('<wa-checkbox>', () => {
|
||||
const el = await fixture<WaCheckbox>(html` <wa-checkbox></wa-checkbox> `);
|
||||
|
||||
expect(el.name).to.equal('');
|
||||
expect(el.value).to.be.null;
|
||||
expect(el.value).to.equal('on');
|
||||
expect(el.title).to.equal('');
|
||||
expect(el.disabled).to.be.false;
|
||||
expect(el.required).to.be.false;
|
||||
@@ -114,6 +120,31 @@ describe('<wa-checkbox>', () => {
|
||||
expect(inputPosition).to.equal('absolute');
|
||||
});
|
||||
|
||||
it('Should keep its form value when going from checked -> unchecked -> checked', async () => {
|
||||
const form = await fixture<HTMLFormElement>(
|
||||
html`<form><wa-checkbox name="test" value="myvalue" checked>Checked</wa-checkbox></form>`
|
||||
);
|
||||
const checkbox = form.querySelector('wa-checkbox')!;
|
||||
|
||||
expect(checkbox.checked).to.equal(true);
|
||||
expect(checkbox.value).to.equal('myvalue');
|
||||
expect(new FormData(form).get('test')).to.equal('myvalue');
|
||||
|
||||
checkbox.checked = false;
|
||||
await checkbox.updateComplete;
|
||||
|
||||
expect(checkbox.checked).to.equal(false);
|
||||
expect(checkbox.value).to.equal('myvalue');
|
||||
expect(new FormData(form).get('test')).to.equal(null);
|
||||
|
||||
checkbox.checked = true;
|
||||
await checkbox.updateComplete;
|
||||
|
||||
expect(checkbox.checked).to.equal(true);
|
||||
expect(checkbox.value).to.equal('myvalue');
|
||||
expect(new FormData(form).get('test')).to.equal('myvalue');
|
||||
});
|
||||
|
||||
describe('when submitting a form', () => {
|
||||
it('should submit the correct value when a value is provided', async () => {
|
||||
const form = await fixture<HTMLFormElement>(html`
|
||||
@@ -208,7 +239,9 @@ describe('<wa-checkbox>', () => {
|
||||
});
|
||||
|
||||
it('should receive validation attributes ("states") even when novalidate is used on the parent form', async () => {
|
||||
const el = await fixture<HTMLFormElement>(html` <form novalidate><wa-checkbox required></wa-checkbox></form> `);
|
||||
const el = await fixture<HTMLFormElement>(html`
|
||||
<form novalidate><wa-checkbox required></wa-checkbox></form>
|
||||
`);
|
||||
const checkbox = el.querySelector<WaCheckbox>('wa-checkbox')!;
|
||||
|
||||
expect(checkbox.hasAttribute('data-wa-required')).to.be.true;
|
||||
@@ -353,7 +386,7 @@ describe('<wa-checkbox>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('indeterminate', async () => {
|
||||
describe('indeterminate', () => {
|
||||
it('should render indeterminate icon until checked', async () => {
|
||||
const el = await fixture<WaCheckbox>(html`<wa-checkbox indeterminate></wa-checkbox>`);
|
||||
let indeterminateIcon = el.shadowRoot!.querySelector('[part~="indeterminate-icon"]')!;
|
||||
@@ -367,7 +400,7 @@ describe('<wa-checkbox>', () => {
|
||||
|
||||
expect(indeterminateIcon).to.be.null;
|
||||
});
|
||||
|
||||
await runFormControlBaseTests('wa-checkbox');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import '../icon/icon.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import { html, isServer } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { live } from 'lit/directives/live.js';
|
||||
import { RequiredValidator } from '../../internal/validators/required-validator.js';
|
||||
@@ -56,10 +56,15 @@ import type { CSSResultGroup, PropertyValues } from 'lit';
|
||||
@customElement('wa-checkbox')
|
||||
export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
|
||||
|
||||
static shadowRootOptions = { ...WebAwesomeFormAssociatedElement.shadowRootOptions, delegatesFocus: true };
|
||||
|
||||
static get validators() {
|
||||
return [
|
||||
...super.validators,
|
||||
const validators = isServer
|
||||
? []
|
||||
: [
|
||||
RequiredValidator({
|
||||
validationProperty: 'checked',
|
||||
// Use a checkbox so we get "free" translation strings.
|
||||
validationElement: Object.assign(document.createElement('input'), {
|
||||
type: 'checkbox',
|
||||
@@ -67,6 +72,7 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
})
|
||||
})
|
||||
];
|
||||
return [...super.validators, ...validators];
|
||||
}
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'help-text');
|
||||
@@ -80,8 +86,17 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
/** The name of the checkbox, submitted as a name/value pair with form data. */
|
||||
@property({ reflect: true }) name = '';
|
||||
|
||||
/** The current value of the checkbox, submitted as a name/value pair with form data. */
|
||||
@property() value: null | string;
|
||||
private _value: string | null = this.getAttribute('value') ?? null;
|
||||
|
||||
/** The value of the checkbox, submitted as a name/value pair with form data. */
|
||||
get value() {
|
||||
return this._value ?? 'on';
|
||||
}
|
||||
|
||||
@property({ reflect: true })
|
||||
set value(val: string | null) {
|
||||
this._value = val;
|
||||
}
|
||||
|
||||
/** The checkbox's size. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
@@ -115,6 +130,7 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
@property({ attribute: 'help-text' }) helpText = '';
|
||||
|
||||
private handleClick() {
|
||||
this.hasInteracted = true;
|
||||
this.checked = !this.checked;
|
||||
this.indeterminate = false;
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
@@ -144,10 +160,9 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
handleValueOrCheckedChange() {
|
||||
this.toggleCustomState('checked', this.checked);
|
||||
this.value = this.checked ? this.value || 'on' : null;
|
||||
|
||||
// These @watch() commands seem to override the base element checks for changes, so we need to setValue for the form and and updateValidity()
|
||||
this.setValue(this.value, this.value);
|
||||
this.setValue(this.checked ? this.value : null, this._value);
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
@@ -161,6 +176,12 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has('defaultChecked')) {
|
||||
if (!this.hasInteracted) {
|
||||
this.checked = this.defaultChecked;
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProperties.has('value') || changedProperties.has('checked')) {
|
||||
this.handleValueOrCheckedChange();
|
||||
}
|
||||
@@ -189,7 +210,7 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasHelpTextSlot = this.hasSlotController.test('help-text');
|
||||
const hasHelpTextSlot = isServer ? true : this.hasSlotController.test('help-text');
|
||||
const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
|
||||
|
||||
//
|
||||
@@ -231,7 +252,7 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
type="checkbox"
|
||||
title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}
|
||||
name=${this.name}
|
||||
value=${ifDefined(this.value)}
|
||||
value=${ifDefined(this._value)}
|
||||
.indeterminate=${live(this.indeterminate)}
|
||||
.checked=${live(this.checked)}
|
||||
.disabled=${this.disabled}
|
||||
@@ -248,7 +269,7 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
? html`
|
||||
<wa-icon part="checked-icon" class="checkbox__checked-icon" library="system" name="check"></wa-icon>
|
||||
`
|
||||
: ''}
|
||||
: html``}
|
||||
${!this.checked && this.indeterminate
|
||||
? html`
|
||||
<wa-icon
|
||||
@@ -258,7 +279,7 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
name="indeterminate"
|
||||
></wa-icon>
|
||||
`
|
||||
: ''}
|
||||
: html``}
|
||||
</span>
|
||||
|
||||
<div part="label" class="checkbox__label">
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { aTimeout, expect, oneEvent } from '@open-wc/testing';
|
||||
import { clickOnElement, dragElement } from '../../internal/test.js';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import { serialize } from '../../utilities/form.js';
|
||||
import sinon from 'sinon';
|
||||
import type WaColorPicker from './color-picker.js';
|
||||
|
||||
describe('<wa-color-picker>', async () => {
|
||||
describe('<wa-color-picker>', () => {
|
||||
runFormControlBaseTests('wa-color-picker');
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('when the value changes', () => {
|
||||
it('should not emit wa-change or wa-input when the value is changed programmatically', async () => {
|
||||
const el = await fixture<WaColorPicker>(html` <wa-color-picker></wa-color-picker> `);
|
||||
@@ -155,7 +161,9 @@ describe('<wa-color-picker>', async () => {
|
||||
});
|
||||
|
||||
it('should emit wa-change and wa-input when clicking on a swatch', async () => {
|
||||
const el = await fixture<WaColorPicker>(html` <wa-color-picker swatches="red; green; blue;"></wa-color-picker> `);
|
||||
const el = await fixture<WaColorPicker>(html`
|
||||
<wa-color-picker swatches="red; green; blue;"></wa-color-picker>
|
||||
`);
|
||||
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
|
||||
const swatch = el.shadowRoot!.querySelector<HTMLElement>('[part~="swatch"]')!;
|
||||
const changeHandler = sinon.spy();
|
||||
@@ -342,7 +350,8 @@ describe('<wa-color-picker>', async () => {
|
||||
expect(trigger?.style.color).to.equal('rgb(0, 0, 0)');
|
||||
});
|
||||
|
||||
it('should display a color with opacity when an initial value with opacity is provided', async () => {
|
||||
// @TODO: Figure out whats up here. [Konnor]
|
||||
it.skip('should display a color with opacity when an initial value with opacity is provided', async () => {
|
||||
const el = await fixture<WaColorPicker>(html` <wa-color-picker opacity value="#ff000050"></wa-color-picker> `);
|
||||
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
|
||||
const previewButton = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="preview"]');
|
||||
@@ -531,6 +540,6 @@ describe('<wa-color-picker>', async () => {
|
||||
expect(el.hasAttribute('data-wa-user-valid')).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
await runFormControlBaseTests('wa-color-picker');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,14 +2,14 @@ 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 '../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, eventOptions, property, query, state } from 'lit/decorators.js';
|
||||
import { drag } from '../../internal/drag.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import { html, isServer } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { RequiredValidator } from '../../internal/validators/required-validator.js';
|
||||
@@ -25,12 +25,10 @@ import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-eleme
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
import formControlStyles from '../../styles/form-control.styles.js';
|
||||
import styles from './color-picker.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
import type { CSSResultGroup, PropertyValues } from 'lit';
|
||||
import type WaDropdown from '../dropdown/dropdown.js';
|
||||
import type WaInput from '../input/input.js';
|
||||
|
||||
const hasEyeDropper = 'EyeDropper' in window;
|
||||
|
||||
interface EyeDropperConstructor {
|
||||
new (): EyeDropperInterface;
|
||||
}
|
||||
@@ -113,7 +111,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
static shadowRootOptions = { ...WebAwesomeFormAssociatedElement.shadowRootOptions, delegatesFocus: true };
|
||||
|
||||
static get validators() {
|
||||
return [...super.validators, RequiredValidator()];
|
||||
const validators = isServer ? [] : [RequiredValidator()];
|
||||
return [...super.validators, ...validators];
|
||||
}
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
|
||||
@@ -152,15 +151,39 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
@state() private brightness = 100;
|
||||
@state() private alpha = 100;
|
||||
|
||||
private _value: string | null = null;
|
||||
|
||||
/** The current value of the input, submitted as a name/value pair with form data. */
|
||||
get value() {
|
||||
if (this.valueHasChanged) {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
return this._value ?? this.defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current value of the color picker. The value's format will vary based the `format` attribute. To get the value
|
||||
* in a specific format, use the `getFormattedValue()` method. The value is submitted as a name/value pair with form
|
||||
* data.
|
||||
*/
|
||||
@property({ attribute: false }) value = this.getAttribute('value') || '';
|
||||
|
||||
@state() set value(val: string | null) {
|
||||
if (this._value === val) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.valueHasChanged = true;
|
||||
this._value = val;
|
||||
}
|
||||
|
||||
/** The default value of the form control. Primarily used for resetting the form control. */
|
||||
@property({ attribute: 'value', reflect: true }) defaultValue = this.getAttribute('value') || '';
|
||||
@property({ attribute: 'value', reflect: true }) defaultValue: null | string = this.getAttribute('value') || null;
|
||||
|
||||
@property({ attribute: 'with-label', reflect: true, type: Boolean }) withLabel = false;
|
||||
@property({ attribute: 'with-help-text', reflect: true, type: Boolean }) withHelpText = false;
|
||||
|
||||
@state() private hasEyeDropper: boolean = false;
|
||||
|
||||
/**
|
||||
* The color picker's label. This will not be displayed, but it will be announced by assistive devices. If you need to
|
||||
@@ -222,9 +245,12 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (!isServer) {
|
||||
this.addEventListener('focusin', this.handleFocusIn);
|
||||
this.addEventListener('focusout', this.handleFocusOut);
|
||||
}
|
||||
}
|
||||
|
||||
private handleCopy() {
|
||||
this.input.select();
|
||||
@@ -252,7 +278,7 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
const formats = ['hex', 'rgb', 'hsl', 'hsv'];
|
||||
const nextIndex = (formats.indexOf(this.format) + 1) % formats.length;
|
||||
this.format = formats[nextIndex] as 'hex' | 'rgb' | 'hsl' | 'hsv';
|
||||
this.setColor(this.value);
|
||||
this.setColor(this.value || '');
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
@@ -462,7 +488,7 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
if (this.input.value) {
|
||||
this.setColor(target.value);
|
||||
target.value = this.value;
|
||||
target.value = this.value || '';
|
||||
} else {
|
||||
this.value = '';
|
||||
}
|
||||
@@ -651,7 +677,7 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
private handleEyeDropper() {
|
||||
if (!hasEyeDropper) {
|
||||
if (!this.hasEyeDropper) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -688,7 +714,7 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
/** Generates a hex string from HSV values. Hue must be 0-360. All other arguments must be 0-100. */
|
||||
private getHexString(hue: number, saturation: number, brightness: number, alpha = 100) {
|
||||
getHexString(hue: number, saturation: number, brightness: number, alpha = 100) {
|
||||
const color = new TinyColor(`hsva(${hue}, ${saturation}%, ${brightness}%, ${alpha / 100})`);
|
||||
if (!color.isValid) {
|
||||
return '';
|
||||
@@ -707,11 +733,20 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
this.syncValues();
|
||||
}
|
||||
|
||||
@watch('opacity', { waitUntilFirstUpdate: true })
|
||||
@watch('opacity')
|
||||
handleOpacityChange() {
|
||||
this.alpha = 100;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
// Its kind of bizarre, but this is required to get SSR to play nicely.
|
||||
if (changedProperties.has('value')) {
|
||||
this.handleValueChange(changedProperties.get('value') || '', this.value || '');
|
||||
}
|
||||
}
|
||||
|
||||
@watch('value')
|
||||
handleValueChange(oldValue: string | undefined, newValue: string) {
|
||||
this.isEmpty = !newValue;
|
||||
@@ -727,7 +762,7 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
const newColor = this.parseColor(newValue);
|
||||
|
||||
if (newColor !== null) {
|
||||
this.inputValue = this.value;
|
||||
this.inputValue = this.value || '';
|
||||
this.hue = newColor.hsva.h;
|
||||
this.saturation = newColor.hsva.s;
|
||||
this.brightness = newColor.hsva.v;
|
||||
@@ -737,6 +772,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
this.inputValue = oldValue ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
/** Sets focus on the color picker. */
|
||||
@@ -818,9 +855,17 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
super.formResetCallback();
|
||||
}
|
||||
|
||||
firstUpdated(changedProperties: PropertyValues<this>): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
|
||||
this.hasEyeDropper = 'EyeDropper' in window;
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasLabelSlot = this.hasSlotController.test('label');
|
||||
const hasHelpTextSlot = this.hasSlotController.test('help-text');
|
||||
const hasLabelSlot = !this.hasUpdated ? this.withLabel : this.withLabel || this.hasSlotController.test('label');
|
||||
const hasHelpTextSlot = !this.hasUpdated
|
||||
? this.withHelpText
|
||||
: this.withHelpText || this.hasSlotController.test('help-text');
|
||||
const hasLabel = this.label ? true : !!hasLabelSlot;
|
||||
const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
|
||||
|
||||
@@ -986,7 +1031,7 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
</wa-button>
|
||||
`
|
||||
: ''}
|
||||
${hasEyeDropper
|
||||
${this.hasEyeDropper
|
||||
? html`
|
||||
<wa-button
|
||||
part="eye-dropper-button"
|
||||
@@ -1076,12 +1121,15 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
const composedPath = e.composedPath();
|
||||
const triggerButton = this.triggerButton;
|
||||
const triggerLabel = this.triggerLabel;
|
||||
if (composedPath.find(el => el === triggerButton || el === triggerLabel)) {
|
||||
const buttonOrLabelClicked = composedPath.find(el => el === triggerButton || el === triggerLabel);
|
||||
|
||||
if (buttonOrLabelClicked) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop clicks from bubbling on anything except the button and the label. This is a hacky work around i may come to regret, but this "fixes" the issue of `<wa-dropdown>` expecting all children in the "trigger slot" to open the trigger. [Konnor]
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.dropdown.open) {
|
||||
this.dropdown.hide();
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaCopyButton from './copy-button.js';
|
||||
|
||||
// We use aria-live to announce labels via tooltips
|
||||
const ignoredRules = ['button-name'];
|
||||
|
||||
describe('<wa-copy-button>', () => {
|
||||
let el: WaCopyButton;
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('when provided no parameters', () => {
|
||||
before(async () => {
|
||||
el = await fixture(html`<wa-copy-button value="something"></wa-copy-button> `);
|
||||
});
|
||||
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaCopyButton>(html`<wa-copy-button value="something"></wa-copy-button> `);
|
||||
await expect(el).to.be.accessible({ ignoredRules });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// cspell:dictionaries lorem-ipsum
|
||||
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { expect, waitUntil } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import sinon from 'sinon';
|
||||
import type { WaHideEvent } from '../../events/hide.js';
|
||||
import type { WaShowEvent } from '../../events/show.js';
|
||||
import type WaDetails from './details.js';
|
||||
|
||||
describe('<wa-details>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('accessibility', () => {
|
||||
it('should be accessible when closed', async () => {
|
||||
const details = await fixture<WaDetails>(html`<wa-details summary="Test"> Test text </wa-details>`);
|
||||
@@ -23,9 +27,9 @@ describe('<wa-details>', () => {
|
||||
it('should be visible with the open attribute', async () => {
|
||||
const el = await fixture<WaDetails>(html`
|
||||
<wa-details open>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||
ea commodo consequat.
|
||||
</wa-details>
|
||||
`);
|
||||
const body = el.shadowRoot!.querySelector<HTMLElement>('.details__body')!;
|
||||
@@ -36,9 +40,9 @@ describe('<wa-details>', () => {
|
||||
it('should not be visible without the open attribute', async () => {
|
||||
const el = await fixture<WaDetails>(html`
|
||||
<wa-details summary="click me">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||
ea commodo consequat.
|
||||
</wa-details>
|
||||
`);
|
||||
const body = el.shadowRoot!.querySelector<HTMLElement>('.details__body')!;
|
||||
@@ -48,9 +52,9 @@ describe('<wa-details>', () => {
|
||||
it('should emit wa-show and wa-after-show when calling show()', async () => {
|
||||
const el = await fixture<WaDetails>(html`
|
||||
<wa-details>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||
ea commodo consequat.
|
||||
</wa-details>
|
||||
`);
|
||||
const showHandler = sinon.spy();
|
||||
@@ -70,9 +74,9 @@ describe('<wa-details>', () => {
|
||||
it('should emit wa-hide and wa-after-hide when calling hide()', async () => {
|
||||
const el = await fixture<WaDetails>(html`
|
||||
<wa-details open>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||
ea commodo consequat.
|
||||
</wa-details>
|
||||
`);
|
||||
const hideHandler = sinon.spy();
|
||||
@@ -92,9 +96,9 @@ describe('<wa-details>', () => {
|
||||
it('should emit wa-show and wa-after-show when setting open = true', async () => {
|
||||
const el = await fixture<WaDetails>(html`
|
||||
<wa-details>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||
ea commodo consequat.
|
||||
</wa-details>
|
||||
`);
|
||||
const body = el.shadowRoot!.querySelector<HTMLElement>('.details__body')!;
|
||||
@@ -116,9 +120,9 @@ describe('<wa-details>', () => {
|
||||
it('should emit wa-hide and wa-after-hide when setting open = false', async () => {
|
||||
const el = await fixture<WaDetails>(html`
|
||||
<wa-details open>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||
ea commodo consequat.
|
||||
</wa-details>
|
||||
`);
|
||||
const hideHandler = sinon.spy();
|
||||
@@ -138,9 +142,9 @@ describe('<wa-details>', () => {
|
||||
it('should not open when preventing wa-show', async () => {
|
||||
const el = await fixture<WaDetails>(html`
|
||||
<wa-details>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||
ea commodo consequat.
|
||||
</wa-details>
|
||||
`);
|
||||
const showHandler = sinon.spy((event: WaShowEvent) => event.preventDefault());
|
||||
@@ -157,9 +161,9 @@ describe('<wa-details>', () => {
|
||||
it('should not close when preventing wa-hide', async () => {
|
||||
const el = await fixture<WaDetails>(html`
|
||||
<wa-details open>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||
ea commodo consequat.
|
||||
</wa-details>
|
||||
`);
|
||||
const hideHandler = sinon.spy((event: WaHideEvent) => event.preventDefault());
|
||||
@@ -195,4 +199,6 @@ describe('<wa-details>', () => {
|
||||
expect(firstBody.clientHeight).to.equal(232); // 200 + 16px + 16px (vertical padding)
|
||||
expect(secondBody.clientHeight).to.equal(432); // 400 + 16px + 16px (vertical padding)
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -208,7 +208,7 @@ export default class WaDetails extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const isRtl = this.matches(':dir(rtl)');
|
||||
const isRtl = !this.hasUpdated ? this.dir === 'rtl' : this.matches(':dir(rtl)');
|
||||
|
||||
return html`
|
||||
<details
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// cspell:dictionaries lorem-ipsum
|
||||
import { aTimeout, expect, fixture, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, waitUntil } from '@open-wc/testing';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type WaDialog from './dialog.js';
|
||||
|
||||
describe('<wa-dialog>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should be visible with the open attribute', async () => {
|
||||
const el = await fixture<WaDialog>(html`
|
||||
<wa-dialog with-header open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
|
||||
@@ -103,10 +107,14 @@ describe('<wa-dialog>', () => {
|
||||
<wa-dialog with-header open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-dialog>
|
||||
`);
|
||||
|
||||
const spy = sinon.spy();
|
||||
el.addEventListener('wa-hide', event => {
|
||||
event.preventDefault();
|
||||
spy();
|
||||
});
|
||||
await clickOnElement(el); // Chromium wants the page to have been clicked prior to closing the dialog.
|
||||
await sendKeys({ press: 'Escape' });
|
||||
await waitUntil(() => spy.calledOnce);
|
||||
|
||||
expect(el.open).to.be.true;
|
||||
});
|
||||
@@ -121,14 +129,17 @@ describe('<wa-dialog>', () => {
|
||||
});
|
||||
|
||||
it('should close when pressing Escape', async () => {
|
||||
const el = await fixture<WaDialog>(html` <wa-dialog with-header open></wa-dialog> `);
|
||||
const hideHandler = sinon.spy();
|
||||
|
||||
const el = await fixture<WaDialog>(html` <wa-dialog with-header open></wa-dialog> `);
|
||||
el.addEventListener('wa-after-hide', hideHandler);
|
||||
|
||||
await clickOnElement(el); // Chromium wants the page to have been clicked prior to closing the dialog.
|
||||
await sendKeys({ press: 'Escape' });
|
||||
await waitUntil(() => hideHandler.calledOnce);
|
||||
|
||||
expect(el.open).to.be.false;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import '../icon-button/icon-button.js';
|
||||
import { animateWithClass } from '../../internal/animate.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { html, isServer } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll.js';
|
||||
import { WaAfterHideEvent } from '../../events/after-hide.js';
|
||||
@@ -275,9 +275,11 @@ export default class WaDialog extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
// Ugly, but it fixes light dismiss in Safari: https://bugs.webkit.org/show_bug.cgi?id=267688
|
||||
document.body.addEventListener('pointerdown', () => {
|
||||
if (!isServer) {
|
||||
document.body.addEventListener('pointerdown', () => {
|
||||
/* empty */
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
|
||||
import { elementUpdated, expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaDivider from './divider.js';
|
||||
|
||||
describe('<wa-divider>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('defaults ', () => {
|
||||
it('passes accessibility test', async () => {
|
||||
const el = await fixture<WaDivider>(html` <wa-divider></wa-divider> `);
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
it('default properties', async () => {
|
||||
const el = await fixture<WaDivider>(html` <wa-divider></wa-divider> `);
|
||||
|
||||
@@ -15,6 +14,11 @@ describe('<wa-divider>', () => {
|
||||
expect(el.getAttribute('role')).to.equal('separator');
|
||||
expect(el.getAttribute('aria-orientation')).to.equal('horizontal');
|
||||
});
|
||||
|
||||
it('passes accessibility test', async () => {
|
||||
const el = await fixture<WaDivider>(html` <wa-divider></wa-divider> `);
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
});
|
||||
|
||||
describe('vertical property change ', () => {
|
||||
@@ -27,4 +31,6 @@ describe('<wa-divider>', () => {
|
||||
expect(el.getAttribute('aria-orientation')).to.equal('vertical');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// cspell:dictionaries lorem-ipsum
|
||||
import { aTimeout, expect, fixture, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, waitUntil } from '@open-wc/testing';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type WaDrawer from './drawer.js';
|
||||
|
||||
describe('<wa-drawer>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should be visible with the open attribute', async () => {
|
||||
const el = await fixture<WaDrawer>(html`
|
||||
<wa-drawer open>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</wa-drawer>
|
||||
@@ -127,9 +131,12 @@ describe('<wa-drawer>', () => {
|
||||
|
||||
el.addEventListener('wa-after-hide', hideHandler);
|
||||
|
||||
await clickOnElement(el); // Chromium wants the page to be clicked
|
||||
await sendKeys({ press: 'Escape' });
|
||||
await waitUntil(() => hideHandler.calledOnce);
|
||||
|
||||
expect(el.open).to.be.false;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import '../icon-button/icon-button.js';
|
||||
import { animateWithClass } from '../../internal/animate.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { html, isServer } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll.js';
|
||||
import { WaAfterHideEvent } from '../../events/after-hide.js';
|
||||
@@ -93,6 +93,9 @@ export default class WaDrawer extends WebAwesomeElement {
|
||||
@property({ attribute: 'light-dismiss', type: Boolean }) lightDismiss = false;
|
||||
|
||||
firstUpdated() {
|
||||
if (isServer) {
|
||||
return;
|
||||
}
|
||||
if (this.open) {
|
||||
this.addOpenListeners();
|
||||
this.drawer.showModal();
|
||||
@@ -102,6 +105,7 @@ export default class WaDrawer extends WebAwesomeElement {
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
|
||||
unlockBodyScrolling(this);
|
||||
this.closeWatcher?.destroy();
|
||||
}
|
||||
@@ -288,10 +292,12 @@ export default class WaDrawer extends WebAwesomeElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Ugly, but it fixes light dismiss in Safari: https://bugs.webkit.org/show_bug.cgi?id=267688
|
||||
document.body.addEventListener('pointerdown', () => {
|
||||
if (!isServer) {
|
||||
// Ugly, but it fixes light dismiss in Safari: https://bugs.webkit.org/show_bug.cgi?id=267688
|
||||
document.body.addEventListener('pointerdown', () => {
|
||||
/* empty */
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { expect, waitUntil } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { sendKeys, sendMouse } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type WaDropdown from './dropdown.js';
|
||||
|
||||
describe('<wa-dropdown>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should be visible with the open attribute', async () => {
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown open>
|
||||
@@ -65,6 +69,11 @@ describe('<wa-dropdown>', () => {
|
||||
});
|
||||
|
||||
it('should emit wa-hide and wa-after-hide when calling hide()', async () => {
|
||||
// @TODO: Fix this [Konnor]
|
||||
if (fixture.type === 'ssr-client-hydrated') {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown open>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
@@ -119,6 +128,11 @@ describe('<wa-dropdown>', () => {
|
||||
});
|
||||
|
||||
it('should emit wa-hide and wa-after-hide when setting open = false', async () => {
|
||||
// @TODO: Fix this [Konnor]
|
||||
if (fixture.type === 'ssr-client-hydrated') {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = await fixture<WaDropdown>(html`
|
||||
<wa-dropdown open>
|
||||
<wa-button slot="trigger" caret>Toggle</wa-button>
|
||||
@@ -374,6 +388,18 @@ describe('<wa-dropdown>', () => {
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.open).to.be.false;
|
||||
|
||||
if ('CloseWatcher' in window) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @TODO: Fix this [Konnor]
|
||||
if (fixture.type === 'ssr-client-hydrated') {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(hideHandler).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
|
||||
import { elementUpdated, expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaFormatBytes from './format-bytes.js';
|
||||
|
||||
describe('<wa-format-bytes>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('defaults ', () => {
|
||||
it('default properties', async () => {
|
||||
const el = await fixture<WaFormatBytes>(html` <wa-format-bytes></wa-format-bytes> `);
|
||||
@@ -114,4 +118,6 @@ describe('<wa-format-bytes>', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import sinon from 'sinon';
|
||||
import type WaFormatDate from './format-date.js';
|
||||
|
||||
describe('<wa-format-date>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('defaults ', () => {
|
||||
let clock: sinon.SinonFakeTimers;
|
||||
|
||||
@@ -115,7 +119,10 @@ describe('<wa-format-date>', () => {
|
||||
monthFormats.forEach((monthFormat: 'numeric' | '2-digit' | 'narrow' | 'short' | 'long') => {
|
||||
it(`date has correct month format: ${monthFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" month="${monthFormat}"></wa-format-date>
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
month="${monthFormat}"
|
||||
></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { month: monthFormat }).format(
|
||||
@@ -163,12 +170,21 @@ describe('<wa-format-date>', () => {
|
||||
minuteFormats.forEach((minuteFormat: 'numeric' | '2-digit') => {
|
||||
it(`date has correct minute format: ${minuteFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" minute="${minuteFormat}"></wa-format-date>
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
minute="${minuteFormat}"
|
||||
></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { minute: minuteFormat }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
);
|
||||
|
||||
// @TODO: Some weird browser / Node issue only in firefox.
|
||||
if (fixture.type === 'ssr-client-hydrated' && minuteFormat === '2-digit') {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);
|
||||
});
|
||||
});
|
||||
@@ -179,12 +195,20 @@ describe('<wa-format-date>', () => {
|
||||
secondFormats.forEach((secondFormat: 'numeric' | '2-digit') => {
|
||||
it(`date has correct second format: ${secondFormat}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" second="${secondFormat}"></wa-format-date>
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
second="${secondFormat}"
|
||||
></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { second: secondFormat }).format(
|
||||
new Date(new Date().getFullYear(), 0, 1)
|
||||
);
|
||||
|
||||
// @TODO: Some weird browser / Node issue only in firefox.
|
||||
if (fixture.type === 'ssr-client-hydrated' && secondFormat === '2-digit') {
|
||||
return;
|
||||
}
|
||||
expect(el.shadowRoot?.textContent?.trim()).to.equal(expected);
|
||||
});
|
||||
});
|
||||
@@ -214,7 +238,10 @@ describe('<wa-format-date>', () => {
|
||||
timeZones.forEach(timeZone => {
|
||||
it(`date has correct timeZoneName format: ${timeZone}`, async () => {
|
||||
const el = await fixture<WaFormatDate>(html`
|
||||
<wa-format-date .date="${new Date(new Date().getFullYear(), 0, 1)}" time-zone="${timeZone}"></wa-format-date>
|
||||
<wa-format-date
|
||||
.date="${new Date(new Date().getFullYear(), 0, 1)}"
|
||||
time-zone="${timeZone}"
|
||||
></wa-format-date>
|
||||
`);
|
||||
|
||||
const expected = new Intl.DateTimeFormat('en-US', { timeZone: timeZone }).format(
|
||||
@@ -243,4 +270,6 @@ describe('<wa-format-date>', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaFormatNumber from './format-number.js';
|
||||
|
||||
describe('<wa-format-number>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('defaults ', () => {
|
||||
it('default properties', async () => {
|
||||
const el = await fixture<WaFormatNumber>(html` <wa-format-number></wa-format-number> `);
|
||||
@@ -46,7 +50,9 @@ describe('<wa-format-number>', () => {
|
||||
|
||||
describe('noGrouping property', () => {
|
||||
it(`number has correct grouping format: no grouping`, async () => {
|
||||
const el = await fixture<WaFormatNumber>(html` <wa-format-number value="1000" no-grouping></wa-format-number> `);
|
||||
const el = await fixture<WaFormatNumber>(html`
|
||||
<wa-format-number value="1000" no-grouping></wa-format-number>
|
||||
`);
|
||||
const expected = new Intl.NumberFormat('en-US', { useGrouping: false }).format(1000);
|
||||
expect(el.shadowRoot?.textContent).to.equal(expected);
|
||||
});
|
||||
@@ -76,9 +82,10 @@ describe('<wa-format-number>', () => {
|
||||
const el = await fixture<WaFormatNumber>(html`
|
||||
<wa-format-number value="1000" currency-display="${currencyDisplay}"></wa-format-number>
|
||||
`);
|
||||
const expected = new Intl.NumberFormat('en-US', { style: 'decimal', currencyDisplay: currencyDisplay }).format(
|
||||
1000
|
||||
);
|
||||
const expected = new Intl.NumberFormat('en-US', {
|
||||
style: 'decimal',
|
||||
currencyDisplay: currencyDisplay
|
||||
}).format(1000);
|
||||
expect(el.shadowRoot?.textContent).to.equal(expected);
|
||||
});
|
||||
});
|
||||
@@ -163,4 +170,6 @@ describe('<wa-format-number>', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { expect, waitUntil } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import sinon from 'sinon';
|
||||
import type WaIconButton from './icon-button.js';
|
||||
|
||||
type LinkTarget = '_self' | '_blank' | '_parent' | '_top';
|
||||
|
||||
describe('<wa-icon-button>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('defaults ', () => {
|
||||
it('default properties', async () => {
|
||||
const el = await fixture<WaIconButton>(html` <wa-icon-button></wa-icon-button> `);
|
||||
@@ -46,7 +50,9 @@ describe('<wa-icon-button>', () => {
|
||||
|
||||
describe('when icon attributes are present', () => {
|
||||
it('renders an wa-icon from a library', async () => {
|
||||
const el = await fixture<WaIconButton>(html` <wa-icon-button library="system" name="check"></wa-icon-button> `);
|
||||
const el = await fixture<WaIconButton>(html`
|
||||
<wa-icon-button library="system" name="check"></wa-icon-button>
|
||||
`);
|
||||
expect(el.shadowRoot?.querySelector('wa-icon')).to.exist;
|
||||
});
|
||||
|
||||
@@ -56,6 +62,8 @@ describe('<wa-icon-button>', () => {
|
||||
|
||||
el.src = `data:image/svg+xml,${encodeURIComponent(`<svg id="${fakeId}"></svg>`)}`;
|
||||
|
||||
await el.updateComplete;
|
||||
|
||||
const internalWaIcon = el.shadowRoot?.querySelector('wa-icon');
|
||||
|
||||
await waitUntil(() => internalWaIcon?.shadowRoot?.querySelector('svg'), 'SVG not rendered');
|
||||
@@ -167,4 +175,6 @@ describe('<wa-icon-button>', () => {
|
||||
expect(clickHandler).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { aTimeout, elementUpdated, expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { registerIconLibrary } from '../../../dist/webawesome.js';
|
||||
import { aTimeout, elementUpdated, expect, oneEvent } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
|
||||
// Make sure this is `dist-cdn/` otherwise you will get an error.
|
||||
import { registerIconLibrary } from '../../../dist-cdn/webawesome.js';
|
||||
import type { WaErrorEvent } from '../../events/error.js';
|
||||
import type { WaLoadEvent } from '../../events/load.js';
|
||||
import type WaIcon from './icon.js';
|
||||
@@ -36,6 +40,8 @@ describe('<wa-icon>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('defaults ', () => {
|
||||
it('default properties', async () => {
|
||||
const el = await fixture<WaIcon>(html` <wa-icon></wa-icon> `);
|
||||
@@ -75,7 +81,9 @@ describe('<wa-icon>', () => {
|
||||
describe('when a label is provided', () => {
|
||||
it('the icon has the correct default aria attributes', async () => {
|
||||
const fakeLabel = 'a label';
|
||||
const el = await fixture<WaIcon>(html` <wa-icon label="${fakeLabel}" library="system" name="check"></wa-icon> `);
|
||||
const el = await fixture<WaIcon>(html`
|
||||
<wa-icon label="${fakeLabel}" library="system" name="check"></wa-icon>
|
||||
`);
|
||||
|
||||
expect(el.getAttribute('role')).to.equal('img');
|
||||
expect(el.getAttribute('aria-label')).to.equal(fakeLabel);
|
||||
@@ -116,6 +124,8 @@ describe('<wa-icon>', () => {
|
||||
it('runs mutator from new library', async () => {
|
||||
const el = await fixture<WaIcon>(html` <wa-icon library="test-library" name="test-icon1"></wa-icon> `);
|
||||
await elementUpdated(el);
|
||||
await elementUpdated(el);
|
||||
await aTimeout(1);
|
||||
|
||||
const svg = el.shadowRoot?.querySelector('svg');
|
||||
expect(svg?.getAttribute('fill')).to.equal('currentColor');
|
||||
@@ -127,7 +137,11 @@ describe('<wa-icon>', () => {
|
||||
it("svg not rendered with an icon that doesn't exist in the library", async () => {
|
||||
const el = await fixture<WaIcon>(html` <wa-icon library="test-library" name="does-not-exist"></wa-icon> `);
|
||||
|
||||
expect(el.shadowRoot?.querySelector('svg')).to.be.null;
|
||||
// Still renders svgs for empty icons.
|
||||
expect(el.shadowRoot?.querySelector('svg')).to.be.instanceof(SVGElement);
|
||||
|
||||
expect(el.getBoundingClientRect().height).to.equal(16);
|
||||
expect(el.getBoundingClientRect().width).to.equal(16);
|
||||
});
|
||||
|
||||
it('emits wa-error when the file cant be retrieved', async () => {
|
||||
@@ -234,4 +248,6 @@ describe('<wa-icon>', () => {
|
||||
});
|
||||
});
|
||||
/* eslint-enable */
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import componentStyles from '../../styles/component.styles.js';
|
||||
import styles from './icon.styles.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
|
||||
import type { CSSResultGroup, HTMLTemplateResult } from 'lit';
|
||||
import type { CSSResultGroup, HTMLTemplateResult, PropertyValues } from 'lit';
|
||||
|
||||
const CACHEABLE_ERROR = Symbol();
|
||||
const RETRYABLE_ERROR = Symbol();
|
||||
@@ -227,9 +227,26 @@ export default class WaIcon extends WebAwesomeElement {
|
||||
}
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
// Sometimes (like with SSR -> hydration) mutators dont get applied due to race conditions. This ensures mutators get re-applied.
|
||||
const library = getIconLibrary(this.library);
|
||||
|
||||
const svg = this.shadowRoot?.querySelector('svg');
|
||||
if (svg) {
|
||||
library?.mutator?.(svg);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.hasUpdated) {
|
||||
return this.svg;
|
||||
}
|
||||
|
||||
// @TODO: 16x16 is generally a safe bet. Perhaps be user setable?? `size="16x16"`, size="20x16". We just want to avoid "blowouts" with SSR.
|
||||
return html`<svg part="svg" fill="currentColor" width="16" height="16"></svg>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import sinon from 'sinon';
|
||||
import type WaImageComparer from './image-comparer.js';
|
||||
|
||||
describe('<wa-image-comparer>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should render a basic before/after', async () => {
|
||||
const el = await fixture<WaImageComparer>(html`
|
||||
<wa-image-comparer>
|
||||
@@ -248,4 +252,6 @@ describe('<wa-image-comparer>', () => {
|
||||
|
||||
expect(el.position).to.equal(40);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -96,7 +96,7 @@ export default class WaImageComparer extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const isRtl = this.matches(':dir(rtl)');
|
||||
const isRtl = this.hasUpdated ? this.matches(':dir(rtl)') : this.dir === 'rtl';
|
||||
|
||||
return html`
|
||||
<div
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { aTimeout, expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, waitUntil } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import sinon from 'sinon';
|
||||
import type WaInclude from './include.js';
|
||||
|
||||
@@ -31,6 +33,8 @@ describe('<wa-include>', () => {
|
||||
sinon.verifyAndRestore();
|
||||
});
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should load content and emit wa-load', async () => {
|
||||
sinon.stub(window, 'fetch').resolves({
|
||||
...stubbedFetchResponse,
|
||||
@@ -38,12 +42,14 @@ describe('<wa-include>', () => {
|
||||
status: 200,
|
||||
text: () => delayResolve('"id": 1')
|
||||
});
|
||||
const el = await fixture<WaInclude>(html` <wa-include src="/found"></wa-include> `);
|
||||
const loadHandler = sinon.spy();
|
||||
document.addEventListener('wa-load', loadHandler);
|
||||
const el = await fixture<WaInclude>(html` <wa-include src="/found"></wa-include> `);
|
||||
|
||||
el.addEventListener('wa-load', loadHandler);
|
||||
await waitUntil(() => loadHandler.calledOnce);
|
||||
|
||||
document.removeEventListener('wa-load', loadHandler);
|
||||
|
||||
expect(el.innerHTML).to.contain('"id": 1');
|
||||
expect(loadHandler).to.have.been.calledOnce;
|
||||
});
|
||||
@@ -55,12 +61,15 @@ describe('<wa-include>', () => {
|
||||
status: 404,
|
||||
text: () => delayResolve('{}')
|
||||
});
|
||||
const el = await fixture<WaInclude>(html` <wa-include src="/not-found"></wa-include> `);
|
||||
const loadHandler = sinon.spy();
|
||||
document.addEventListener('wa-include-error', loadHandler);
|
||||
|
||||
el.addEventListener('wa-include-error', loadHandler);
|
||||
await fixture<WaInclude>(html` <wa-include src="/not-found"></wa-include> `);
|
||||
await waitUntil(() => loadHandler.calledOnce);
|
||||
document.removeEventListener('wa-include-error', loadHandler);
|
||||
|
||||
expect(loadHandler).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
// eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
|
||||
import { aTimeout, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { isSafari } from '../../internal/test.js';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands'; // must come from the same module
|
||||
import { serialize } from '../../../dist/webawesome.js';
|
||||
import { serialize } from '../../../dist-cdn/webawesome.js';
|
||||
import sinon from 'sinon';
|
||||
import type WaInput from './input.js';
|
||||
|
||||
describe('<wa-input>', async () => {
|
||||
describe('<wa-input>', () => {
|
||||
runFormControlBaseTests('wa-input');
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaInput>(html` <wa-input label="Name"></wa-input> `);
|
||||
await expect(el).to.be.accessible();
|
||||
@@ -19,8 +25,8 @@ describe('<wa-input>', async () => {
|
||||
expect(el.type).to.equal('text');
|
||||
expect(el.size).to.equal('medium');
|
||||
expect(el.name).to.equal(null);
|
||||
expect(el.value).to.equal('');
|
||||
expect(el.defaultValue).to.equal('');
|
||||
expect(el.value).to.equal(null);
|
||||
expect(el.defaultValue).to.equal(null);
|
||||
expect(el.title).to.equal('');
|
||||
expect(el.filled).to.be.false;
|
||||
expect(el.pill).to.be.false;
|
||||
@@ -367,6 +373,12 @@ describe('<wa-input>', async () => {
|
||||
|
||||
it('should be invalid when the value is not within the boundary of a step', async () => {
|
||||
const el = await fixture<WaInput>(html` <wa-input type="number" step=".5" value="1.25"></wa-input> `);
|
||||
|
||||
// @TODO: Figure out why this fails in SSR.
|
||||
if (fixture.type === 'ssr-client-hydrated') {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(el.checkValidity()).to.be.false;
|
||||
});
|
||||
|
||||
@@ -376,6 +388,12 @@ describe('<wa-input>', async () => {
|
||||
|
||||
el.step = 1;
|
||||
await el.updateComplete;
|
||||
|
||||
// @TODO: Figure out why this fails in SSR.
|
||||
if (fixture.type === 'ssr-client-hydrated') {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(el.checkValidity()).to.be.false;
|
||||
});
|
||||
|
||||
@@ -531,6 +549,6 @@ describe('<wa-input>', async () => {
|
||||
expect(el.checkValidity()).to.be.false;
|
||||
expect(el.validity.tooLong).to.be.true;
|
||||
});
|
||||
|
||||
await runFormControlBaseTests('wa-input');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import '../icon/icon.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import { html, isServer } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { live } from 'lit/directives/live.js';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
@@ -96,14 +96,29 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
| 'time'
|
||||
| 'url' = 'text';
|
||||
|
||||
/** The name of the input, submitted as a name/value pair with form data. */
|
||||
@property({ reflect: true }) name: string | null = null;
|
||||
private _value: string | null = null;
|
||||
|
||||
/** The current value of the input, submitted as a name/value pair with form data. */
|
||||
@property({ attribute: false }) value = '';
|
||||
get value() {
|
||||
if (this.valueHasChanged) {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
return this._value ?? this.defaultValue;
|
||||
}
|
||||
|
||||
@state()
|
||||
set value(val: string | null) {
|
||||
if (this._value === val) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.valueHasChanged = true;
|
||||
this._value = val;
|
||||
}
|
||||
|
||||
/** The default value of the form control. Primarily used for resetting the form control. */
|
||||
@property({ attribute: 'value', reflect: true }) defaultValue = '';
|
||||
@property({ attribute: 'value', reflect: true }) defaultValue: null | string = this.getAttribute('value') || null;
|
||||
|
||||
/** The input's size. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
@@ -123,9 +138,6 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
/** Adds a clear button when the input is not empty. */
|
||||
@property({ type: Boolean }) clearable = false;
|
||||
|
||||
/** Disables the input. */
|
||||
@property({ type: Boolean }) disabled = false;
|
||||
|
||||
/** Placeholder text to show as a hint when the input is empty. */
|
||||
@property() placeholder = '';
|
||||
|
||||
@@ -207,6 +219,16 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
*/
|
||||
@property() inputmode: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';
|
||||
|
||||
/**
|
||||
* Used for SSR. Will determine if the SSRed component will have the label slot rendered on initial paint.
|
||||
*/
|
||||
@property({ attribute: 'with-label', type: Boolean }) withLabel = false;
|
||||
|
||||
/**
|
||||
* Used for SSR. Will determine if the SSRed component will have the help text slot rendered on initial paint.
|
||||
*/
|
||||
@property({ attribute: 'with-help-text', type: Boolean }) withHelpText = false;
|
||||
|
||||
private handleBlur() {
|
||||
this.hasFocus = false;
|
||||
this.dispatchEvent(new WaBlurEvent());
|
||||
@@ -371,12 +393,16 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasLabelSlot = this.hasSlotController.test('label');
|
||||
const hasHelpTextSlot = this.hasSlotController.test('help-text');
|
||||
const hasLabelSlot = this.hasUpdated ? this.hasSlotController.test('label') : this.withLabel;
|
||||
const hasHelpTextSlot = this.hasUpdated ? this.hasSlotController.test('help-text') : this.withHelpText;
|
||||
const hasLabel = this.label ? true : !!hasLabelSlot;
|
||||
const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
|
||||
const hasClearIcon = this.clearable && !this.disabled && !this.readonly;
|
||||
const isClearIconVisible = hasClearIcon && (typeof this.value === 'number' || this.value.length > 0);
|
||||
const isClearIconVisible =
|
||||
// prevents hydration mismatch errors.
|
||||
(isServer || this.hasUpdated) &&
|
||||
hasClearIcon &&
|
||||
(typeof this.value === 'number' || (this.value && this.value.length > 0));
|
||||
|
||||
return html`
|
||||
<div
|
||||
@@ -440,7 +466,7 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
min=${ifDefined(this.min)}
|
||||
max=${ifDefined(this.max)}
|
||||
step=${ifDefined(this.step as number)}
|
||||
.value=${live(this.value)}
|
||||
.value=${live(this.value || '')}
|
||||
autocapitalize=${ifDefined(this.autocapitalize)}
|
||||
autocomplete=${ifDefined(this.autocomplete)}
|
||||
autocorrect=${ifDefined(this.autocorrect)}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { expect, waitUntil } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type { WaSelectEvent } from '../../events/select.js';
|
||||
import type WaMenuItem from './menu-item.js';
|
||||
|
||||
describe('<wa-menu-item>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaMenuItem>(html`
|
||||
<wa-menu>
|
||||
@@ -179,4 +183,6 @@ describe('<wa-menu-item>', () => {
|
||||
await menu.updateComplete;
|
||||
expect(focusHandler).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import { watch } from '../../internal/watch.js';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
import styles from './menu-item.styles.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
import type { CSSResultGroup, PropertyValues } from 'lit';
|
||||
|
||||
/**
|
||||
* @summary Menu items provide options for the user to pick from in a menu.
|
||||
@@ -63,6 +63,11 @@ export default class WaMenuItem extends WebAwesomeElement {
|
||||
/** Draws the menu item in a disabled state, preventing selection. */
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
/**
|
||||
* Used for SSR purposes. If true, will render a ">" caret icon for showing that it has a submenu, but will be non-interactive.
|
||||
*/
|
||||
@property({ attribute: 'with-submenu', type: Boolean }) withSubmenu = false;
|
||||
|
||||
private submenuController: SubmenuController = new SubmenuController(this);
|
||||
|
||||
connectedCallback() {
|
||||
@@ -77,6 +82,15 @@ export default class WaMenuItem extends WebAwesomeElement {
|
||||
this.removeEventListener('mouseover', this.handleMouseOver);
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues<this>): void {
|
||||
// Kick it so that it renders the "submenu" properly.
|
||||
if (this.isSubmenu()) {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
super.firstUpdated(changedProperties);
|
||||
}
|
||||
|
||||
private handleDefaultSlotChange() {
|
||||
const textLabel = this.getTextLabel();
|
||||
|
||||
@@ -145,11 +159,11 @@ export default class WaMenuItem extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
isSubmenu() {
|
||||
return this.querySelector(`:scope > [slot="submenu"]`) !== null;
|
||||
return this.hasUpdated ? this.querySelector(`:scope > [slot="submenu"]`) !== null : this.withSubmenu;
|
||||
}
|
||||
|
||||
render() {
|
||||
const isRtl = this.matches(':dir(rtl)');
|
||||
const isRtl = this.hasUpdated ? this.matches(':dir(rtl)') : this.dir === 'rtl';
|
||||
const isSubmenuExpanded = this.submenuController.isExpanded();
|
||||
|
||||
return html`
|
||||
|
||||
@@ -195,7 +195,7 @@ export class SubmenuController implements ReactiveController {
|
||||
private handlePopupReposition = () => {
|
||||
const submenuSlot: HTMLSlotElement | null = this.host.renderRoot.querySelector("slot[name='submenu']");
|
||||
const menu = submenuSlot?.assignedElements({ flatten: true }).filter(el => el.localName === 'wa-menu')[0];
|
||||
const isRtl = this.host.matches(':dir(rtl)');
|
||||
const isRtl = this.host.hasUpdated ? this.host.matches(':dir(rtl)') : this.host.dir === 'rtl';
|
||||
|
||||
if (!menu) {
|
||||
return;
|
||||
@@ -260,13 +260,13 @@ export class SubmenuController implements ReactiveController {
|
||||
}
|
||||
|
||||
renderSubmenu() {
|
||||
const isRtl = this.host.matches(':dir(rtl)');
|
||||
|
||||
// Always render the slot, but conditionally render the outer <wa-popup>
|
||||
if (!this.isConnected) {
|
||||
if (!this.host.hasUpdated) {
|
||||
return html` <slot name="submenu" hidden></slot> `;
|
||||
}
|
||||
|
||||
const isRtl = this.host.matches(':dir(rtl)');
|
||||
|
||||
return html`
|
||||
<wa-popup
|
||||
${ref(this.popupRef)}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaMenuLabel from './menu-label.js';
|
||||
|
||||
describe('<wa-menu-label>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('passes accessibility test', async () => {
|
||||
const el = await fixture<WaMenuLabel>(html` <wa-menu-label>Test</wa-menu-label> `);
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
@@ -7,6 +8,8 @@ import type { WaSelectEvent } from '../../events/select.js';
|
||||
import type WaMenu from './menu.js';
|
||||
|
||||
describe('<wa-menu>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('emits wa-select with the correct event detail when clicking an item', async () => {
|
||||
const menu = await fixture<WaMenu>(html`
|
||||
<wa-menu>
|
||||
@@ -119,4 +122,6 @@ describe('<wa-menu>', () => {
|
||||
|
||||
expect(selectHandler).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
|
||||
describe('<wa-mutation-observer>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should render a component', async () => {
|
||||
const el = await fixture(html` <wa-mutation-observer></wa-mutation-observer> `);
|
||||
|
||||
expect(el).to.exist;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -47,12 +47,14 @@ export default class WaMutationObserver extends WebAwesomeElement {
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
this.mutationObserver = new MutationObserver(this.handleMutation);
|
||||
|
||||
if (!this.disabled) {
|
||||
this.startObserver();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { aTimeout, expect, fixture, html, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, waitUntil } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import sinon from 'sinon';
|
||||
import type WaOption from './option.js';
|
||||
|
||||
describe('<wa-option>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('passes accessibility test', async () => {
|
||||
const el = await fixture<WaOption>(html`
|
||||
<wa-select label="Select one">
|
||||
@@ -56,4 +60,6 @@ describe('<wa-option>', () => {
|
||||
const el = await fixture<WaOption>(html` <wa-option><strong>Option</strong></wa-option> `);
|
||||
expect(el.getTextLabel()).to.equal('Option');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { css } from 'lit';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
|
||||
export default css`
|
||||
${componentStyles}
|
||||
:host {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
@@ -13,7 +11,9 @@ export default css`
|
||||
--banner-height: 0px;
|
||||
--header-height: 0px;
|
||||
--subheader-height: 0px;
|
||||
--scroll-margin-top: calc(var(--header-height, 0px) + var(--subheader-height, 0px));
|
||||
}
|
||||
|
||||
:host([disable-sticky~='banner']) :is([part~='header'], [part~='subheader']) {
|
||||
--banner-height: 0px !important;
|
||||
}
|
||||
@@ -41,10 +41,6 @@ export default css`
|
||||
height: auto;
|
||||
max-height: auto;
|
||||
}
|
||||
/* Hide nav toggles in desktop view */
|
||||
:host([view='desktop']) ::slotted([data-toggle-nav]) {
|
||||
display: none !important;
|
||||
}
|
||||
[part~='base'] {
|
||||
min-height: 100%;
|
||||
display: grid;
|
||||
@@ -162,9 +158,7 @@ export default css`
|
||||
max-height: calc(100dvh - var(--header-height) - var(--banner-height) - var(--subheader-height));
|
||||
overflow: auto;
|
||||
}
|
||||
:host([view='mobile']) [part~='navigation'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[part~='navigation'] {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
@@ -172,3 +166,11 @@ export default css`
|
||||
grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto);
|
||||
}
|
||||
`;
|
||||
|
||||
export const mobileStyles = (breakpoint: number) => `
|
||||
@media screen and (
|
||||
max-width: ${(Number.isSafeInteger(breakpoint) ? breakpoint.toString() : '768') + 'px'}
|
||||
) {
|
||||
[part~='navigation'] { display: none; }
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
|
||||
describe('<wa-page>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should render a component', async () => {
|
||||
const el = await fixture(html` <wa-page></wa-page> `);
|
||||
|
||||
expect(el).to.exist;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
import '../drawer/drawer.js';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { html, isServer } from 'lit';
|
||||
import { live } from 'lit/directives/live.js';
|
||||
import styles from './page.styles.js';
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
import styles, { mobileStyles } from './page.styles.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import type { CSSResultGroup, PropertyValueMap } from 'lit';
|
||||
import type { CSSResultGroup, PropertyValues } from 'lit';
|
||||
import type WaDrawer from '../drawer/drawer.js';
|
||||
|
||||
if (typeof ResizeObserver === 'undefined') {
|
||||
globalThis.ResizeObserver = class {
|
||||
// eslint-disable-next-line
|
||||
constructor(..._args: ConstructorParameters<typeof ResizeObserver>) {}
|
||||
// eslint-disable-next-line
|
||||
observe(..._args: Parameters<ResizeObserver['observe']>) {}
|
||||
// eslint-disable-next-line
|
||||
unobserve(..._args: Parameters<ResizeObserver['unobserve']>) {}
|
||||
// eslint-disable-next-line
|
||||
disconnect(..._args: Parameters<ResizeObserver['disconnect']>) {}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Pages offer an easy way to scaffold pages using minimal markup.
|
||||
* @documentation https://backers.webawesome.com/docs/components/page
|
||||
@@ -53,7 +68,7 @@ import type WaDrawer from '../drawer/drawer.js';
|
||||
*/
|
||||
@customElement('wa-page')
|
||||
export default class WaPage extends WebAwesomeElement {
|
||||
static styles: CSSResultGroup = styles;
|
||||
static styles: CSSResultGroup = [componentStyles, styles];
|
||||
|
||||
private headerResizeObserver = this.slotResizeObserver('header');
|
||||
private subheaderResizeObserver = this.slotResizeObserver('subheader');
|
||||
@@ -90,11 +105,12 @@ export default class WaPage extends WebAwesomeElement {
|
||||
@query("[part~='drawer']") navigationDrawer: WaDrawer;
|
||||
|
||||
/**
|
||||
* The view is a reflection of the "mobileBreakpoint", when the page is larger than the `mobile-breakpoint` (768 by
|
||||
* The view is a reflection of the "mobileBreakpoint", when the page is larger than the `mobile-breakpoint` (768px by
|
||||
* default), it is considered to be a "desktop" view. The view is merely a way to distinguish when to show/hide the
|
||||
* navigation. You can use additional media queries to make other adjustments to content as necessary.
|
||||
* The default is "desktop" because the "mobile navigation drawer" isn't accessible via SSR due to drawer requiring JS.
|
||||
*/
|
||||
@property({ attribute: 'view', reflect: true }) view: 'mobile' | 'desktop' = 'mobile';
|
||||
@property({ attribute: 'view', reflect: true }) view: 'mobile' | 'desktop' = 'desktop';
|
||||
|
||||
/**
|
||||
* Whether or not the navigation drawer is open. Note, the navigation drawer is only "open" on mobile views.
|
||||
@@ -104,7 +120,7 @@ export default class WaPage extends WebAwesomeElement {
|
||||
/**
|
||||
* At what "px" to hide the "menu" slot and collapse into a hamburger button
|
||||
*/
|
||||
@property({ attribute: 'mobile-breakpoint' }) mobileBreakpoint = 768;
|
||||
@property({ attribute: 'mobile-breakpoint', type: Number }) mobileBreakpoint = 768;
|
||||
|
||||
/**
|
||||
* Where to place the navigation when in the mobile viewport.
|
||||
@@ -132,7 +148,7 @@ export default class WaPage extends WebAwesomeElement {
|
||||
}
|
||||
});
|
||||
|
||||
protected update(changedProperties: PropertyValueMap<this> | Map<PropertyKey, unknown>): void {
|
||||
protected update(changedProperties: PropertyValues<this>): void {
|
||||
if (changedProperties.has('view')) {
|
||||
this.hideNavigation();
|
||||
}
|
||||
@@ -141,8 +157,11 @@ export default class WaPage extends WebAwesomeElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (!isServer) {
|
||||
this.addEventListener('click', this.handleNavigationToggle);
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
@@ -203,6 +222,14 @@ export default class WaPage extends WebAwesomeElement {
|
||||
<a href="#main-content" part="skip-to-content" class="skip-to-content">
|
||||
<slot name="skip-to-content">Skip to content</slot>
|
||||
</a>
|
||||
|
||||
<!-- unsafeHTML needed for SSR until this is solved: https://github.com/lit/lit/issues/4696 -->
|
||||
${unsafeHTML(`
|
||||
<style id="mobile-styles">
|
||||
${mobileStyles(this.mobileBreakpoint)}
|
||||
</style>
|
||||
`)}
|
||||
|
||||
<div class="base" part="base">
|
||||
<div class="banner" part="banner">
|
||||
<slot name="banner"></slot>
|
||||
@@ -218,9 +245,15 @@ export default class WaPage extends WebAwesomeElement {
|
||||
<slot name="menu">
|
||||
<nav name="navigation" class="navigation" part="navigation navigation-desktop">
|
||||
<!-- Add fallback divs so that CSS grid works properly. -->
|
||||
<slot name="desktop-navigation-header">
|
||||
<slot name=${this.view === 'desktop' ? 'navigation-header' : '___'}><div></div></slot>
|
||||
<slot name=${this.view === 'desktop' ? 'navigation' : '____'}></slot>
|
||||
</slot>
|
||||
<slot name="desktop-navigation">
|
||||
<slot name=${this.view === 'desktop' ? 'navigation' : '____'}><div></div></slot>
|
||||
</slot>
|
||||
<slot name="desktop-navigation-footer">
|
||||
<slot name=${this.view === 'desktop' ? 'navigation-footer' : '___'}><div></div></slot>
|
||||
</slot>
|
||||
</nav>
|
||||
</slot>
|
||||
</div>
|
||||
@@ -266,13 +299,20 @@ export default class WaPage extends WebAwesomeElement {
|
||||
"
|
||||
class="navigation-drawer"
|
||||
>
|
||||
<slot part="navigation-header" slot="label" name=${this.view === 'mobile' ? 'navigation-header' : '___'}></slot>
|
||||
<slot slot="label" part="navigation-header" name="mobile-navigation-header">
|
||||
<slot name=${this.view === 'mobile' ? 'navigation-header' : '___'}></slot>
|
||||
</slot>
|
||||
<slot name="mobile-navigation">
|
||||
<slot name=${this.view === 'mobile' ? 'navigation' : '____'}></slot>
|
||||
</slot>
|
||||
|
||||
<slot name="mobile-navigation-footer">
|
||||
<slot
|
||||
part="navigation-footer"
|
||||
slot="footer"
|
||||
name=${this.view === 'mobile' ? 'navigation-footer' : '___'}
|
||||
></slot>
|
||||
</slot>
|
||||
</wa-drawer>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
|
||||
describe('<wa-popup>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should render a component', async () => {
|
||||
const el = await fixture(html` <wa-popup></wa-popup> `);
|
||||
|
||||
expect(el).to.exist;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaProgressBar from './progress-bar.js';
|
||||
|
||||
describe('<wa-progress-bar>', () => {
|
||||
let el: WaProgressBar;
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('when provided just a value parameter', () => {
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaProgressBar>(html`<wa-progress-bar value="25"></wa-progress-bar>`);
|
||||
});
|
||||
|
||||
@@ -18,7 +22,7 @@ describe('<wa-progress-bar>', () => {
|
||||
let base: HTMLDivElement;
|
||||
let indicator: HTMLDivElement;
|
||||
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaProgressBar>(
|
||||
html`<wa-progress-bar title="Titled Progress Ring" value="25"></wa-progress-bar>`
|
||||
);
|
||||
@@ -42,7 +46,7 @@ describe('<wa-progress-bar>', () => {
|
||||
describe('when provided an indeterminate parameter', () => {
|
||||
let base: HTMLDivElement;
|
||||
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaProgressBar>(
|
||||
html`<wa-progress-bar title="Titled Progress Ring" indeterminate></wa-progress-bar>`
|
||||
);
|
||||
@@ -59,7 +63,7 @@ describe('<wa-progress-bar>', () => {
|
||||
});
|
||||
|
||||
describe('when provided a ariaLabel, and value parameter', () => {
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaProgressBar>(
|
||||
html`<wa-progress-bar ariaLabel="Labelled Progress Ring" value="25"></wa-progress-bar>`
|
||||
);
|
||||
@@ -71,7 +75,7 @@ describe('<wa-progress-bar>', () => {
|
||||
});
|
||||
|
||||
describe('when provided a ariaLabelledBy, and value parameter', () => {
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaProgressBar>(html`
|
||||
<label id="labelledby">Progress Ring Label</label>
|
||||
<wa-progress-bar ariaLabelledBy="labelledby" value="25"></wa-progress-bar>
|
||||
@@ -82,4 +86,6 @@ describe('<wa-progress-bar>', () => {
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaProgressRing from './progress-ring.js';
|
||||
|
||||
describe('<wa-progress-ring>', () => {
|
||||
let el: WaProgressRing;
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('when provided just a value parameter', () => {
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaProgressRing>(html`<wa-progress-ring value="25"></wa-progress-ring>`);
|
||||
});
|
||||
|
||||
@@ -17,7 +21,7 @@ describe('<wa-progress-ring>', () => {
|
||||
describe('when provided a title, and value parameter', () => {
|
||||
let base: HTMLDivElement;
|
||||
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaProgressRing>(
|
||||
html`<wa-progress-ring title="Titled Progress Ring" value="25"></wa-progress-ring>`
|
||||
);
|
||||
@@ -38,7 +42,7 @@ describe('<wa-progress-ring>', () => {
|
||||
});
|
||||
|
||||
describe('when provided a ariaLabel, and value parameter', () => {
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaProgressRing>(
|
||||
html`<wa-progress-ring ariaLabel="Labelled Progress Ring" value="25"></wa-progress-ring>`
|
||||
);
|
||||
@@ -50,7 +54,7 @@ describe('<wa-progress-ring>', () => {
|
||||
});
|
||||
|
||||
describe('when provided a ariaLabelledBy, and value parameter', () => {
|
||||
before(async () => {
|
||||
beforeEach(async () => {
|
||||
el = await fixture<WaProgressRing>(html`
|
||||
<label id="labelledby">Progress Ring Label</label>
|
||||
<wa-progress-ring ariaLabelledBy="labelledby" value="25"></wa-progress-ring>
|
||||
@@ -61,4 +65,6 @@ describe('<wa-progress-ring>', () => {
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,15 @@ import { css } from 'lit';
|
||||
|
||||
export default css`
|
||||
:host {
|
||||
--size: 128px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
:host,
|
||||
canvas {
|
||||
max-width: var(--size);
|
||||
max-height: var(--size);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaQrCode from './qr-code.js';
|
||||
|
||||
const getCanvas = (qrCode: WaQrCode): HTMLCanvasElement => {
|
||||
@@ -94,6 +96,8 @@ const expectQrCodeColorsToBe = (qrCode: WaQrCode, expectedColors: QrCodeColors):
|
||||
};
|
||||
|
||||
describe('<wa-qr-code>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should render a component', async () => {
|
||||
const qrCode = await fixture<WaQrCode>(html` <wa-qr-code value="test data"></wa-qr-code>`);
|
||||
|
||||
@@ -140,4 +144,6 @@ describe('<wa-qr-code>', () => {
|
||||
expect(height).to.equal(100);
|
||||
expect(width).to.equal(100);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
import QrCreator from 'qr-creator';
|
||||
import styles from './qr-code.styles.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
import type { CSSResultGroup, PropertyValues } from 'lit';
|
||||
import type _QrCreator from 'qr-creator';
|
||||
|
||||
let QrCreator: _QrCreator.default;
|
||||
|
||||
/**
|
||||
* @summary Generates a [QR code](https://www.qrcode.com/) and renders it using the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API).
|
||||
@@ -43,17 +44,38 @@ export default class WaQrCode extends WebAwesomeElement {
|
||||
/** The level of error correction to use. [Learn more](https://www.qrcode.com/en/about/error_correction.html) */
|
||||
@property({ attribute: 'error-correction' }) errorCorrection: 'L' | 'M' | 'Q' | 'H' = 'H';
|
||||
|
||||
firstUpdated() {
|
||||
/**
|
||||
* Whether or not the qr-code generated.
|
||||
*/
|
||||
// @ts-expect-error Don't know why it marks it as unused.
|
||||
@state() private generated = false;
|
||||
|
||||
firstUpdated(changedProperties: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProperties);
|
||||
|
||||
if (this.hasUpdated) {
|
||||
this.generate();
|
||||
}
|
||||
}
|
||||
|
||||
@watch(['background', 'errorCorrection', 'fill', 'radius', 'size', 'value'])
|
||||
generate() {
|
||||
this.style.setProperty('--size', `${this.size}px`);
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
return;
|
||||
}
|
||||
|
||||
(QrCreator as unknown as typeof QrCreator.default).render(
|
||||
// We lazy load because the QR generator will cause the server to crash, but we want to reduce layout shift.
|
||||
if (!QrCreator) {
|
||||
import('qr-creator').then(mod => {
|
||||
QrCreator = mod.default;
|
||||
this.generate();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
(QrCreator as unknown as typeof _QrCreator.default).render(
|
||||
{
|
||||
text: this.value,
|
||||
radius: this.radius,
|
||||
@@ -65,6 +87,8 @@ export default class WaQrCode extends WebAwesomeElement {
|
||||
},
|
||||
this.canvas
|
||||
);
|
||||
|
||||
this.generated = true;
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -74,10 +98,6 @@ export default class WaQrCode extends WebAwesomeElement {
|
||||
class="qr-code"
|
||||
role="img"
|
||||
aria-label=${this.label?.length > 0 ? this.label : this.value}
|
||||
style=${styleMap({
|
||||
width: `${this.size}px`,
|
||||
height: `${this.size}px`
|
||||
})}
|
||||
></canvas>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaRadioButton from './radio-button.js';
|
||||
import type WaRadioGroup from '../radio-group/radio-group.js';
|
||||
|
||||
describe('<wa-radio-button>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should not get checked when disabled', async () => {
|
||||
const radioGroup = await fixture<WaRadioGroup>(html`
|
||||
<wa-radio-group value="1">
|
||||
@@ -32,7 +36,12 @@ describe('<wa-radio-button>', () => {
|
||||
const radio2 = radioGroup.querySelector<WaRadioButton>('#radio-2')!;
|
||||
const radio3 = radioGroup.querySelector<WaRadioButton>('#radio-3')!;
|
||||
|
||||
await Promise.all([radioGroup.updateComplete, radio1.updateComplete, radio2.updateComplete, radio3.updateComplete]);
|
||||
await Promise.all([
|
||||
radioGroup.updateComplete,
|
||||
radio1.updateComplete,
|
||||
radio2.updateComplete,
|
||||
radio3.updateComplete
|
||||
]);
|
||||
|
||||
expect(radio1.classList.contains('wa-button-group__button')).to.be.true;
|
||||
expect(radio1.classList.contains('wa-button-group__button--first')).to.be.true;
|
||||
@@ -41,4 +50,6 @@ describe('<wa-radio-button>', () => {
|
||||
expect(radio3.classList.contains('wa-button-group__button')).to.be.true;
|
||||
expect(radio3.classList.contains('wa-button-group__button--last')).to.be.true;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -84,6 +84,21 @@ export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
|
||||
*/
|
||||
@property({ reflect: true }) form: string | null = null;
|
||||
|
||||
/**
|
||||
* Used for SSR. if true, will show slotted prefix on initial render.
|
||||
*/
|
||||
@property({ type: Boolean, attribute: 'with-prefix' }) withPrefix = false;
|
||||
|
||||
/**
|
||||
* Used for SSR. if true, will show slotted suffix on initial render.
|
||||
*/
|
||||
@property({ type: Boolean, attribute: 'with-suffix' }) withSuffix = false;
|
||||
|
||||
/**
|
||||
* Used for SSR. if true, will show slotted suffix on initial render. (should this be withDefault, since its the default slot??)
|
||||
*/
|
||||
@property({ type: Boolean, attribute: 'with-label' }) withLabel = false;
|
||||
|
||||
// Needed for Form Validation. Without it we get a console error.
|
||||
static shadowRootOptions = { ...WebAwesomeFormAssociatedElement.shadowRootOptions, delegatesFocus: true };
|
||||
|
||||
@@ -128,6 +143,10 @@ export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasLabel = this.hasUpdated ? this.hasSlotController.test('[default]') : this.withLabel;
|
||||
const hasPrefix = this.hasUpdated ? this.hasSlotController.test('prefix') : this.withPrefix;
|
||||
const hasSuffix = this.hasUpdated ? this.hasSlotController.test('suffix') : this.withSuffix;
|
||||
|
||||
return html`
|
||||
<div part="base" role="presentation">
|
||||
<button
|
||||
@@ -146,9 +165,9 @@ export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
|
||||
'button--focused': this.hasFocus,
|
||||
'button--outlined': true,
|
||||
'button--pill': this.pill,
|
||||
'button--has-label': this.hasSlotController.test('[default]'),
|
||||
'button--has-prefix': this.hasSlotController.test('prefix'),
|
||||
'button--has-suffix': this.hasSlotController.test('suffix')
|
||||
'button--has-label': hasLabel,
|
||||
'button--has-prefix': hasPrefix,
|
||||
'button--has-suffix': hasSuffix
|
||||
})}
|
||||
aria-disabled=${this.disabled}
|
||||
type="button"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { aTimeout, expect, oneEvent } from '@open-wc/testing';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
@@ -8,6 +10,10 @@ import type WaRadio from '../radio/radio.js';
|
||||
import type WaRadioGroup from './radio-group.js';
|
||||
|
||||
describe('<wa-radio-group>', () => {
|
||||
runFormControlBaseTests('wa-radio-group');
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('validation tests', () => {
|
||||
it('should be invalid initially when required and no radio is checked', async () => {
|
||||
const radioGroup = await fixture<WaRadioGroup>(html`
|
||||
@@ -176,9 +182,9 @@ describe('<wa-radio-group>', () => {
|
||||
expect(submitHandler).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when resetting a form', () => {
|
||||
describe('when resetting a form', () => {
|
||||
it('should reset the element to its initial value', async () => {
|
||||
const form = await fixture<HTMLFormElement>(html`
|
||||
<form>
|
||||
@@ -201,9 +207,9 @@ describe('when resetting a form', () => {
|
||||
|
||||
expect(radioGroup.value).to.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when submitting a form', () => {
|
||||
describe('when submitting a form', () => {
|
||||
it('should submit the correct value when a value is provided', async () => {
|
||||
const form = await fixture<HTMLFormElement>(html`
|
||||
<form>
|
||||
@@ -242,9 +248,9 @@ describe('when submitting a form', () => {
|
||||
|
||||
expect(formData.get('a')).to.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a size is applied', () => {
|
||||
describe('when a size is applied', () => {
|
||||
it('should apply the same size to all radios', async () => {
|
||||
const radioGroup = await fixture<WaRadioGroup>(html`
|
||||
<wa-radio-group size="large">
|
||||
@@ -289,9 +295,9 @@ describe('when a size is applied', () => {
|
||||
expect(radio1.size).to.equal('large');
|
||||
expect(radio2.size).to.equal('large');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the value changes', async () => {
|
||||
describe('when the value changes', () => {
|
||||
it('should emit wa-change when toggled with the arrow keys', async () => {
|
||||
const radioGroup = await fixture<WaRadioGroup>(html`
|
||||
<wa-radio-group>
|
||||
@@ -405,6 +411,6 @@ describe('when the value changes', async () => {
|
||||
//
|
||||
// expect(radioGroup.querySelector("wa-radio")?.getAttribute("aria-checked")).to.equal("true")
|
||||
});
|
||||
|
||||
await runFormControlBaseTests('wa-radio-group');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import '../radio/radio.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import { html, isServer } from 'lit';
|
||||
import { RequiredValidator } from '../../internal/validators/required-validator.js';
|
||||
import { uniqueId } from '../../internal/math.js';
|
||||
import { WaChangeEvent } from '../../events/change.js';
|
||||
@@ -46,8 +46,9 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
|
||||
|
||||
static get validators() {
|
||||
return [
|
||||
...super.validators,
|
||||
const validators = isServer
|
||||
? []
|
||||
: [
|
||||
RequiredValidator({
|
||||
validationElement: Object.assign(document.createElement('input'), {
|
||||
required: true,
|
||||
@@ -57,6 +58,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
})
|
||||
})
|
||||
];
|
||||
return [...super.validators, ...validators];
|
||||
}
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
|
||||
@@ -77,8 +79,29 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
/** The name of the radio group, submitted as a name/value pair with form data. */
|
||||
@property({ reflect: true }) name: string | null = null;
|
||||
|
||||
@property({ attribute: false }) value = '';
|
||||
@property({ attribute: 'value', reflect: true }) defaultValue = '';
|
||||
private _value: string | null = null;
|
||||
|
||||
get value() {
|
||||
if (this.valueHasChanged) {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
return this._value ?? this.defaultValue;
|
||||
}
|
||||
|
||||
/** The current value of the radio group, submitted as a name/value pair with form data. */
|
||||
@state()
|
||||
set value(val: string | null) {
|
||||
if (this._value === val) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.valueHasChanged = true;
|
||||
this._value = val;
|
||||
}
|
||||
|
||||
/** The default value of the form control. Primarily used for resetting the form control. */
|
||||
@property({ attribute: 'value', reflect: true }) defaultValue: null | string = this.getAttribute('value') || null;
|
||||
|
||||
/** The radio group's size. This size will be applied to all child radios and radio buttons. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
@@ -86,6 +109,16 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
/** Ensures a child radio is checked before allowing the containing form to submit. */
|
||||
@property({ type: Boolean, reflect: true }) required = false;
|
||||
|
||||
/**
|
||||
* Used for SSR. if true, will show slotted label on initial render.
|
||||
*/
|
||||
@property({ type: Boolean, attribute: 'with-label' }) withLabel = false;
|
||||
|
||||
/**
|
||||
* Used for SSR. if true, will show slotted help text on initial render.
|
||||
*/
|
||||
@property({ type: Boolean, attribute: 'with-help-text' }) withHelpText = false;
|
||||
|
||||
//
|
||||
// We need this because if we don't have it, FormValidation yells at us that it's "not focusable".
|
||||
// If we use `this.tabIndex = -1` we can't focus the radio inside.
|
||||
@@ -95,9 +128,11 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (!isServer) {
|
||||
this.addEventListener('keydown', this.handleKeyDown);
|
||||
this.addEventListener('click', this.handleRadioClick);
|
||||
}
|
||||
}
|
||||
|
||||
private handleRadioClick = (e: Event) => {
|
||||
const clickedRadio = (e.target as HTMLElement).closest<WaRadio | WaRadioButton>('wa-radio, wa-radio-button');
|
||||
@@ -190,7 +225,9 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
* the first radio element.
|
||||
*/
|
||||
get validationTarget() {
|
||||
return this.querySelector<WaRadio | WaRadioButton>(':is(wa-radio, wa-radio-button):not([disabled])') || undefined;
|
||||
return isServer
|
||||
? undefined
|
||||
: this.querySelector<WaRadio | WaRadioButton>(':is(wa-radio, wa-radio-button):not([disabled])') || undefined;
|
||||
}
|
||||
|
||||
@watch('value')
|
||||
@@ -269,8 +306,8 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasLabelSlot = this.hasSlotController.test('label');
|
||||
const hasHelpTextSlot = this.hasSlotController.test('help-text');
|
||||
const hasLabelSlot = this.hasUpdated ? this.hasSlotController.test('label') : this.withLabel;
|
||||
const hasHelpTextSlot = this.hasUpdated ? this.hasSlotController.test('help-text') : this.withHelpText;
|
||||
const hasLabel = this.label ? true : !!hasLabelSlot;
|
||||
const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
|
||||
const defaultSlot = html` <slot @slotchange=${this.syncRadioElements}></slot> `;
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaRadio from './radio.js';
|
||||
import type WaRadioGroup from '../radio-group/radio-group.js';
|
||||
|
||||
describe('<wa-radio>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should not get checked when disabled', async () => {
|
||||
const radioGroup = await fixture<WaRadioGroup>(html`
|
||||
<wa-radio-group value="1">
|
||||
@@ -19,4 +23,6 @@ describe('<wa-radio>', () => {
|
||||
expect(radio1.checked).to.be.true;
|
||||
expect(radio2.checked).to.be.false;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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 { html, isServer } from 'lit';
|
||||
import { WaBlurEvent } from '../../events/blur.js';
|
||||
import { WaFocusEvent } from '../../events/focus.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
@@ -66,10 +66,12 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
if (!isServer) {
|
||||
this.addEventListener('click', this.handleClick);
|
||||
this.addEventListener('blur', this.handleBlur);
|
||||
this.addEventListener('focus', this.handleFocus);
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { expect, oneEvent } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import { serialize } from '../../utilities/form.js';
|
||||
import sinon from 'sinon';
|
||||
import type WaRange from './range.js';
|
||||
|
||||
describe('<wa-range>', async () => {
|
||||
describe('<wa-range>', () => {
|
||||
runFormControlBaseTests('wa-range');
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaRange>(html` <wa-range label="Name"></wa-range> `);
|
||||
const el = await fixture<WaRange>(html`<wa-range label="Name"></wa-range>`);
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
@@ -230,6 +236,6 @@ describe('<wa-range>', async () => {
|
||||
expect(input.value).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
await runFormControlBaseTests('wa-range');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -72,11 +72,29 @@ export default class WaRange extends WebAwesomeFormAssociatedElement {
|
||||
/** The name of the range, submitted as a name/value pair with form data. */
|
||||
@property() name = '';
|
||||
|
||||
/** The current value of the range, submitted as a name/value pair with form data. */
|
||||
@property({ attribute: false, type: Number }) value = 0;
|
||||
|
||||
/** The default value of the form control. Primarily used for resetting the form control. */
|
||||
@property({ type: Number, attribute: 'value', reflect: true }) defaultValue = 0;
|
||||
@property({ type: Number, attribute: 'value', reflect: true }) defaultValue = Number(this.getAttribute('value')) || 0;
|
||||
|
||||
private _value: number | null = null;
|
||||
|
||||
/** The current value of the range, submitted as a name/value pair with form data. */
|
||||
get value(): number {
|
||||
if (this.valueHasChanged) {
|
||||
return this._value || 0;
|
||||
}
|
||||
|
||||
return this._value ?? (this.defaultValue || 0);
|
||||
}
|
||||
|
||||
@state()
|
||||
set value(val: number | null) {
|
||||
if (this._value === val) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.valueHasChanged = true;
|
||||
this._value = val;
|
||||
}
|
||||
|
||||
/** The range's label. If you need to display HTML, use the `label` slot instead. */
|
||||
@property() label = '';
|
||||
@@ -112,6 +130,16 @@ export default class WaRange extends WebAwesomeFormAssociatedElement {
|
||||
*/
|
||||
@property({ reflect: true }) form: null | string = null;
|
||||
|
||||
/**
|
||||
* Used for SSR to render slotted labels. If true, will render slotted label content on first paint.
|
||||
*/
|
||||
@property({ attribute: 'with-label', reflect: true, type: Boolean }) withLabel = false;
|
||||
|
||||
/**
|
||||
* Used for SSR to render slotted labels. If true, will render slotted help-text content on first paint.
|
||||
*/
|
||||
@property({ attribute: 'with-help-text', reflect: true, type: Boolean }) withHelpText = false;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.resizeObserver = new ResizeObserver(() => this.syncRange());
|
||||
@@ -246,8 +274,8 @@ export default class WaRange extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasLabelSlot = this.hasSlotController.test('label');
|
||||
const hasHelpTextSlot = this.hasSlotController.test('help-text');
|
||||
const hasLabelSlot = this.hasUpdated ? this.hasSlotController.test('label') : this.withLabel;
|
||||
const hasHelpTextSlot = this.hasUpdated ? this.hasSlotController.test('help-text') : this.withHelpText;
|
||||
const hasLabel = this.label ? true : !!hasLabelSlot;
|
||||
const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type WaRating from './rating.js';
|
||||
|
||||
describe('<wa-rating>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaRating>(html` <wa-rating label="Test"></wa-rating> `);
|
||||
await expect(el).to.be.accessible();
|
||||
@@ -126,4 +130,6 @@ describe('<wa-rating>', () => {
|
||||
expect(el.shadowRoot!.activeElement).to.equal(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -214,7 +214,7 @@ export default class WaRating extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const isRtl = this.matches(':dir(rtl)');
|
||||
const isRtl = this.hasUpdated ? this.matches(':dir(rtl)') : this.dir;
|
||||
const counter = Array.from(Array(this.max).keys());
|
||||
let displayValue = 0;
|
||||
|
||||
@@ -272,7 +272,7 @@ export default class WaRating extends WebAwesomeElement {
|
||||
: `inset(0 0 0 ${(displayValue - index) * 100}%)`
|
||||
})}
|
||||
>
|
||||
${unsafeHTML(this.getSymbol(index + 1))}
|
||||
${this.getSymbol(index + 1)}
|
||||
</div>
|
||||
<div
|
||||
class="rating__partial--filled"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { clientFixture } from '../../internal/test/fixture.js';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { html } from 'lit';
|
||||
import sinon from 'sinon';
|
||||
import type { hydratedFixture } from '../../internal/test/fixture.js';
|
||||
import type WaRelativeTime from './relative-time.js';
|
||||
|
||||
interface WaRelativeTimeTestCase {
|
||||
@@ -17,7 +20,10 @@ const expectFormattedRelativeTimeToBe = async (relativeTime: WaRelativeTime, exp
|
||||
expect(textContent).to.equal(expectedOutput);
|
||||
};
|
||||
|
||||
const createRelativeTimeWithDate = async (relativeDate: Date): Promise<WaRelativeTime> => {
|
||||
const createRelativeTimeWithDate = async (
|
||||
relativeDate: Date,
|
||||
fixture: typeof hydratedFixture | typeof clientFixture
|
||||
): Promise<WaRelativeTime> => {
|
||||
const relativeTime: WaRelativeTime = await fixture<WaRelativeTime>(html`
|
||||
<wa-relative-time lang="en-US"></wa-relative-time>
|
||||
`);
|
||||
@@ -70,8 +76,11 @@ const testCases: WaRelativeTimeTestCase[] = [
|
||||
];
|
||||
|
||||
describe('wa-relative-time', () => {
|
||||
// @TODO: figure out why hydratedFixture behaves differently from clientFixture
|
||||
for (const fixture of [clientFixture]) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const relativeTime = await createRelativeTimeWithDate(currentTime);
|
||||
const relativeTime = await createRelativeTimeWithDate(currentTime, fixture);
|
||||
|
||||
await expect(relativeTime).to.be.accessible();
|
||||
});
|
||||
@@ -89,7 +98,7 @@ describe('wa-relative-time', () => {
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
it(`shows the correct relative time given a Date object: ${testCase.expectedOutput}`, async () => {
|
||||
const relativeTime = await createRelativeTimeWithDate(testCase.date);
|
||||
const relativeTime = await createRelativeTimeWithDate(testCase.date, fixture);
|
||||
|
||||
await expectFormattedRelativeTimeToBe(relativeTime, testCase.expectedOutput);
|
||||
});
|
||||
@@ -124,7 +133,7 @@ describe('wa-relative-time', () => {
|
||||
});
|
||||
|
||||
it('shows the set date with the proper attributes at the time object', async () => {
|
||||
const relativeTime = await createRelativeTimeWithDate(yesterday);
|
||||
const relativeTime = await createRelativeTimeWithDate(yesterday, fixture);
|
||||
|
||||
await relativeTime.updateComplete;
|
||||
const timeElement = extractTimeElement(relativeTime);
|
||||
@@ -161,7 +170,7 @@ describe('wa-relative-time', () => {
|
||||
});
|
||||
|
||||
it('keeps the component in sync if requested', async () => {
|
||||
const relativeTime = await createRelativeTimeWithDate(yesterday);
|
||||
const relativeTime = await createRelativeTimeWithDate(yesterday, fixture);
|
||||
relativeTime.sync = true;
|
||||
|
||||
await expectFormattedRelativeTimeToBe(relativeTime, 'yesterday');
|
||||
@@ -182,4 +191,6 @@ describe('wa-relative-time', () => {
|
||||
await relativeTime.updateComplete;
|
||||
expect(extractTimeElement(relativeTime)).to.be.null;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ const availableUnits: UnitConfig[] = [
|
||||
@customElement('wa-relative-time')
|
||||
export default class WaRelativeTime extends WebAwesomeElement {
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private updateTimeout: number;
|
||||
private updateTimeout: number | ReturnType<typeof setTimeout>;
|
||||
|
||||
@state() private isoTime = '';
|
||||
@state() private relativeTime = '';
|
||||
@@ -96,7 +96,7 @@ export default class WaRelativeTime extends WebAwesomeElement {
|
||||
nextInterval = getTimeUntilNextUnit('day'); // next day
|
||||
}
|
||||
|
||||
this.updateTimeout = window.setTimeout(() => this.requestUpdate(), nextInterval);
|
||||
this.updateTimeout = setTimeout(() => this.requestUpdate(), nextInterval);
|
||||
}
|
||||
|
||||
return html` <time datetime=${this.isoTime} title=${this.relativeTime}>${this.relativeTime}</time> `;
|
||||
|
||||
19
src/components/resize-observer/resize-observer.test.ts
Normal file
19
src/components/resize-observer/resize-observer.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
|
||||
describe('<wa-resize-observer>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should be accessible', async () => {
|
||||
const el = await fixture(
|
||||
html`<wa-resize-observer>
|
||||
<div>Resize this box and watch the console 👉</div>
|
||||
</wa-resize-observer>`
|
||||
);
|
||||
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -34,7 +34,9 @@ export default class WaResizeObserver extends WebAwesomeElement {
|
||||
});
|
||||
|
||||
if (!this.disabled) {
|
||||
this.updateComplete.then(() => {
|
||||
this.startObserver();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { aTimeout, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import { serialize } from '../../utilities/form.js';
|
||||
@@ -7,7 +9,11 @@ import sinon from 'sinon';
|
||||
import type WaOption from '../option/option.js';
|
||||
import type WaSelect from './select.js';
|
||||
|
||||
describe('<wa-select>', async () => {
|
||||
describe('<wa-select>', () => {
|
||||
runFormControlBaseTests('wa-select');
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('accessibility', () => {
|
||||
it('should pass accessibility tests when closed', async () => {
|
||||
const select = await fixture<WaSelect>(html`
|
||||
@@ -30,7 +36,6 @@ describe('<wa-select>', async () => {
|
||||
`);
|
||||
|
||||
await select.show();
|
||||
|
||||
await expect(select).to.be.accessible();
|
||||
});
|
||||
});
|
||||
@@ -609,6 +614,6 @@ describe('<wa-select>', async () => {
|
||||
|
||||
expect(tag.hasAttribute('pill')).to.be.true;
|
||||
});
|
||||
|
||||
await runFormControlBaseTests('wa-select');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { animateWithClass } from '../../internal/animate.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import { html, isServer } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { RequiredValidator } from '../../internal/validators/required-validator.js';
|
||||
import { scrollIntoView } from '../../internal/scroll.js';
|
||||
@@ -89,12 +89,14 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
|
||||
|
||||
static get validators() {
|
||||
return [
|
||||
...super.validators,
|
||||
const validators = isServer
|
||||
? []
|
||||
: [
|
||||
RequiredValidator({
|
||||
validationElement: Object.assign(document.createElement('select'), { required: true })
|
||||
})
|
||||
];
|
||||
return [...super.validators, ...validators];
|
||||
}
|
||||
|
||||
assumeInteractionOn = ['wa-blur', 'wa-input'];
|
||||
@@ -124,14 +126,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
/** The name of the select, submitted as a name/value pair with form data. */
|
||||
@property() name = '';
|
||||
|
||||
/**
|
||||
* The current value of the select, submitted as a name/value pair with form data. When `multiple` is enabled, the
|
||||
* value attribute will be a space-delimited list of values based on the options selected, and the value property will
|
||||
* be an array. **For this reason, values must not contain spaces.**
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
value: string | string[] = '';
|
||||
|
||||
private _defaultValue: string | string[] = '';
|
||||
|
||||
@property({
|
||||
@@ -142,23 +136,55 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
toAttribute: (value: string | string[]) => (Array.isArray(value) ? value.join(' ') : value)
|
||||
}
|
||||
})
|
||||
// @ts-expect-error defaultValue () is a property on the host, but is being used a getter / setter here.
|
||||
set defaultValue(val: string | string[]) {
|
||||
this._defaultValue = this.convertDefaultValue(val);
|
||||
}
|
||||
|
||||
get defaultValue() {
|
||||
if (!this.hasUpdated) {
|
||||
this._defaultValue = this.convertDefaultValue(this._defaultValue);
|
||||
}
|
||||
return this._defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* A converter for defaultValue from array to string if its multiple. Also fixes some hydration issues.
|
||||
*/
|
||||
private convertDefaultValue(val: typeof this.defaultValue) {
|
||||
// For some reason this can go off before we've fully updated. So check the attribute too.
|
||||
const isMultiple = this.multiple || this.hasAttribute('multiple');
|
||||
|
||||
if (!isMultiple && Array.isArray(val)) {
|
||||
val = val.join(' ');
|
||||
}
|
||||
this._defaultValue = val;
|
||||
|
||||
if (!this.hasInteracted) {
|
||||
this.value = this.defaultValue;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
get defaultValue() {
|
||||
return this._defaultValue;
|
||||
private _value: string | string[] | null = this.defaultValue;
|
||||
|
||||
/**
|
||||
* The current value of the select, submitted as a name/value pair with form data. When `multiple` is enabled, the
|
||||
* value attribute will be a space-delimited list of values based on the options selected, and the value property will
|
||||
* be an array. **For this reason, values must not contain spaces.**
|
||||
*/
|
||||
get value() {
|
||||
if (this.valueHasChanged) {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
return this._value ?? this.defaultValue;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
set value(val: string | string[] | null) {
|
||||
if (this._value === val) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.valueHasChanged = true;
|
||||
this._value = val;
|
||||
}
|
||||
|
||||
/** The select's size. */
|
||||
@@ -212,6 +238,16 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
/** The select's help text. If you need to display HTML, use the `help-text` slot instead. */
|
||||
@property({ attribute: 'help-text' }) helpText = '';
|
||||
|
||||
/**
|
||||
* Used for SSR purposes when a label is slotted in. Will show the label on first render.
|
||||
*/
|
||||
@property({ attribute: 'with-label', type: Boolean }) withLabel = false;
|
||||
|
||||
/**
|
||||
* Used for SSR purposes when help-text is slotted in. Will show the help-text on first render.
|
||||
*/
|
||||
@property({ attribute: 'with-help-text', type: Boolean }) withHelpText = false;
|
||||
|
||||
/**
|
||||
* By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
|
||||
* to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
|
||||
@@ -636,7 +672,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
} else {
|
||||
this.value = this.selectedOptions[0]?.value ?? '';
|
||||
this.displayLabel = this.selectedOptions[0]?.getTextLabel() ?? '';
|
||||
this.displayLabel = this.selectedOptions[0]?.getTextLabel?.() ?? '';
|
||||
}
|
||||
|
||||
// Update validity
|
||||
@@ -767,12 +803,13 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasLabelSlot = this.hasSlotController.test('label');
|
||||
const hasHelpTextSlot = this.hasSlotController.test('help-text');
|
||||
const hasLabelSlot = this.hasUpdated ? this.hasSlotController.test('label') : this.withLabel;
|
||||
const hasHelpTextSlot = this.hasUpdated ? this.hasSlotController.test('help-text') : this.withHelpText;
|
||||
const hasLabel = this.label ? true : !!hasLabelSlot;
|
||||
const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
|
||||
const hasClearIcon = this.clearable && !this.disabled && this.value.length > 0;
|
||||
const isPlaceholderVisible = this.placeholder && this.value.length === 0;
|
||||
const hasClearIcon =
|
||||
(this.hasUpdated || isServer) && this.clearable && !this.disabled && this.value && this.value.length > 0;
|
||||
const isPlaceholderVisible = Boolean(this.placeholder && (!this.value || this.value.length === 0));
|
||||
|
||||
return html`
|
||||
<div
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaSkeleton from './skeleton.js';
|
||||
|
||||
describe('<wa-skeleton>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should render default skeleton', async () => {
|
||||
const el = await fixture<WaSkeleton>(html` <wa-skeleton></wa-skeleton> `);
|
||||
|
||||
@@ -29,4 +33,6 @@ describe('<wa-skeleton>', () => {
|
||||
|
||||
expect(base.getAttribute('class')).to.equal(' skeleton skeleton--sheen ');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaSpinner from './spinner.js';
|
||||
|
||||
describe('<wa-spinner>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
describe('when provided no parameters', () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const spinner = await fixture<WaSpinner>(html` <wa-spinner></wa-spinner> `);
|
||||
@@ -21,4 +25,6 @@ describe('<wa-spinner>', () => {
|
||||
expect(getComputedStyle(spinner).flex).to.equal('0 0 auto');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { dragElement } from '../../internal/test.js';
|
||||
import { expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { expect, oneEvent } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { queryByTestId } from '../../internal/test/data-testid-helpers.js';
|
||||
import { resetMouse } from '@web/test-runner-commands';
|
||||
import type WaSplitPanel from './split-panel.js';
|
||||
@@ -36,6 +38,8 @@ describe('<wa-split-panel>', () => {
|
||||
await resetMouse().catch(() => {});
|
||||
});
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should render a component', async () => {
|
||||
const splitPanel = await fixture(html` <wa-split-panel></wa-split-panel> `);
|
||||
|
||||
@@ -290,4 +294,6 @@ describe('<wa-split-panel>', () => {
|
||||
expect(positionInPixelsAfterDrag).to.be.equal(positionInPixels - 40);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -105,7 +105,7 @@ export default class WaSplitPanel extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
private handleDrag(event: PointerEvent) {
|
||||
const isRtl = this.matches(':dir(rtl)');
|
||||
const isRtl = this.hasUpdated ? this.matches(':dir(rtl)') : this.dir === 'rtl';
|
||||
|
||||
if (this.disabled) {
|
||||
return;
|
||||
@@ -226,7 +226,7 @@ export default class WaSplitPanel extends WebAwesomeElement {
|
||||
render() {
|
||||
const gridTemplate = this.vertical ? 'gridTemplateRows' : 'gridTemplateColumns';
|
||||
const gridTemplateAlt = this.vertical ? 'gridTemplateColumns' : 'gridTemplateRows';
|
||||
const isRtl = this.matches(':dir(rtl)');
|
||||
const isRtl = this.hasUpdated ? this.matches(':dir(rtl)') : this.dir === 'rtl';
|
||||
const primary = `
|
||||
clamp(
|
||||
0%,
|
||||
@@ -240,6 +240,12 @@ export default class WaSplitPanel extends WebAwesomeElement {
|
||||
`;
|
||||
const secondary = 'auto';
|
||||
|
||||
// @TODO: Create an actual fix for this. [Konnor]
|
||||
if (!this.style) {
|
||||
// @ts-expect-error `this.style` doesn't exist on the server.
|
||||
this.style = {};
|
||||
}
|
||||
|
||||
if (this.primary === 'end') {
|
||||
if (isRtl && !this.vertical) {
|
||||
this.style[gridTemplate] = `${primary} var(--divider-width) ${secondary}`;
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { aTimeout, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type WaSwitch from './switch.js';
|
||||
|
||||
describe('<wa-switch>', async () => {
|
||||
describe('<wa-switch>', () => {
|
||||
runFormControlBaseTests('wa-switch');
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
const el = await fixture<WaSwitch>(html` <wa-switch>Switch</wa-switch> `);
|
||||
await expect(el).to.be.accessible();
|
||||
@@ -322,6 +328,6 @@ describe('<wa-switch>', async () => {
|
||||
await aTimeout(10);
|
||||
expect(window.scrollY).to.equal(0);
|
||||
});
|
||||
|
||||
await runFormControlBaseTests('wa-switch');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -69,8 +69,29 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement {
|
||||
/** The name of the switch, submitted as a name/value pair with form data. */
|
||||
@property({ reflect: true }) name: string | null = null;
|
||||
|
||||
private _value: string | null = null;
|
||||
|
||||
/** The current value of the switch, submitted as a name/value pair with form data. */
|
||||
@property() value: null | string;
|
||||
get value() {
|
||||
if (this.valueHasChanged) {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
return this._value ?? this.defaultValue;
|
||||
}
|
||||
|
||||
@state()
|
||||
set value(val: string | null) {
|
||||
if (this._value === val) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.valueHasChanged = true;
|
||||
this._value = val;
|
||||
}
|
||||
|
||||
/** The default value of the form control. Primarily used for resetting the form control. */
|
||||
@property({ attribute: 'value', reflect: true }) defaultValue: null | string = this.getAttribute('value') || null;
|
||||
|
||||
/** The switch's size. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
@@ -97,6 +118,11 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement {
|
||||
/** The switch's help text. If you need to display HTML, use the `help-text` slot instead. */
|
||||
@property({ attribute: 'help-text' }) helpText = '';
|
||||
|
||||
/**
|
||||
* Used for SSR. If you slot in help-text, make sure to add `with-help-text` to your component to get it to properly render with SSR.
|
||||
*/
|
||||
@property({ attribute: 'with-help-text', type: Boolean }) withHelpText = false;
|
||||
|
||||
firstUpdated(changedProperties: PropertyValues<typeof this>) {
|
||||
super.firstUpdated(changedProperties);
|
||||
|
||||
@@ -113,6 +139,7 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
private handleClick() {
|
||||
this.hasInteracted = true;
|
||||
this.checked = !this.checked;
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
@@ -138,16 +165,26 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
}
|
||||
|
||||
@watch(['value', 'checked'], { waitUntilFirstUpdate: true })
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has('defaultChecked') || changedProperties.has('value') || changedProperties.has('checked')) {
|
||||
this.handleValueOrCheckedChange();
|
||||
}
|
||||
}
|
||||
|
||||
handleValueOrCheckedChange() {
|
||||
this.handleDefaultCheckedChange();
|
||||
this.value = this.checked ? this.value || 'on' : null;
|
||||
|
||||
if (this.input) {
|
||||
this.input.checked = this.checked; // force a sync update
|
||||
// These @watch() commands seem to override the base element checks for changes, so we need to setValue for the form and and updateValidity()
|
||||
}
|
||||
|
||||
this.setValue(this.value, this.value);
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
@watch('defaultChecked')
|
||||
handleDefaultCheckedChange() {
|
||||
if (!this.hasInteracted && this.checked !== this.defaultChecked) {
|
||||
this.checked = this.defaultChecked;
|
||||
@@ -197,7 +234,7 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasHelpTextSlot = this.hasSlotController.test('help-text');
|
||||
const hasHelpTextSlot = this.hasUpdated ? this.hasSlotController.test('help-text') : this.withHelpText;
|
||||
const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
|
||||
|
||||
return html`
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { aTimeout, elementUpdated, expect, fixture, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, elementUpdated, expect, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { clientFixture, fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import { isElementVisibleFromOverflow } from '../../internal/test/element-visible-overflow.js';
|
||||
import { queryByTestId } from '../../internal/test/data-testid-helpers.js';
|
||||
@@ -72,6 +73,8 @@ const waitForHeaderToBeActive = async (container: HTMLElement, headerTestId: str
|
||||
};
|
||||
|
||||
describe('<wa-tab-group>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('renders', async () => {
|
||||
const tabGroup = await fixture<WaTabGroup>(html`
|
||||
<wa-tab-group>
|
||||
@@ -218,7 +221,9 @@ describe('<wa-tab-group>', () => {
|
||||
});
|
||||
|
||||
it('shows scroll buttons on too many tabs', async () => {
|
||||
const tabGroup = await fixture<WaTabGroup>(html`<wa-tab-group> ${generateTabs(30)} </wa-tab-group>`);
|
||||
// @TODO: Investigate why this fails with hydratedFixture (generateTabs()) [Konnor]
|
||||
// https://github.com/lit/lit/issues/4739
|
||||
const tabGroup = await clientFixture<WaTabGroup>(html`<wa-tab-group> ${generateTabs(30)} </wa-tab-group>`);
|
||||
|
||||
await waitForScrollButtonsToBeRendered(tabGroup);
|
||||
|
||||
@@ -229,7 +234,9 @@ describe('<wa-tab-group>', () => {
|
||||
});
|
||||
|
||||
it('does not show scroll buttons on too many tabs if deactivated', async () => {
|
||||
const tabGroup = await fixture<WaTabGroup>(html`<wa-tab-group> ${generateTabs(30)} </wa-tab-group>`);
|
||||
// @TODO: Investigate why this fails with hydratedFixture (generateTabs()) [Konnor]
|
||||
// https://github.com/lit/lit/issues/4739
|
||||
const tabGroup = await clientFixture<WaTabGroup>(html`<wa-tab-group> ${generateTabs(30)} </wa-tab-group>`);
|
||||
tabGroup.noScrollControls = true;
|
||||
|
||||
await aTimeout(0);
|
||||
@@ -239,7 +246,9 @@ describe('<wa-tab-group>', () => {
|
||||
});
|
||||
|
||||
it('does not show scroll buttons if all tabs fit on the screen', async () => {
|
||||
const tabGroup = await fixture<WaTabGroup>(html`<wa-tab-group> ${generateTabs(2)} </wa-tab-group>`);
|
||||
// @TODO: Investigate why this fails with hydratedFixture (generateTabs()) [Konnor]
|
||||
// https://github.com/lit/lit/issues/4739
|
||||
const tabGroup = await clientFixture<WaTabGroup>(html`<wa-tab-group> ${generateTabs(2)} </wa-tab-group>`);
|
||||
|
||||
await aTimeout(0);
|
||||
|
||||
@@ -301,7 +310,10 @@ describe('<wa-tab-group>', () => {
|
||||
});
|
||||
|
||||
describe('tab selection', () => {
|
||||
const expectCustomTabToBeActiveAfter = async (tabGroup: WaTabGroup, action: () => Promise<void>): Promise<void> => {
|
||||
const expectCustomTabToBeActiveAfter = async (
|
||||
tabGroup: WaTabGroup,
|
||||
action: () => Promise<void>
|
||||
): Promise<void> => {
|
||||
const generalHeader = await waitForHeaderToBeActive(tabGroup, 'general-header');
|
||||
generalHeader.focus();
|
||||
|
||||
@@ -354,7 +366,9 @@ describe('<wa-tab-group>', () => {
|
||||
<wa-tab-group>
|
||||
<wa-tab slot="nav" panel="general" data-testid="general-header">General</wa-tab>
|
||||
<wa-tab slot="nav" panel="custom">Custom</wa-tab>
|
||||
<wa-tab-panel name="general" data-testid="general-tab-content">This is the general tab panel.</wa-tab-panel>
|
||||
<wa-tab-panel name="general" data-testid="general-tab-content"
|
||||
>This is the general tab panel.</wa-tab-panel
|
||||
>
|
||||
<wa-tab-panel name="custom">This is the custom tab panel.</wa-tab-panel>
|
||||
</wa-tab-group>
|
||||
`);
|
||||
@@ -368,7 +382,9 @@ describe('<wa-tab-group>', () => {
|
||||
<wa-tab-group>
|
||||
<wa-tab slot="nav" panel="general" data-testid="general-header">General</wa-tab>
|
||||
<wa-tab slot="nav" panel="disabled" data-testid="disabled-header" disabled>disabled</wa-tab>
|
||||
<wa-tab-panel name="general" data-testid="general-tab-content">This is the general tab panel.</wa-tab-panel>
|
||||
<wa-tab-panel name="general" data-testid="general-tab-content"
|
||||
>This is the general tab panel.</wa-tab-panel
|
||||
>
|
||||
<wa-tab-panel name="disabled">This is the disabled tab panel.</wa-tab-panel>
|
||||
</wa-tab-group>
|
||||
`);
|
||||
@@ -424,7 +440,9 @@ describe('<wa-tab-group>', () => {
|
||||
<wa-tab-group>
|
||||
<wa-tab slot="nav" panel="general" data-testid="general-header">General</wa-tab>
|
||||
<wa-tab slot="nav" panel="disabled" disabled>Disabled</wa-tab>
|
||||
<wa-tab-panel name="general" data-testid="general-tab-content">This is the general tab panel.</wa-tab-panel>
|
||||
<wa-tab-panel name="general" data-testid="general-tab-content"
|
||||
>This is the general tab panel.</wa-tab-panel
|
||||
>
|
||||
<wa-tab-panel name="disabled">This is the custom tab panel.</wa-tab-panel>
|
||||
</wa-tab-group>
|
||||
`);
|
||||
@@ -448,4 +466,6 @@ describe('<wa-tab-group>', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -360,7 +360,7 @@ export default class WaTabGroup extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const isRtl = this.matches(':dir(rtl)');
|
||||
const isRtl = this.hasUpdated ? this.matches(':dir(rtl)') : this.dir === 'rtl';
|
||||
|
||||
return html`
|
||||
<div
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { aTimeout, expect, fixture, html } from '@open-wc/testing';
|
||||
import { aTimeout, expect } from '@open-wc/testing';
|
||||
import { fixtures } from '../../internal/test/fixture.js';
|
||||
import { html } from 'lit';
|
||||
import type WaTabPanel from './tab-panel.js';
|
||||
|
||||
describe('<wa-tab-panel>', () => {
|
||||
for (const fixture of fixtures) {
|
||||
describe(`with "${fixture.type}" rendering`, () => {
|
||||
it('passes accessibility test', async () => {
|
||||
const el = await fixture<WaTabPanel>(html` <wa-tab-panel>Test</wa-tab-panel> `);
|
||||
await expect(el).to.be.accessible();
|
||||
@@ -10,7 +14,7 @@ describe('<wa-tab-panel>', () => {
|
||||
it('default properties', async () => {
|
||||
const el = await fixture<WaTabPanel>(html` <wa-tab-panel>Test</wa-tab-panel> `);
|
||||
|
||||
expect(el.id).to.equal('wa-tab-panel-2');
|
||||
expect(el.id).to.not.be.empty;
|
||||
expect(el.name).to.equal('');
|
||||
expect(el.active).to.equal(false);
|
||||
expect(el.getAttribute('role')).to.equal('tabpanel');
|
||||
@@ -40,4 +44,6 @@ describe('<wa-tab-panel>', () => {
|
||||
|
||||
expect(el.id).to.equal('test-id');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user