From 9520e850ddd9dfff92ce04615d333a0411c3a307 Mon Sep 17 00:00:00 2001 From: nathan Date: Thu, 17 Aug 2023 11:34:25 -0600 Subject: [PATCH 1/4] Update for path changes see 3a61d20d93f00415b95f7cc80df6d9d3230933b4 --- docs/pages/components/select.md | 54 ++++++++++++++++ src/components/select/select.component.ts | 75 +++++++++++++---------- 2 files changed, 97 insertions(+), 32 deletions(-) diff --git a/docs/pages/components/select.md b/docs/pages/components/select.md index 6fd05cbe..5f7791eb 100644 --- a/docs/pages/components/select.md +++ b/docs/pages/components/select.md @@ -454,3 +454,57 @@ 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 + +```html:preview + + + + Email + + + + + + Phone + + + + + + Chat + + + Option 4 + Option 5 + + + + +``` diff --git a/src/components/select/select.component.ts b/src/components/select/select.component.ts index 54d80985..779b5dc4 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 2/4] 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 779b5dc4..6aa4d36f 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 09:17:02 -0600 Subject: [PATCH 3/4] 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 5f7791eb..87256381 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 6aa4d36f..9bc5a0cd 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 12:05:22 -0400 Subject: [PATCH 4/4] 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 1ae87336..028a6267 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 87256381..4705dc07 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. +:::