diff --git a/.gitignore b/.gitignore index 96208ebcbf..9d2831e121 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ _site .cache .DS_Store -package.json -package-lock.json cdn dist docs/assets/images/sprite.svg diff --git a/cspell.json b/cspell.json index 54468c3e21..70348a834e 100644 --- a/cspell.json +++ b/cspell.json @@ -99,6 +99,7 @@ "monospace", "mousedown", "mousemove", + "mouseout", "mouseup", "multiselectable", "nextjs", diff --git a/custom-elements-manifest.config.js b/custom-elements-manifest.config.js index 795c98513a..afc5a759fa 100644 --- a/custom-elements-manifest.config.js +++ b/custom-elements-manifest.config.js @@ -1,6 +1,7 @@ import * as path from 'path'; import { customElementJetBrainsPlugin } from 'custom-element-jet-brains-integration'; import { customElementVsCodePlugin } from 'custom-element-vs-code-integration'; +import { customElementVuejsPlugin } from 'custom-element-vuejs-integration'; import { parse } from 'comment-parser'; import { pascalCase } from 'pascal-case'; import commandLineArgs from 'command-line-args'; @@ -38,6 +39,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 +68,7 @@ export default { } } }, + // Parse custom jsDoc tags { name: 'shoelace-custom-tags', @@ -140,6 +143,7 @@ export default { } } }, + { name: 'shoelace-react-event-names', analyzePhase({ ts, node, moduleDoc }) { @@ -158,6 +162,7 @@ export default { } } }, + { name: 'shoelace-translate-module-paths', packageLinkPhase({ customElementsManifest }) { @@ -194,6 +199,7 @@ export default { }); } }, + // Generate custom VS Code data customElementVsCodePlugin({ outdir, @@ -205,6 +211,7 @@ export default { } ] }), + customElementJetBrainsPlugin({ outdir: './dist', excludeCss: true, @@ -215,6 +222,12 @@ export default { url: `https://shoelace.style/components/${tag.replace('sl-', '')}` }; } + }), + + customElementVuejsPlugin({ + outdir: './dist/types/vue', + fileName: 'index.d.ts', + componentTypePath: (_, tag) => `../../components/${tag.replace('sl-', '')}/${tag.replace('sl-', '')}.component.js` }) ] }; diff --git a/docs/_includes/component.njk b/docs/_includes/component.njk index df867c6d25..bbce1368c2 100644 --- a/docs/_includes/component.njk +++ b/docs/_includes/component.njk @@ -168,7 +168,7 @@ {% if prop.type.text %} - {{ prop.type.text | markdownInline | safe }} + {{ prop.type.text | trimPipes | markdownInline | safe }} {% else %} - {% endif %} @@ -219,7 +219,7 @@ {{ event.description | markdownInline | safe }} {% if event.type.text %} - {{ event.type.text }} + {{ event.type.text | trimPipes }} {% else %} - {% endif %} @@ -253,7 +253,7 @@ {% if method.parameters.length %} {% 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 %} {% else %} diff --git a/docs/eleventy.config.cjs b/docs/eleventy.config.cjs index 2377943fe2..4ca4cbd485 100644 --- a/docs/eleventy.config.cjs +++ b/docs/eleventy.config.cjs @@ -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 diff --git a/docs/pages/components/button.md b/docs/pages/components/button.md index eb1d5afbac..5eba5b8fc1 100644 --- a/docs/pages/components/button.md +++ b/docs/pages/components/button.md @@ -428,7 +428,7 @@ const App = () => ( ### Loading -Use the `loading` attribute to make a button busy. The width will remain the same as before, preventing adjacent elements from moving around. Clicks will be suppressed until the loading state is removed. +Use the `loading` attribute to make a button busy. The width will remain the same as before, preventing adjacent elements from moving around. ```html:preview Default diff --git a/docs/pages/components/carousel.md b/docs/pages/components/carousel.md index ee4e239fd8..f9e8b75d52 100644 --- a/docs/pages/components/carousel.md +++ b/docs/pages/components/carousel.md @@ -1245,7 +1245,7 @@ const App = () => { {`Thumbnail handleThumbnailClick(i)} + onClick={() => handleThumbnailClick(i)} src={src} /> )} diff --git a/docs/pages/components/menu-item.md b/docs/pages/components/menu-item.md index ef80dfbb0a..b5098843cf 100644 --- a/docs/pages/components/menu-item.md +++ b/docs/pages/components/menu-item.md @@ -60,35 +60,6 @@ const App = () => ( ## Examples -### Disabled - -Add the `disabled` attribute to disable the menu item so it cannot be selected. - -```html:preview - - Option 1 - Option 2 - Option 3 - -``` - -{% raw %} - -```jsx:react -import SlMenu from '@shoelace-style/shoelace/dist/react/menu'; -import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item'; - -const App = () => ( - - Option 1 - Option 2 - Option 3 - -); -``` - -{% 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 + + Option 1 + Option 2 + Option 3 + +``` + +{% raw %} + +```jsx:react +import SlMenu from '@shoelace-style/shoelace/dist/react/menu'; +import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item'; + +const App = () => ( + + Option 1 + Option 2 + Option 3 + +); +``` + +{% 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 + + Option 1 + Option 2 + Option 3 + +``` + +{% raw %} + +```jsx:react +import SlMenu from '@shoelace-style/shoelace/dist/react/menu'; +import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item'; + +const App = () => ( + + Option 1 + Option 2 + Option 3 + +); +``` + +{% 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. diff --git a/docs/pages/components/popup.md b/docs/pages/components/popup.md index 1681209045..8903918f3a 100644 --- a/docs/pages/components/popup.md +++ b/docs/pages/components/popup.md @@ -1530,6 +1530,140 @@ const App = () => { }; ``` +### Hover Bridge + +When a gap exists between the anchor and the popup element, this option will add a "hover bridge" that fills the gap using an invisible element. This makes listening for events such as `mouseover` and `mouseout` more sane because the pointer never technically leaves the element. The hover bridge will only be drawn when the popover is active. For demonstration purposes, the bridge in this example is shown in orange. + +```html:preview + + + + + +``` + +```jsx:react +import { useState } from 'react'; +import SlPopup from '@shoelace-style/shoelace/dist/react/popup'; +import SlRange from '@shoelace-style/shoelace/dist/react/range'; +import SlSwitch from '@shoelace-style/shoelace/dist/react/switch'; + +const css = ` + .popup-hover-bridge span[slot='anchor'] { + display: inline-block; + width: 150px; + height: 150px; + border: dashed 2px var(--sl-color-neutral-600); + margin: 50px; + } + + .popup-hover-bridge .box { + width: 100px; + height: 50px; + background: var(--sl-color-primary-600); + border-radius: var(--sl-border-radius-medium); + } + + .popup-hover-bridge sl-range { + max-width: 260px; + margin-top: .5rem; + } + + .popup-hover-bridge sl-popup::part(hover-bridge) { + background: tomato; + opacity: .5; + } +`; + +const App = () => { + const [hoverBridge, setHoverBridge] = useState(true); + const [distance, setDistance] = useState(10); + const [skidding, setSkidding] = useState(0); + + return ( + <> +