Merge branch 'next' of https://github.com/shoelace-style/shoelace into konnorrogers/account-for-elements-with-tabbable-controls

This commit is contained in:
konnorrogers
2023-12-07 16:47:54 -05:00
11 changed files with 137 additions and 39 deletions

View File

@@ -38,6 +38,7 @@ export default {
customElementsManifest.package = { name, description, version, author, homepage, license };
}
},
// Infer tag names because we no longer use @customElement decorators.
{
name: 'shoelace-infer-tag-names',
@@ -66,6 +67,7 @@ export default {
}
}
},
// Parse custom jsDoc tags
{
name: 'shoelace-custom-tags',
@@ -137,6 +139,7 @@ export default {
}
}
},
{
name: 'shoelace-react-event-names',
analyzePhase({ ts, node, moduleDoc }) {
@@ -155,6 +158,7 @@ export default {
}
}
},
{
name: 'shoelace-translate-module-paths',
packageLinkPhase({ customElementsManifest }) {
@@ -191,6 +195,7 @@ export default {
});
}
},
// Generate custom VS Code data
customElementVsCodePlugin({
outdir,
@@ -202,6 +207,7 @@ export default {
}
]
}),
customElementJetBrainsPlugin({
outdir: './dist',
excludeCss: true,

View File

@@ -160,7 +160,7 @@
</td>
<td>
{% if prop.type.text %}
<code>{{ prop.type.text | markdownInline | safe }}</code>
<code>{{ prop.type.text | trimPipes | markdownInline | safe }}</code>
{% else %}
-
{% endif %}
@@ -211,7 +211,7 @@
<td>{{ event.description | markdownInline | safe }}</td>
<td>
{% if event.type.text %}
<code>{{ event.type.text }}</code>
<code>{{ event.type.text | trimPipes }}</code>
{% else %}
-
{% endif %}
@@ -245,7 +245,7 @@
{% if method.parameters.length %}
<code>
{% for param in method.parameters %}
{{ param.name }}: {{ param.type.text }}{% if not loop.last %},{% endif %}
{{ param.name }}: {{ param.type.text | trimPipes }}{% if not loop.last %},{% endif %}
{% endfor %}
</code>
{% else %}

View File

@@ -96,6 +96,12 @@ module.exports = function (eleventyConfig) {
return shoelaceFlavoredMarkdown.renderInline(content);
});
// Trims whitespace and pipes from the start and end of a string. Useful for CEM types, which can be pipe-delimited.
// With Prettier 3, this means a leading pipe will exist if the line wraps.
eleventyConfig.addFilter('trimPipes', content => {
return typeof content === 'string' ? content.replace(/^(\s|\|)/g, '').replace(/(\s|\|)$/g, '') : content;
});
eleventyConfig.addFilter('classNameToComponentName', className => {
let name = capitalCase(className.replace(/^Sl/, ''));
if (name === 'Qr Code') name = 'QR Code'; // manual override

View File

@@ -60,35 +60,6 @@ const App = () => (
## Examples
### Disabled
Add the `disabled` attribute to disable the menu item so it cannot be selected.
```html:preview
<sl-menu style="max-width: 200px;">
<sl-menu-item>Option 1</sl-menu-item>
<sl-menu-item disabled>Option 2</sl-menu-item>
<sl-menu-item>Option 3</sl-menu-item>
</sl-menu>
```
{% raw %}
```jsx:react
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>
<SlMenuItem>Option 1</SlMenuItem>
<SlMenuItem disabled>Option 2</SlMenuItem>
<SlMenuItem>Option 3</SlMenuItem>
</SlMenu>
);
```
{% endraw %}
### Prefix & Suffix
Add content to the start and end of menu items using the `prefix` and `suffix` slots.
@@ -151,6 +122,64 @@ const App = () => (
{% endraw %}
### Disabled
Add the `disabled` attribute to disable the menu item so it cannot be selected.
```html:preview
<sl-menu style="max-width: 200px;">
<sl-menu-item>Option 1</sl-menu-item>
<sl-menu-item disabled>Option 2</sl-menu-item>
<sl-menu-item>Option 3</sl-menu-item>
</sl-menu>
```
{% raw %}
```jsx:react
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>
<SlMenuItem>Option 1</SlMenuItem>
<SlMenuItem disabled>Option 2</SlMenuItem>
<SlMenuItem>Option 3</SlMenuItem>
</SlMenu>
);
```
{% endraw %}
### Loading
Use the `loading` attribute to indicate that a menu item is busy. Like a disabled menu item, clicks will be suppressed until the loading state is removed.
```html:preview
<sl-menu style="max-width: 200px;">
<sl-menu-item>Option 1</sl-menu-item>
<sl-menu-item loading>Option 2</sl-menu-item>
<sl-menu-item>Option 3</sl-menu-item>
</sl-menu>
```
{% raw %}
```jsx:react
import SlMenu from '@shoelace-style/shoelace/dist/react/menu';
import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item';
const App = () => (
<SlMenu style={{ maxWidth: '200px' }}>
<SlMenuItem>Option 1</SlMenuItem>
<SlMenuItem loading>Option 2</SlMenuItem>
<SlMenuItem>Option 3</SlMenuItem>
</SlMenu>
);
```
{% endraw %}
### Checkbox Menu Items
Set the `type` attribute to `checkbox` to create a menu item that will toggle on and off when selected. You can use the `checked` attribute to set the initial state.

View File

@@ -14,10 +14,12 @@ New versions of Shoelace are released as-needed and generally occur when a criti
## Next
- Added the `loading` attribute and the `spinner` and `spinner__base` part to `<sl-menu-item>` [#1700]
- Fixed focus trapping not scrolling elements into view. [#1750]
- Fixed more performance issues with focus trapping performance. [#1750]
- Added the `hover-bridge` feature to `<sl-popup>` to support better tooltip accessibility [#1734]
- Fixed a bug in `<sl-input>` and `<sl-textarea>` that made it work differently from `<input>` and `<textarea>` when using defaults [#1746]
- Fixed a bug in `<sl-select>` that prevented it from closing when tabbing to another select inside a shadow root [#1763]
- Improved the accessibility of `<sl-tooltip>` so they persist when hovering over the tooltip and dismiss when pressing [[Esc]] [#1734]
## 2.12.0

View File

@@ -47,9 +47,19 @@ files.forEach(async file => {
{ parser: 'babel-ts' }
);
let dTs = await prettier.format(
`
declare const _default: import("lit").CSSResult;
export default _default;
`,
{ parser: 'babel-ts' }
);
const cssFile = path.join(themesDir, path.basename(file));
const jsFile = path.join(themesDir, path.basename(file).replace('.css', '.styles.js'));
const dTsFile = path.join(themesDir, path.basename(file).replace('.css', '.styles.d.ts'));
fs.writeFileSync(cssFile, css, 'utf8');
fs.writeFileSync(jsFile, js, 'utf8');
fs.writeFileSync(dTsFile, dTs, 'utf8');
});

View File

@@ -8,6 +8,7 @@ import { watch } from '../../internal/watch.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import SlIcon from '../icon/icon.component.js';
import SlPopup from '../popup/popup.component.js';
import SlSpinner from '../spinner/spinner.component.js';
import styles from './menu-item.styles.js';
import type { CSSResultGroup } from 'lit';
@@ -19,6 +20,7 @@ import type { CSSResultGroup } from 'lit';
*
* @dependency sl-icon
* @dependency sl-popup
* @dependency sl-spinner
*
* @slot - The menu item's label.
* @slot prefix - Used to prepend an icon or similar element to the menu item.
@@ -30,6 +32,8 @@ import type { CSSResultGroup } from 'lit';
* @csspart prefix - The prefix container.
* @csspart label - The menu item label.
* @csspart suffix - The suffix container.
* @csspart spinner - The spinner that shows when the menu item is in the loading state.
* @csspart spinner__base - The spinner's base part.
* @csspart submenu-icon - The submenu icon, visible only when the menu item has a submenu (not yet implemented).
*
* @cssproperty [--submenu-offset=-2px] - The distance submenus shift to overlap the parent menu.
@@ -38,7 +42,8 @@ export default class SlMenuItem extends ShoelaceElement {
static styles: CSSResultGroup = styles;
static dependencies = {
'sl-icon': SlIcon,
'sl-popup': SlPopup
'sl-popup': SlPopup,
'sl-spinner': SlSpinner
};
private cachedTextLabel: string;
@@ -55,6 +60,9 @@ export default class SlMenuItem extends ShoelaceElement {
/** A unique value to store in the menu item. This can be used as a way to identify menu items when selected. */
@property() value = '';
/** Draws the menu item in a loading state. */
@property({ type: Boolean, reflect: true }) loading = false;
/** Draws the menu item in a disabled state, preventing selection. */
@property({ type: Boolean, reflect: true }) disabled = false;
@@ -158,6 +166,7 @@ export default class SlMenuItem extends ShoelaceElement {
'menu-item--rtl': isRtl,
'menu-item--checked': this.checked,
'menu-item--disabled': this.disabled,
'menu-item--loading': this.loading,
'menu-item--has-submenu': this.isSubmenu(),
'menu-item--submenu-expanded': isSubmenuExpanded
})}
@@ -179,6 +188,7 @@ export default class SlMenuItem extends ShoelaceElement {
</span>
${this.submenuController.renderSubmenu()}
${this.loading ? html` <sl-spinner part="spinner" exportparts="base:spinner__base"></sl-spinner> ` : ''}
</div>
`;
}

View File

@@ -38,6 +38,25 @@ export default css`
cursor: not-allowed;
}
.menu-item.menu-item--loading {
outline: none;
cursor: wait;
}
.menu-item.menu-item--loading *:not(sl-spinner) {
opacity: 0.5;
}
.menu-item--loading sl-spinner {
--indicator-color: currentColor;
--track-width: 1px;
position: absolute;
font-size: 0.75em;
top: calc(50% - 0.5em);
left: 0.65rem;
opacity: 1;
}
.menu-item .menu-item__label {
flex: 1 1 auto;
display: inline-block;

View File

@@ -40,6 +40,7 @@ describe('<sl-menu-item>', () => {
expect(el.value).to.equal('');
expect(el.disabled).to.be.false;
expect(el.loading).to.equal(false);
expect(el.getAttribute('aria-disabled')).to.equal('false');
});
@@ -48,6 +49,13 @@ describe('<sl-menu-item>', () => {
expect(el.getAttribute('aria-disabled')).to.equal('true');
});
describe('when loading', () => {
it('should have a spinner present', async () => {
const el = await fixture<SlMenuItem>(html` <sl-menu-item loading>Menu Item Label</sl-menu-item> `);
expect(el.shadowRoot!.querySelector('sl-spinner')).to.exist;
});
});
it('should return a text label when calling getTextLabel()', async () => {
const el = await fixture<SlMenuItem>(html` <sl-menu-item>Test</sl-menu-item> `);
expect(el.getTextLabel()).to.equal('Test');

View File

@@ -216,15 +216,22 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
}
private addOpenListeners() {
document.addEventListener('focusin', this.handleDocumentFocusIn);
document.addEventListener('keydown', this.handleDocumentKeyDown);
document.addEventListener('mousedown', this.handleDocumentMouseDown);
//
// Listen on the root node instead of the document in case the elements are inside a shadow root
//
// https://github.com/shoelace-style/shoelace/issues/1763
//
const root = this.getRootNode();
root.addEventListener('focusin', this.handleDocumentFocusIn);
root.addEventListener('keydown', this.handleDocumentKeyDown);
root.addEventListener('mousedown', this.handleDocumentMouseDown);
}
private removeOpenListeners() {
document.removeEventListener('focusin', this.handleDocumentFocusIn);
document.removeEventListener('keydown', this.handleDocumentKeyDown);
document.removeEventListener('mousedown', this.handleDocumentMouseDown);
const root = this.getRootNode();
root.removeEventListener('focusin', this.handleDocumentFocusIn);
root.removeEventListener('keydown', this.handleDocumentKeyDown);
root.removeEventListener('mousedown', this.handleDocumentMouseDown);
}
private handleFocus() {

View File

@@ -156,6 +156,7 @@ describe('<sl-select>', () => {
await el.updateComplete;
await sendKeys({ press: 'ArrowDown' }); // move selection to the third option
await el.updateComplete;
el.focus(); // For some reason, the browser loses focus before we press enter. Refocus the select.
await sendKeys({ press: 'Enter' }); // commit the selection
await el.updateComplete;