From aeef986cf571641836e562ac4b91407806223ce0 Mon Sep 17 00:00:00 2001 From: Burton Smith <31320098+break-stuff@users.noreply.github.com> Date: Mon, 14 Aug 2023 09:34:34 -0400 Subject: [PATCH 01/30] JetBrains IDE Integration (#1512) * upgrade vs code integration package * add references * add web-types plugin * update reference * run prettier * update documentation * run prettier * remove test script --- .gitignore | 1 + custom-elements-manifest.config.js | 10 +++++ docs/pages/getting-started/usage.md | 6 +++ package-lock.json | 19 ++++++++ package.json | 3 +- scripts/build.js | 5 +-- scripts/make-web-types.js | 68 ----------------------------- 7 files changed, 39 insertions(+), 73 deletions(-) delete mode 100644 scripts/make-web-types.js diff --git a/.gitignore b/.gitignore index 24a14172f..0a2fc3d4d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ docs/assets/images/sprite.svg node_modules src/react cdn +web-types.json \ No newline at end of file diff --git a/custom-elements-manifest.config.js b/custom-elements-manifest.config.js index 2e3277912..e5912cd06 100644 --- a/custom-elements-manifest.config.js +++ b/custom-elements-manifest.config.js @@ -1,4 +1,5 @@ import * as path from 'path'; +import { customElementJetBrainsPlugin } from 'custom-element-jet-brains-integration'; import { customElementVsCodePlugin } from 'custom-element-vs-code-integration'; import { parse } from 'comment-parser'; import { pascalCase } from 'pascal-case'; @@ -200,6 +201,15 @@ export default { url: `https://shoelace.style/components/${tag.replace('sl-', '')}` } ] + }), + customElementJetBrainsPlugin({ + excludeCss: true, + referencesTemplate: (_, tag) => { + return { + name: 'Documentation', + url: `https://shoelace.style/components/${tag.replace('sl-', '')}` + }; + } }) ] }; diff --git a/docs/pages/getting-started/usage.md b/docs/pages/getting-started/usage.md index f8195e929..4032588e0 100644 --- a/docs/pages/getting-started/usage.md +++ b/docs/pages/getting-started/usage.md @@ -210,6 +210,12 @@ Shoelace ships with a file called `vscode.html-custom-data.json` that can be use If `settings.json` already exists, simply add the above line to the root of the object. Note that you may need to restart VS Code for the changes to take affect. +## JetBrains IDEs + +If you are using a [JetBrains IDE](https://www.jetbrains.com/) and you are installing Shoelace from NPM, the editor will automatically detect the `web-types.json` file from the package and you should immediately see component information in your editor. + +If you are installing from the CDN, you can [download a local copy](https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace/cdn/web-types.json) and add it to the root of your project. + ### Other Editors Most popular editors support custom code completion with a bit of configuration. Please [submit a feature request](https://github.com/shoelace-style/shoelace/issues/new/choose) for your editor of choice. PRs are also welcome! diff --git a/package-lock.json b/package-lock.json index 53853f7dd..7351e3c5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "command-line-args": "^5.2.1", "comment-parser": "^1.3.1", "cspell": "^6.18.1", + "custom-element-jet-brains-integration": "^1.1.0", "custom-element-vs-code-integration": "^1.1.0", "del": "^7.0.0", "download": "^8.0.0", @@ -5687,6 +5688,15 @@ "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", "dev": true }, + "node_modules/custom-element-jet-brains-integration": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/custom-element-jet-brains-integration/-/custom-element-jet-brains-integration-1.1.0.tgz", + "integrity": "sha512-wesa4OEvRQdxNzynk5ugU7ZRy0Ghkoaa6NmRGTqOASIng1hVaE3EKKO3rK11b4Y/pR3HUPIPKs1mRSnRCjHBfg==", + "dev": true, + "dependencies": { + "prettier": "^2.8.0" + } + }, "node_modules/custom-element-vs-code-integration": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/custom-element-vs-code-integration/-/custom-element-vs-code-integration-1.1.0.tgz", @@ -21507,6 +21517,15 @@ "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", "dev": true }, + "custom-element-jet-brains-integration": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/custom-element-jet-brains-integration/-/custom-element-jet-brains-integration-1.1.0.tgz", + "integrity": "sha512-wesa4OEvRQdxNzynk5ugU7ZRy0Ghkoaa6NmRGTqOASIng1hVaE3EKKO3rK11b4Y/pR3HUPIPKs1mRSnRCjHBfg==", + "dev": true, + "requires": { + "prettier": "^2.8.0" + } + }, "custom-element-vs-code-integration": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/custom-element-vs-code-integration/-/custom-element-vs-code-integration-1.1.0.tgz", diff --git a/package.json b/package.json index b1781a642..00a17ff17 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "author": "Cory LaViska", "license": "MIT", "customElements": "dist/custom-elements.json", - "web-types": "dist/web-types.json", + "web-types": "./web-types.json", "type": "module", "types": "dist/shoelace.d.ts", "jsdelivr": "./cdn/shoelace-autoloader.js", @@ -95,6 +95,7 @@ "command-line-args": "^5.2.1", "comment-parser": "^1.3.1", "cspell": "^6.18.1", + "custom-element-jet-brains-integration": "^1.1.0", "custom-element-vs-code-integration": "^1.1.0", "del": "^7.0.0", "download": "^8.0.0", diff --git a/scripts/build.js b/scripts/build.js index 08b686e92..d27793b59 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -188,10 +188,6 @@ await nextTask('Wrapping components for React', () => { return execPromise(`node scripts/make-react.js --outdir "${outdir}"`, { stdio: 'inherit' }); }); -await nextTask('Generating Web Types', () => { - return execPromise(`node scripts/make-web-types.js --outdir "${outdir}"`, { stdio: 'inherit' }); -}); - await nextTask('Generating themes', () => { return execPromise(`node scripts/make-themes.js --outdir "${outdir}"`, { stdio: 'inherit' }); }); @@ -207,6 +203,7 @@ await nextTask('Running the TypeScript compiler', () => { // Copy the above steps to the CDN directory directly so we don't need to twice the work for nothing. await nextTask(`Copying Web Types, Themes, Icons, and TS Types to "${cdndir}"`, async () => { await deleteAsync(cdndir); + await copy('./web-types.json', `${outdir}/web-types.json`); await copy(outdir, cdndir); }); diff --git a/scripts/make-web-types.js b/scripts/make-web-types.js deleted file mode 100644 index 156ff2ba2..000000000 --- a/scripts/make-web-types.js +++ /dev/null @@ -1,68 +0,0 @@ -// -// This script generates a web-types.json file from custom-elements.json for use with WebStorm/PHPStorm -// -// Docs: https://github.com/JetBrains/web-types -// -import commandLineArgs from 'command-line-args'; -import jsonata from 'jsonata'; -import fs from 'fs'; -import path from 'path'; - -const { outdir } = commandLineArgs({ name: 'outdir', type: String }); -const metadata = JSON.parse(fs.readFileSync(path.join(outdir, 'custom-elements.json'), 'utf8')); - -const jsonataExprString = `{ - "$schema": "http://json.schemastore.org/web-types", - "name": package.name, - "version": package.version, - "description-markup": "markdown", - "framework-config": { - "enable-when": { - "node-packages": [ - package.name - ] - } - }, - "contributions": { - "html": { - "elements": [ - modules.declarations.{ - "name": tagName, - "description": description, - "doc-url": $join(["https://shoelace.style/components/", $substringAfter(tagName, 'sl-')]), - "js": { - "properties": [ - members.{ - "name": name, - "description": description, - "value": { - "type": type.text - } - } - ], - "events": [ - events.{ - "name": name, - "description": description - } - ] - }, - "attributes": [ - attributes.{ - "name": name, - "description": description, - "value": { - "type": type.text - } - } - ] - } - ] - } - } -}`; - -const expression = jsonata(jsonataExprString); -const result = await expression.evaluate(metadata); - -fs.writeFileSync(path.join(outdir, 'web-types.json'), JSON.stringify(result, null, 2), 'utf8'); From b09a48bec4ad8e5f068a1c0d151db77c50c10249 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Mon, 14 Aug 2023 10:02:23 -0400 Subject: [PATCH 02/30] fix arg name --- src/components/details/details.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/details/details.component.ts b/src/components/details/details.component.ts index 8d68ef716..b7eab4460 100644 --- a/src/components/details/details.component.ts +++ b/src/components/details/details.component.ts @@ -90,15 +90,15 @@ export default class SlDetails extends ShoelaceElement { this.detailsObserver.disconnect(); } - private handleSummaryClick(ev: MouseEvent) { - ev.preventDefault(); + private handleSummaryClick(event: MouseEvent) { + event.preventDefault(); + if (!this.disabled) { if (this.open) { this.hide(); } else { this.show(); } - this.header.focus(); } } From e73e32fb7145987b0c9d78684a664c0f3cd5a42e Mon Sep 17 00:00:00 2001 From: Alexander Krolick <104371843+ak-beam@users.noreply.github.com> Date: Mon, 14 Aug 2023 07:21:52 -0700 Subject: [PATCH 03/30] Add docs on setting multiple values in select (#1508) --- docs/pages/components/select.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/pages/components/select.md b/docs/pages/components/select.md index 90d537b13..e830a7471 100644 --- a/docs/pages/components/select.md +++ b/docs/pages/components/select.md @@ -250,7 +250,11 @@ Note that multi-select options may wrap, causing the control to expand verticall ### Setting Initial Values -Use the `value` attribute to set the initial selection. When using `multiple`, use space-delimited values to select more than one option. +Use the `value` attribute to set the initial selection. + +When using `multiple`, the `value` _attribute_ uses space-delimited values to select more than one option. +Note that `sl-option` values cannot contain spaces for this reason. If accessing or setting the `value` _property_ +through Javascript, `value` is an array. ```html:preview @@ -261,6 +265,11 @@ Use the `value` attribute to set the initial selection. When using `multiple`, u ``` +```js +const select = document.querySelector('sl-select[multiple]'); +select.value = ['option-1', 'option-2'] +``` + ```jsx:react import SlDivider from '@shoelace-style/shoelace/dist/react/divider'; import SlOption from '@shoelace-style/shoelace/dist/react/option'; From c743561c25187cd5607de36ebeafbdf267f1faef Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Mon, 14 Aug 2023 10:23:59 -0400 Subject: [PATCH 04/30] update docs --- docs/pages/components/select.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/pages/components/select.md b/docs/pages/components/select.md index e830a7471..6fd05cbe1 100644 --- a/docs/pages/components/select.md +++ b/docs/pages/components/select.md @@ -252,9 +252,7 @@ Note that multi-select options may wrap, causing the control to expand verticall Use the `value` attribute to set the initial selection. -When using `multiple`, the `value` _attribute_ uses space-delimited values to select more than one option. -Note that `sl-option` values cannot contain spaces for this reason. If accessing or setting the `value` _property_ -through Javascript, `value` is an array. +When using `multiple`, the `value` _attribute_ uses space-delimited values to select more than one option. Because of this, `` values cannot contain spaces. If you're accessing the `value` _property_ through Javascript, it will be an array. ```html:preview @@ -265,11 +263,6 @@ through Javascript, `value` is an array. ``` -```js -const select = document.querySelector('sl-select[multiple]'); -select.value = ['option-1', 'option-2'] -``` - ```jsx:react import SlDivider from '@shoelace-style/shoelace/dist/react/divider'; import SlOption from '@shoelace-style/shoelace/dist/react/option'; From e298f7e5f4478b6f47ad5d65a5184605515d1600 Mon Sep 17 00:00:00 2001 From: Konnor Rogers Date: Mon, 14 Aug 2023 11:23:00 -0400 Subject: [PATCH 05/30] fix broken tests for shoelace-element (#1516) * add stub code prior to test * fix broken test * prettier * prettier * prettier --- src/internal/shoelace-element.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internal/shoelace-element.test.ts b/src/internal/shoelace-element.test.ts index 21fbf6cd3..d41f333c5 100644 --- a/src/internal/shoelace-element.test.ts +++ b/src/internal/shoelace-element.test.ts @@ -133,10 +133,10 @@ before(async () => { relevantMetadata.forEach(({ tagName, path }) => { it(`Should not register any components: ${tagName}`, async () => { - // Check if importing the files automatically registers any components await import('../../dist/' + path); - const registeredTags = tagNames.filter(tag => Boolean(window.customElements.get(tag))); + // Need to make sure we remove the current tag from the tagNames and *then* see whats been registered. + const registeredTags = tagNames.filter(tag => tag !== tagName && Boolean(window.customElements.get(tag))); const errorMessage = `Expected ${path} to not register any tags, but it registered the following tags: ` + From c380368b6187fd8e1c9954a8004dcb555e2b56bc Mon Sep 17 00:00:00 2001 From: Peter Siska <63866+peschee@users.noreply.github.com> Date: Tue, 15 Aug 2023 16:46:51 +0200 Subject: [PATCH 06/30] Fix NPMDIR config (#1518) * Fix NPMDIR config * Add missing semi --- docs/pages/getting-started/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/getting-started/installation.md b/docs/pages/getting-started/installation.md index 8cae5c29e..96bf2d0cf 100644 --- a/docs/pages/getting-started/installation.md +++ b/docs/pages/getting-started/installation.md @@ -187,7 +187,7 @@ import '@shoelace-style/shoelace/%NPMDIR%/components/rating/rating.js'; import { setBasePath } from '@shoelace-style/shoelace/%NPMDIR%/utilities/base-path.js'; // Set the base path to the folder you copied Shoelace's assets to -setBasePath('/path/to/shoelace/%NPMDIR% +setBasePath('/path/to/shoelace/%NPMDIR%'); // , , , and are ready to use! ``` From 9cb5ba7ac1c70c4795eb2732230ffba33a525db4 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Wed, 16 Aug 2023 14:51:46 -0400 Subject: [PATCH 07/30] Radio button fix (#1524) * fix formatting * fix radio button spacing; fixes #1523 --- docs/pages/resources/changelog.md | 4 ++++ package.json | 16 +++----------- .../button-group/button-group.component.ts | 2 +- .../radio-button/radio-button.test.ts | 22 +++++++++++++++++++ .../radio-group/radio-group.component.ts | 8 ++----- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/docs/pages/resources/changelog.md b/docs/pages/resources/changelog.md index e6b8d8c75..4877bb3a5 100644 --- a/docs/pages/resources/changelog.md +++ b/docs/pages/resources/changelog.md @@ -12,6 +12,10 @@ Components with the Experimental bad New versions of Shoelace are released as-needed and generally occur when a critical mass of changes have accumulated. At any time, you can see what's coming in the next release by visiting [next.shoelace.style](https://next.shoelace.style). +## Next + +- Fixed a regression that caused `` to render incorrectly with gaps [#1523] + ## 2.7.0 - Added the experimental `` component [#1473] diff --git a/package.json b/package.json index 00a17ff17..55554f475 100644 --- a/package.json +++ b/package.json @@ -25,15 +25,8 @@ "./dist/react/*": "./dist/react/*", "./dist/translations/*": "./dist/translations/*" }, - "files": [ - "dist", - "cdn" - ], - "keywords": [ - "web components", - "custom elements", - "components" - ], + "files": ["dist", "cdn"], + "keywords": ["web components", "custom elements", "components"], "repository": { "type": "git", "url": "git+https://github.com/shoelace-style/shoelace.git" @@ -140,9 +133,6 @@ "user-agent-data-types": "^0.3.0" }, "lint-staged": { - "*.{ts,js}": [ - "eslint --max-warnings 0 --cache --fix", - "prettier --write" - ] + "*.{ts,js}": ["eslint --max-warnings 0 --cache --fix", "prettier --write"] } } diff --git a/src/components/button-group/button-group.component.ts b/src/components/button-group/button-group.component.ts index 69cd90d9f..84328a78f 100644 --- a/src/components/button-group/button-group.component.ts +++ b/src/components/button-group/button-group.component.ts @@ -54,7 +54,7 @@ export default class SlButtonGroup extends ShoelaceElement { const index = slottedElements.indexOf(el); const button = findButton(el); - if (button !== null) { + if (button) { button.classList.add('sl-button-group__button'); button.classList.toggle('sl-button-group__button--first', index === 0); button.classList.toggle('sl-button-group__button--inner', index > 0 && index < slottedElements.length - 1); diff --git a/src/components/radio-button/radio-button.test.ts b/src/components/radio-button/radio-button.test.ts index 1a86178c7..cea587628 100644 --- a/src/components/radio-button/radio-button.test.ts +++ b/src/components/radio-button/radio-button.test.ts @@ -20,4 +20,26 @@ describe('', () => { expect(radio1.checked).to.be.true; expect(radio2.checked).to.be.false; }); + + it('should receive positional classes from ', async () => { + const radioGroup = await fixture(html` + + + + + + `); + const radio1 = radioGroup.querySelector('#radio-1')!; + const radio2 = radioGroup.querySelector('#radio-2')!; + const radio3 = radioGroup.querySelector('#radio-3')!; + + await Promise.all([radioGroup.updateComplete, radio1.updateComplete, radio2.updateComplete, radio3.updateComplete]); + + expect(radio1.classList.contains('sl-button-group__button')).to.be.true; + expect(radio1.classList.contains('sl-button-group__button--first')).to.be.true; + expect(radio2.classList.contains('sl-button-group__button')).to.be.true; + expect(radio2.classList.contains('sl-button-group__button--inner')).to.be.true; + expect(radio3.classList.contains('sl-button-group__button')).to.be.true; + expect(radio3.classList.contains('sl-button-group__button--last')).to.be.true; + }); }); diff --git a/src/components/radio-group/radio-group.component.ts b/src/components/radio-group/radio-group.component.ts index 96fea508e..11210183e 100644 --- a/src/components/radio-group/radio-group.component.ts +++ b/src/components/radio-group/radio-group.component.ts @@ -327,11 +327,8 @@ export default class SlRadioGroup extends ShoelaceElement implements ShoelaceFor const hasHelpTextSlot = this.hasSlotController.test('help-text'); const hasLabel = this.label ? true : !!hasLabelSlot; const hasHelpText = this.helpText ? true : !!hasHelpTextSlot; - const defaultSlot = html` - - - + `; return html` @@ -378,7 +375,7 @@ export default class SlRadioGroup extends ShoelaceElement implements ShoelaceFor ${this.hasButtonGroup ? html` - + ${defaultSlot} ` @@ -395,6 +392,5 @@ export default class SlRadioGroup extends ShoelaceElement implements ShoelaceFor `; - /* eslint-enable lit-a11y/click-events-have-key-events */ } } From 7ee31be6d6a108858937a31b24cb5c16e5da69f8 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Wed, 16 Aug 2023 14:57:03 -0400 Subject: [PATCH 08/30] ignore package.json --- .prettierignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierignore b/.prettierignore index 92476da66..e3f4ba0ae 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,6 +7,7 @@ docs/search.json src/components/icon/icons src/react/index.ts node_modules +package.json package-lock.json tsconfig.json cdn From d8de7bcc51d4ce325b60c76fc33d6ea3028a04c2 Mon Sep 17 00:00:00 2001 From: Thomas Allmer Date: Wed, 16 Aug 2023 20:59:21 +0200 Subject: [PATCH 09/30] fix(docs): Inline Form Validation Docs throw error on top level await (#1522) --- docs/pages/getting-started/form-controls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/getting-started/form-controls.md b/docs/pages/getting-started/form-controls.md index 1fccb422f..8cc04584e 100644 --- a/docs/pages/getting-started/form-controls.md +++ b/docs/pages/getting-started/form-controls.md @@ -462,7 +462,7 @@ To disable the browser's error messages, you need to cancel the `sl-invalid` eve Reset - + +``` diff --git a/src/components/select/select.component.ts b/src/components/select/select.component.ts index 54d809854..779b5dc43 100644 --- a/src/components/select/select.component.ts +++ b/src/components/select/select.component.ts @@ -4,7 +4,7 @@ import { defaultValue } from '../../internal/default-value.js'; import { FormControlController } from '../../internal/form.js'; import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js'; import { HasSlotController } from '../../internal/slot.js'; -import { html } from 'lit'; +import { html, TemplateResult } from 'lit'; import { LocalizeController } from '../../utilities/localize.js'; import { property, query, state } from 'lit/decorators.js'; import { scrollIntoView } from '../../internal/scroll.js'; @@ -19,6 +19,7 @@ import type { CSSResultGroup } from 'lit'; import type { ShoelaceFormControl } from '../../internal/shoelace-element.js'; import type SlOption from '../option/option.component.js'; import type SlRemoveEvent from '../../events/sl-remove.js'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; /** * @summary Selects allow you to choose items from a menu of predefined options. @@ -172,6 +173,31 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon /** The select's required attribute. */ @property({ type: Boolean, reflect: true }) required = false; + /** + * A function that customizes the tags to be rendered when multiple=true. The first argument is the option, the second + * is the current tag's index. The function should return either a Lit TemplateResult or a string containing trusted HTML of the symbol to render at + * the specified value. + */ + @property() getTag: (option: SlOption, index: number) => TemplateResult | string = (option, index) => { + return html` + this.handleTagRemove(event, option)} + > + ${option.getTextLabel()} + + `; + }; + /** Gets the validity state object */ get validity() { return this.valueInput.validity; @@ -547,6 +573,21 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon this.formControlController.updateValidity(); }); } + protected get tags() { + return this.selectedOptions.map((option, index) => { + if (index < this.maxOptionsVisible || this.maxOptionsVisible <= 0) { + const tag = this.getTag(option, index); + // Wrap so we can handle the remove + return html`
this.handleTagRemove(e, option)}> + ${typeof tag === 'string' ? unsafeHTML(tag) : tag} +
`; + } else if (index === this.maxOptionsVisible) { + // Hit tag limit + return html`+${this.selectedOptions.length - index}`; + } + return html``; + }); + } private handleInvalid(event: Event) { this.formControlController.setValidity(false); @@ -755,37 +796,7 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon @blur=${this.handleBlur} /> - ${this.multiple - ? html` -
- ${this.selectedOptions.map((option, index) => { - if (index < this.maxOptionsVisible || this.maxOptionsVisible <= 0) { - return html` - this.handleTagRemove(event, option)} - > - ${option.getTextLabel()} - - `; - } else if (index === this.maxOptionsVisible) { - return html` +${this.selectedOptions.length - index} `; - } else { - return null; - } - })} -
- ` - : ''} + ${this.multiple ? html`
${this.tags}
` : ''} Date: Thu, 17 Aug 2023 13:18:51 -0600 Subject: [PATCH 11/30] Fix lint warnings --- src/components/select/select.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/select/select.component.ts b/src/components/select/select.component.ts index 779b5dc43..6aa4d36f8 100644 --- a/src/components/select/select.component.ts +++ b/src/components/select/select.component.ts @@ -4,10 +4,11 @@ import { defaultValue } from '../../internal/default-value.js'; import { FormControlController } from '../../internal/form.js'; import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js'; import { HasSlotController } from '../../internal/slot.js'; -import { html, TemplateResult } from 'lit'; +import { html } from 'lit'; import { LocalizeController } from '../../utilities/localize.js'; import { property, query, state } from 'lit/decorators.js'; import { scrollIntoView } from '../../internal/scroll.js'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { waitForEvent } from '../../internal/event.js'; import { watch } from '../../internal/watch.js'; import ShoelaceElement from '../../internal/shoelace-element.js'; @@ -15,11 +16,10 @@ import SlIcon from '../icon/icon.component.js'; import SlPopup from '../popup/popup.component.js'; import SlTag from '../tag/tag.component.js'; import styles from './select.styles.js'; -import type { CSSResultGroup } from 'lit'; +import type { CSSResultGroup, TemplateResult } from 'lit'; import type { ShoelaceFormControl } from '../../internal/shoelace-element.js'; import type SlOption from '../option/option.component.js'; import type SlRemoveEvent from '../../events/sl-remove.js'; -import { unsafeHTML } from 'lit/directives/unsafe-html.js'; /** * @summary Selects allow you to choose items from a menu of predefined options. @@ -178,7 +178,7 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon * is the current tag's index. The function should return either a Lit TemplateResult or a string containing trusted HTML of the symbol to render at * the specified value. */ - @property() getTag: (option: SlOption, index: number) => TemplateResult | string = (option, index) => { + @property() getTag: (option: SlOption, index: number) => TemplateResult | string = option => { return html` Date: Fri, 18 Aug 2023 15:55:29 +0200 Subject: [PATCH 12/30] SlTree: separate expand/collapse and selection behaviour in 'single' mode (#1521) * Never select tree items when clicking the chevron This changes the behaviour of sl-tree so that clicking on the expand/collapse icon will not select/deselect the item, only toggle it's expanded state. * Refactor: inline SlTree.syncTreeItems This was only called from 2 places, and they each had different behaviour anyways. * SlTree: separate expand/collapse from selection This makes 'multi' and 'single' mode consistent with each other, and with native file managers. --- src/components/tree/tree.component.ts | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/components/tree/tree.component.ts b/src/components/tree/tree.component.ts index 65a41f38a..9f083a3a1 100644 --- a/src/components/tree/tree.component.ts +++ b/src/components/tree/tree.component.ts @@ -168,20 +168,6 @@ export default class SlTree extends ShoelaceElement { } }; - private syncTreeItems(selectedItem: SlTreeItem) { - const items = this.getAllTreeItems(); - - if (this.selection === 'multiple') { - syncCheckboxes(selectedItem); - } else { - for (const item of items) { - if (item !== selectedItem) { - item.selected = false; - } - } - } - } - private selectItem(selectedItem: SlTreeItem) { const previousSelection = [...this.selectedItems]; @@ -190,12 +176,12 @@ export default class SlTree extends ShoelaceElement { if (selectedItem.lazy) { selectedItem.expanded = true; } - this.syncTreeItems(selectedItem); + syncCheckboxes(selectedItem); } else if (this.selection === 'single' || selectedItem.isLeaf) { - selectedItem.expanded = !selectedItem.expanded; - selectedItem.selected = true; - - this.syncTreeItems(selectedItem); + const items = this.getAllTreeItems(); + for (const item of items) { + item.selected = (item === selectedItem); + } } else if (this.selection === 'leaf') { selectedItem.expanded = !selectedItem.expanded; } @@ -311,7 +297,7 @@ export default class SlTree extends ShoelaceElement { return; } - if (this.selection === 'multiple' && isExpandButton) { + if (isExpandButton) { treeItem.expanded = !treeItem.expanded; } else { this.selectItem(treeItem); From c8919ad11fea879e22b4e5fbbe978ebd1a0bd4a2 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Fri, 18 Aug 2023 09:55:57 -0400 Subject: [PATCH 13/30] prettier --- src/components/tree/tree.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/tree/tree.component.ts b/src/components/tree/tree.component.ts index 9f083a3a1..81a0ba6d9 100644 --- a/src/components/tree/tree.component.ts +++ b/src/components/tree/tree.component.ts @@ -180,7 +180,7 @@ export default class SlTree extends ShoelaceElement { } else if (this.selection === 'single' || selectedItem.isLeaf) { const items = this.getAllTreeItems(); for (const item of items) { - item.selected = (item === selectedItem); + item.selected = item === selectedItem; } } else if (this.selection === 'leaf') { selectedItem.expanded = !selectedItem.expanded; From 621aa4362bd1bf995e84d069e1b59b6dff1b2745 Mon Sep 17 00:00:00 2001 From: nathan Date: Fri, 18 Aug 2023 09:17:02 -0600 Subject: [PATCH 14/30] Add HTMLElement to the getTag() return type --- docs/pages/components/select.md | 3 ++- src/components/select/select.component.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/pages/components/select.md b/docs/pages/components/select.md index 5f7791eb3..872563817 100644 --- a/docs/pages/components/select.md +++ b/docs/pages/components/select.md @@ -458,7 +458,8 @@ const App = () => ( ### Custom Tags When multiple options can be selected, you can provide custom tags by passing a function to the `getTag` property. -Your `getTag(option, index)` function can return a string or a Lit Template +Your `getTag(option, index)` function can return a string, a Lit Template, +or an HTMLElement. ```html:preview diff --git a/src/components/select/select.component.ts b/src/components/select/select.component.ts index 6aa4d36f8..9bc5a0cd3 100644 --- a/src/components/select/select.component.ts +++ b/src/components/select/select.component.ts @@ -178,7 +178,7 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon * is the current tag's index. The function should return either a Lit TemplateResult or a string containing trusted HTML of the symbol to render at * the specified value. */ - @property() getTag: (option: SlOption, index: number) => TemplateResult | string = option => { + @property() getTag: (option: SlOption, index: number) => TemplateResult | string | HTMLElement = option => { return html` Date: Fri, 18 Aug 2023 11:20:14 -0400 Subject: [PATCH 15/30] fix tree tests; #1521 --- docs/pages/resources/changelog.md | 1 + src/components/tree/tree.test.ts | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/pages/resources/changelog.md b/docs/pages/resources/changelog.md index 4877bb3a5..57ed9edfa 100644 --- a/docs/pages/resources/changelog.md +++ b/docs/pages/resources/changelog.md @@ -15,6 +15,7 @@ New versions of Shoelace are released as-needed and generally occur when a criti ## Next - Fixed a regression that caused `` to render incorrectly with gaps [#1523] +- Improved expand/collapse behavior of `` to work more like users expect [#1521] ## 2.7.0 diff --git a/src/components/tree/tree.test.ts b/src/components/tree/tree.test.ts index 6c52e2311..aec85286c 100644 --- a/src/components/tree/tree.test.ts +++ b/src/components/tree/tree.test.ts @@ -275,7 +275,6 @@ describe('', () => { // Assert expect(el.selectedItems.length).to.eq(1); expect(el.children[2]).to.have.attribute('selected'); - expect(el.children[2]).to.have.attribute('expanded'); }); }); @@ -439,7 +438,6 @@ describe('', () => { await el.updateComplete; // Assert - expect(node).to.have.attribute('selected'); expect(node).to.have.attribute('expanded'); }); }); From 402a00dcd3fa0cfab6ef7d9007719eaaa86be640 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Fri, 18 Aug 2023 12:05:22 -0400 Subject: [PATCH 16/30] update docs --- cspell.json | 1 + docs/pages/components/select.md | 63 +++++++++++++++------------------ 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/cspell.json b/cspell.json index 1ae873361..028a62671 100644 --- a/cspell.json +++ b/cspell.json @@ -160,6 +160,7 @@ "unbundles", "unbundling", "unicons", + "unsanitized", "unsupportive", "valpha", "valuenow", diff --git a/docs/pages/components/select.md b/docs/pages/components/select.md index 872563817..4705dc07b 100644 --- a/docs/pages/components/select.md +++ b/docs/pages/components/select.md @@ -457,55 +457,50 @@ const App = () => ( ### Custom Tags -When multiple options can be selected, you can provide custom tags by passing a function to the `getTag` property. -Your `getTag(option, index)` function can return a string, a Lit Template, -or an HTMLElement. +When multiple options can be selected, you can provide custom tags by passing a function to the `getTag` property. Your function can return a string of HTML, a Lit Template, or an [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement). The `getTag()` function will be called for each option. The first argument is an `` element and the second argument is the tag's index (its position in the tag list). + +Remember that custom tags are rendered in a shadow root. To style them, you can use the `style` attribute in your template or you can add your own [parts](/getting-started/customizing/#css-parts) and target them with the [`::part()`](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) selector. ```html:preview - - + + Email - - - + Phone - - - + Chat - - Option 4 - Option 5 - + ``` + +:::warning +Be sure you trust the content you are outputting! Passing unsanitized user input to `getTag()` can result in XSS vulnerabilities. +::: From 539eaded73dfb4ec9c50bd7dd4f974e5137eda17 Mon Sep 17 00:00:00 2001 From: Konnor Rogers Date: Fri, 18 Aug 2023 13:31:50 -0400 Subject: [PATCH 17/30] Update React Wrappers with Refs that work (#1526) * fix react types for refs * fix displayName * fix displayName] * attempt to fix typings for React refs * fix bad type * prettier * add changelog entry * prettier --- docs/pages/resources/changelog.md | 1 + package-lock.json | 14 +++++++------- package.json | 2 +- scripts/make-react.js | 11 ++++++++++- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/pages/resources/changelog.md b/docs/pages/resources/changelog.md index 57ed9edfa..1463fb5f4 100644 --- a/docs/pages/resources/changelog.md +++ b/docs/pages/resources/changelog.md @@ -14,6 +14,7 @@ New versions of Shoelace are released as-needed and generally occur when a criti ## Next +- Fixed type issues with the `ref` attribute in React Wrappers. [#1526] - Fixed a regression that caused `` to render incorrectly with gaps [#1523] - Improved expand/collapse behavior of `` to work more like users expect [#1521] diff --git a/package-lock.json b/package-lock.json index 7351e3c5f..acf21a85a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@ctrl/tinycolor": "^3.5.0", "@floating-ui/dom": "^1.2.1", - "@lit-labs/react": "^1.1.1", + "@lit-labs/react": "^1.2.1", "@shoelace-style/animations": "^1.1.0", "@shoelace-style/localize": "^3.1.1", "composed-offset-position": "^0.0.4", @@ -1474,9 +1474,9 @@ } }, "node_modules/@lit-labs/react": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-1.1.1.tgz", - "integrity": "sha512-9TC+/ZWb6BJlWCyUr14FKFlaGnyKpeEDorufXozQgke/VoVrslUQNaL7nBmrAWdNrmzx5jWgi8lFmWwrxMjnlA==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-1.2.1.tgz", + "integrity": "sha512-DiZdJYFU0tBbdQkfwwRSwYyI/mcWkg3sWesKRsHUd4G+NekTmmeq9fzsurvcKTNVa0comNljwtg4Hvi1ds3V+A==" }, "node_modules/@lit-labs/ssr-dom-shim": { "version": "1.1.1", @@ -18291,9 +18291,9 @@ } }, "@lit-labs/react": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-1.1.1.tgz", - "integrity": "sha512-9TC+/ZWb6BJlWCyUr14FKFlaGnyKpeEDorufXozQgke/VoVrslUQNaL7nBmrAWdNrmzx5jWgi8lFmWwrxMjnlA==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-1.2.1.tgz", + "integrity": "sha512-DiZdJYFU0tBbdQkfwwRSwYyI/mcWkg3sWesKRsHUd4G+NekTmmeq9fzsurvcKTNVa0comNljwtg4Hvi1ds3V+A==" }, "@lit-labs/ssr-dom-shim": { "version": "1.1.1", diff --git a/package.json b/package.json index 55554f475..9b1e2b809 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "dependencies": { "@ctrl/tinycolor": "^3.5.0", "@floating-ui/dom": "^1.2.1", - "@lit-labs/react": "^1.1.1", + "@lit-labs/react": "^1.2.1", "@shoelace-style/animations": "^1.1.0", "@shoelace-style/localize": "^3.1.1", "composed-offset-position": "^0.0.4", diff --git a/scripts/make-react.js b/scripts/make-react.js index db31c97e1..6169dd341 100644 --- a/scripts/make-react.js +++ b/scripts/make-react.js @@ -51,6 +51,15 @@ components.map(component => { ${eventImports} ${eventExports} + export type ForwardComponent< + Element extends HTMLElement, + ReactComponent extends React.ElementType + > = React.JSXElementConstructor< + React.ComponentPropsWithoutRef & { + ref?: React.ForwardedRef; + } + > & { displayName?: string } + const tagName = '${component.tagName}' const component = createComponent({ @@ -76,7 +85,7 @@ components.map(component => { } } - export default SlComponent; + export default SlComponent as ForwardComponent; `, Object.assign(prettierConfig, { parser: 'babel-ts' From a4fc1c5b44a8260efa890726e32d6d756db26260 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Mon, 21 Aug 2023 17:26:41 -0400 Subject: [PATCH 18/30] Submenus (#1527) * [RFC] Proof-of-concept commit for submenu support This is a Request For Comments to seek directional guidance towards implementing the submenu slot of menu-item. Includes: - SubmenuController to manage event listeners on menu-item. - Example usage in menu-item documentation. - Trivial tests to check rendering. Outstanding questions include: - Accessibility concerns. E.g. where to handle 'ArrowRight', 'ArrowLeft'? - Should selection of menu-item denoting submenu be possible or customizable? - How to parameterize contained popup? - Implementation concerns: - Use of ref / id - delegation of some rendering to the controller - What to test Related to [#620](https://github.com/shoelace-style/shoelace/issues/620). * Update submenu-controller.ts Removed extraneous `console.log()`. * PoC working of ArrowRight to focus on submenu. * Revert "PoC working of ArrowRight to focus on submenu." (Didn't mean to publish this.) This reverts commit be04e9a221e7b9e38995297e70d4517b3fae6468. * [WIP] Submenu WIP continues. - Submenus now close on change-of-focus, not a timeout. - Keyboard navigation support added. - Skidding fix for better alignment. - Submenu documentation moved to Menu page. - Tests for accessibility, right and left arrow keys. * Cleanup: Removed dead code and dead code comments. * style: Eslint warnings and errors fixed. npm run verify now passes. * fix: 2 changes to menu / submenu on-click behavior: 1. Close submenu on click explicitly, so this occurs even if the menu is not inside of an sl-dropdown. 2. In menu, ignore clicks that do not explicitly target a menu-item. Clicks that were on (e.g. a menu-border) were emitting select events. * fix: Prevent menu's extraneous Enter / space key propagation. Menu's handleKeyDown calls item.click (to emit the selection). Propagating the keyboard event on Enter / space would the cause re-entry into a submenu, so prevent the needless propagation. * Submenu tweaks ... - 100 ms delay when opening submenus on mouseover - Shadows added - Distance added to popup to have submenus overlap menu slightly. * polish up submenu stuff * stay highlighted when submenu is open * update changelog * resolve feedback --------- Co-authored-by: Bryce Moore --- docs/pages/components/dropdown.md | 91 +++++- docs/pages/components/menu.md | 109 ++++++++ docs/pages/components/select.md | 6 +- docs/pages/resources/changelog.md | 3 + .../menu-item/menu-item.component.ts | 61 +++- src/components/menu-item/menu-item.styles.ts | 18 +- src/components/menu-item/menu-item.test.ts | 111 ++++++++ .../menu-item/submenu-controller.ts | 262 ++++++++++++++++++ src/components/menu/menu.component.ts | 13 +- 9 files changed, 658 insertions(+), 16 deletions(-) create mode 100644 src/components/menu-item/submenu-controller.ts diff --git a/docs/pages/components/dropdown.md b/docs/pages/components/dropdown.md index da08f8816..b1876056e 100644 --- a/docs/pages/components/dropdown.md +++ b/docs/pages/components/dropdown.md @@ -310,6 +310,96 @@ const App = () => ( ); ``` +### Submenus + +To create a submenu, nest an `` element in a [menu item](/components/menu-item). + +```html:preview + + Edit + + + Undo + Redo + + Cut + Copy + Paste + + + Find + + Find… + Find Next + Find Previous + + + + Transformations + + Make uppercase + Make lowercase + Capitalize + + + + +``` + +```jsx:react +import SlButton from '@shoelace-style/shoelace/dist/react/button'; +import SlDivider from '@shoelace-style/shoelace/dist/react/divider'; +import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown'; +import SlMenu from '@shoelace-style/shoelace/dist/react/menu'; +import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item'; + +const css = ` + .dropdown-hoist { + border: solid 2px var(--sl-panel-border-color); + padding: var(--sl-spacing-medium); + overflow: hidden; + } +`; + +const App = () => ( + <> + + Edit + + + Undo + Redo + + Cut + Copy + Paste + + + Find + + Find… + Find Next + Find Previous + + + + Transformations + + Make uppercase + Make lowercase + Capitalize + + + + + +); +``` + +:::warning +As a UX best practice, avoid using more than one level of submenu when possible. +::: + ### Hoisting Dropdown panels will be clipped if they're inside a container that has `overflow: auto|hidden`. The `hoist` attribute forces the panel to use a fixed positioning strategy, allowing it to break out of the container. In this case, the panel will be positioned relative to its [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details. @@ -349,7 +439,6 @@ Dropdown panels will be clipped if they're inside a container that has `overflow import SlButton from '@shoelace-style/shoelace/dist/react/button'; import SlDivider from '@shoelace-style/shoelace/dist/react/divider'; import SlDropdown from '@shoelace-style/shoelace/dist/react/dropdown'; -import SlIcon from '@shoelace-style/shoelace/dist/react/icon'; import SlMenu from '@shoelace-style/shoelace/dist/react/menu'; import SlMenuItem from '@shoelace-style/shoelace/dist/react/menu-item'; diff --git a/docs/pages/components/menu.md b/docs/pages/components/menu.md index 2039960ae..dd9c662d5 100644 --- a/docs/pages/components/menu.md +++ b/docs/pages/components/menu.md @@ -44,3 +44,112 @@ const App = () => ( :::tip Menus are intended for system menus (dropdown menus, select menus, context menus, etc.). They should not be mistaken for navigation menus which serve a different purpose and have a different semantic meaning. If you're building navigation, use `