diff --git a/libs/components/src/lib/checkbox/README.md b/libs/components/src/lib/checkbox/README.md index 326c5ab150..0d006baffc 100644 --- a/libs/components/src/lib/checkbox/README.md +++ b/libs/components/src/lib/checkbox/README.md @@ -71,7 +71,7 @@ Use the `connotation` attribute to set the checkbox color. ### Helper text -Add the `helper-text` to add some helper text below the checkbox. +Add the `helper-text` to add some helper text below the checkbox. If you need to add HTML to the helper text, use the `helper-text` slot. - Type: `string` | `undefined` - Default: `undefined` @@ -165,6 +165,24 @@ The default slot allows you to use rich content as the checkbox's label. ``` +### Helper-Text + +The `helper-text` slot allows you to use rich content as the checkbox's helper text. + +```html preview + + + Signed Webhooks are a way to verify that the request is + coming from Vonage. + +``` + ## Events
diff --git a/libs/components/src/lib/checkbox/checkbox.spec.ts b/libs/components/src/lib/checkbox/checkbox.spec.ts index 188eccdd52..79f4f5e458 100644 --- a/libs/components/src/lib/checkbox/checkbox.spec.ts +++ b/libs/components/src/lib/checkbox/checkbox.spec.ts @@ -132,72 +132,22 @@ describe('vwc-checkbox', () => { }); }); - describe('helper text', function () { - it('should render the helper text when attribute is set on checkbox', async function () { - const helperTextElementWithoutText = - element.shadowRoot?.querySelector('.helper-text'); - const helperText = 'Helper Text'; - element.helperText = helperText; - await elementUpdated(element); - expect(helperTextElementWithoutText).toBeNull(); - expect( - element.shadowRoot - ?.querySelector('.helper-message') - ?.textContent?.trim() - ).toEqual(helperText); - }); - }); - describe('success Text', () => { it('should add success class to base when successText is set', async function () { - (element as any).successText = 'success'; + element.successText = 'success'; await elementUpdated(element); expect( getBaseElement(element).classList.contains('success') ).toBeTruthy(); }); - - it('should show success text when successText is set', async function () { - (element as any).successText = 'success'; - await elementUpdated(element); - expect( - element.shadowRoot - ?.querySelector('.success-message') - ?.textContent?.trim() - ).toEqual('success'); - }); - - it('should remove success text when undefined', async function () { - (element as any).successText = 'success'; - await elementUpdated(element); - (element as any).successText = undefined; - await elementUpdated(element); - expect(element.shadowRoot?.querySelector('.success-message')).toBeNull(); - }); }); describe('error Text', () => { it('should add error class to base when errorText is set', async function () { - (element as any).errorText = 'error'; + element.errorText = 'error'; await elementUpdated(element); expect(getBaseElement(element).classList.contains('error')).toBeTruthy(); }); - - it('should show error text when errorText is set', async function () { - (element as any).errorText = 'error'; - await elementUpdated(element); - expect( - element.shadowRoot?.querySelector('.error-message')?.textContent?.trim() - ).toEqual('error'); - }); - - it('should remove error text when undefined', async function () { - (element as any).errorText = 'error'; - await elementUpdated(element); - (element as any).errorText = undefined; - await elementUpdated(element); - expect(element.shadowRoot?.querySelector('.error-message')).toBeNull(); - }); }); describe.each(['input', 'change'])('%s event', (eventName) => { diff --git a/libs/components/src/lib/checkbox/checkbox.template.ts b/libs/components/src/lib/checkbox/checkbox.template.ts index 1e7b7deae9..770b826b9c 100644 --- a/libs/components/src/lib/checkbox/checkbox.template.ts +++ b/libs/components/src/lib/checkbox/checkbox.template.ts @@ -71,11 +71,6 @@ export const CheckboxTemplate: FoundationElementTemplate< >${(x) => x.label}`}
- ${when((x) => x.helperText?.length, getFeedbackTemplate('helper', context))} - ${when( - (x) => !x.successText && x.errorValidationMessage, - getFeedbackTemplate('error', context) - )} - ${when((x) => x.successText, getFeedbackTemplate('success', context))} + ${getFeedbackTemplate(context)} `; }; diff --git a/libs/components/src/lib/checkbox/checkbox.ts b/libs/components/src/lib/checkbox/checkbox.ts index d74ba245e0..4c9373fe46 100644 --- a/libs/components/src/lib/checkbox/checkbox.ts +++ b/libs/components/src/lib/checkbox/checkbox.ts @@ -1,7 +1,4 @@ -import { - applyMixins, - Checkbox as FoundationCheckbox, -} from '@microsoft/fast-foundation'; +import { Checkbox as FoundationCheckbox } from '@microsoft/fast-foundation'; import { attr, observable } from '@microsoft/fast-element'; import type { Connotation } from '../enums.js'; import { @@ -12,6 +9,7 @@ import { formElements, FormElementSuccessText, } from '../../shared/patterns'; +import { applyMixinsWithObservables } from '../../shared/utils/applyMixinsWithObservables'; export const keySpace: ' ' = ' ' as const; @@ -28,6 +26,7 @@ export type CheckboxConnotation = Extract< /** * @public * @component checkbox + * @slot helper-text - Describes how to use the checkbox. Alternative to the `helper-text` attribute. * @event input - Event that emits when the component checked state changes * @vueModel modelValue current-checked change `(event.target as HTMLInputElement).checked` */ @@ -114,4 +113,8 @@ export interface Checkbox FormElementHelperText, ErrorText, FormElementSuccessText {} -applyMixins(Checkbox, FormElementHelperText, FormElementSuccessText); +applyMixinsWithObservables( + Checkbox, + FormElementHelperText, + FormElementSuccessText +); diff --git a/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Chromium-linux.png b/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Chromium-linux.png index e20829962b..6bc61ad01f 100644 Binary files a/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Chromium-linux.png and b/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Chromium-linux.png differ diff --git a/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Firefox-linux.png b/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Firefox-linux.png index e014d64e37..70db25712c 100644 Binary files a/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Firefox-linux.png and b/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Firefox-linux.png differ diff --git a/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Safari-linux.png b/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Safari-linux.png index 9011c613c7..3f79416d31 100644 Binary files a/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Safari-linux.png and b/libs/components/src/lib/checkbox/ui.test.ts-snapshots/snapshots-checkbox-Desktop-Safari-linux.png differ diff --git a/libs/components/src/lib/date-picker/README.md b/libs/components/src/lib/date-picker/README.md index af72d6dada..fac3f12849 100644 --- a/libs/components/src/lib/date-picker/README.md +++ b/libs/components/src/lib/date-picker/README.md @@ -29,7 +29,7 @@ In case you choose not to add a label, it is strongly recommended to add an `ari ### Helper text -Add the `helper-text` to add some helper text below the date picker. +Add the `helper-text` to add some helper text below the date picker. If you need to add HTML to the helper text, use the `helper-text` slot. - Type: `string` | `undefined` - Default: `undefined` @@ -120,6 +120,18 @@ Set the `max` attribute to configure the latest date to accept. The user will be > ``` +## Slots + +### Helper-Text + +The `helper-text` slot allows you to use rich content as the date picker's helper text. + +```html preview locale-switcher 460px + + Please see our opening times. + +``` + ## Events
diff --git a/libs/components/src/lib/date-picker/date-picker.ts b/libs/components/src/lib/date-picker/date-picker.ts index edbe56c373..a4da1d52c7 100644 --- a/libs/components/src/lib/date-picker/date-picker.ts +++ b/libs/components/src/lib/date-picker/date-picker.ts @@ -20,6 +20,7 @@ import { * * @public * @component date-picker + * @slot helper-text - Describes how to use the date-picker. Alternative to the `helper-text` attribute. * @vueModel modelValue value input `(event.target as HTMLInputElement).value` */ @errorText diff --git a/libs/components/src/lib/date-range-picker/README.md b/libs/components/src/lib/date-range-picker/README.md index ed9310872d..b54ae66720 100644 --- a/libs/components/src/lib/date-range-picker/README.md +++ b/libs/components/src/lib/date-range-picker/README.md @@ -46,7 +46,7 @@ In case you choose not to add a label, it is strongly recommended to add an `ari ### Helper text -Add the `helper-text` to add some helper text below the date range picker. +Add the `helper-text` to add some helper text below the date range picker. If you need to add HTML to the helper text, use the `helper-text` slot. - Type: `string` | `undefined` - Default: `undefined` @@ -153,6 +153,18 @@ Set the `max` attribute to configure the latest date to accept. The user will be > ``` +## Slots + +### Helper-Text + +The `helper-text` slot allows you to use rich content as the date range picker's helper text. + +```html preview locale-switcher 460px + + Please see our opening times. + +``` + ## Events
diff --git a/libs/components/src/lib/date-range-picker/date-range-picker.ts b/libs/components/src/lib/date-range-picker/date-range-picker.ts index de84476f05..41ad82484b 100644 --- a/libs/components/src/lib/date-range-picker/date-range-picker.ts +++ b/libs/components/src/lib/date-range-picker/date-range-picker.ts @@ -30,6 +30,7 @@ function isDefined(value: T | null | undefined): value is T { /** * @public * @component date-range-picker + * @slot helper-text - Describes how to use the date-range-picker. Alternative to the `helper-text` attribute. * @event input:start - Event emitted when the start value changes * @event input:end - Event emitted when the end value changes * @vueModel start current-start input:start `(event.target as any).start` diff --git a/libs/components/src/lib/file-picker/README.md b/libs/components/src/lib/file-picker/README.md index 51e77ebb00..f1389ba20b 100644 --- a/libs/components/src/lib/file-picker/README.md +++ b/libs/components/src/lib/file-picker/README.md @@ -23,7 +23,7 @@ Use the `label` member to set the file picker's label. ### Helper text -Add the `helper-text` to add some helper text below the file picker. +Add the `helper-text` to add some helper text below the file picker. If you need to add HTML to the helper text, use the `helper-text` slot. - Type: `string` | `undefined` - Default: `undefined` @@ -127,6 +127,20 @@ Use the default slot to set the content of the file picker. ``` +### Helper-Text + +The `helper-text` slot allows you to use rich content as the file picker's helper text. + +```html preview + + Drag & Drop the .csv file here or click to upload + Max file size is 0.1MB. + Learn how export your data to .csv + +``` + ## Properties
diff --git a/libs/components/src/lib/file-picker/file-picker.spec.ts b/libs/components/src/lib/file-picker/file-picker.spec.ts index acf7dac3fd..e329804edb 100644 --- a/libs/components/src/lib/file-picker/file-picker.spec.ts +++ b/libs/components/src/lib/file-picker/file-picker.spec.ts @@ -103,45 +103,6 @@ describe('vwc-file-picker', () => { }); }); - describe('helper text', function () { - it('should render the helper text when attribute is set on file picker', async function () { - const helperTextElementWithoutText = - element.shadowRoot?.querySelector('.helper-message'); - const helperText = 'Helper Text'; - element.helperText = helperText; - await elementUpdated(element); - expect(helperTextElementWithoutText).toBeNull(); - expect( - element.shadowRoot - ?.querySelector('.helper-message') - ?.textContent?.trim() - ).toEqual(helperText); - }); - }); - - describe('error text', function () { - it('should render the error text when attribute is set', async function () { - const errorTextElementWithoutAttribute = - element.shadowRoot?.querySelector('.error-message'); - element.errorText = 'Error Text'; - await elementUpdated(element); - expect(errorTextElementWithoutAttribute).toBeNull(); - expect( - element.shadowRoot?.querySelector('.error-message')?.textContent?.trim() - ).toEqual('Error Text'); - }); - - it('should hide helper text if error text is set', async function () { - element.helperText = 'Helper Text'; - element.errorText = 'Error Text'; - await elementUpdated(element); - expect(element.shadowRoot?.querySelector('.helper-message')).toBe(null); - expect(element.shadowRoot?.querySelector('.error-message')).not.toBe( - null - ); - }); - }); - describe('value', function () { it('should be set to a fake path when a file is added', async function () { addFiles([await generateFile('london.png', 1)]); diff --git a/libs/components/src/lib/file-picker/file-picker.template.ts b/libs/components/src/lib/file-picker/file-picker.template.ts index 5eb1820d95..1c651589ea 100644 --- a/libs/components/src/lib/file-picker/file-picker.template.ts +++ b/libs/components/src/lib/file-picker/file-picker.template.ts @@ -42,14 +42,7 @@ export const FilePickerTemplate: ( >
- ${when( - (x) => !x.errorValidationMessage && x.helperText?.length, - getFeedbackTemplate('helper', context) - )} - ${when( - (x) => x.errorValidationMessage, - getFeedbackTemplate('error', context) - )} + ${getFeedbackTemplate(context)}
`; diff --git a/libs/components/src/lib/file-picker/file-picker.ts b/libs/components/src/lib/file-picker/file-picker.ts index 5d6496a2de..d55c898ef6 100644 --- a/libs/components/src/lib/file-picker/file-picker.ts +++ b/libs/components/src/lib/file-picker/file-picker.ts @@ -1,5 +1,4 @@ /* eslint-disable max-len */ -import { applyMixins } from '@microsoft/fast-foundation'; import { attr } from '@microsoft/fast-element'; import type { DropzoneFile } from 'dropzone'; import Dropzone from 'dropzone'; @@ -14,6 +13,7 @@ import { Localized, } from '../../shared/patterns'; import type { Button } from '../button/button'; +import { applyMixinsWithObservables } from '../../shared/utils/applyMixinsWithObservables'; import { FormAssociatedFilePicker } from './file-picker.form-associated'; /** @@ -30,6 +30,7 @@ const isFormAssociatedTryingToSetFormValueToFakePath = ( /** * @public * @component file-picker + * @slot helper-text - Describes how to use the file-picker. Alternative to the `helper-text` attribute. * @event change - Emitted when a file is added or removed. */ @errorText @@ -278,4 +279,4 @@ export interface FilePicker ErrorText, FormElement, FormElementHelperText {} -applyMixins(FilePicker, FormElementHelperText, Localized); +applyMixinsWithObservables(FilePicker, FormElementHelperText, Localized); diff --git a/libs/components/src/lib/number-field/README.md b/libs/components/src/lib/number-field/README.md index 2599736365..fed0de7f62 100644 --- a/libs/components/src/lib/number-field/README.md +++ b/libs/components/src/lib/number-field/README.md @@ -73,7 +73,7 @@ Set the `max` attribute to set the maximum value for the number field. ### Helper text -Add the `helper-text` to add some helper text below the number field. +Add the `helper-text` to add some helper text below the number field. If you need to add HTML to the helper text, use the `helper-text` slot. - Type: `string` | `undefined` - Default: `undefined` @@ -166,6 +166,22 @@ Add the `readonly` attribute to restrict user from changing the number field's v > ``` +## Slots + +### Helper-Text + +The `helper-text` slot allows you to use rich content as the number field's helper text. + +Example showing a link in the helper text: + +```html preview + + The timeout in seconds. Guide to setting timeouts + +``` + ## Methods
diff --git a/libs/components/src/lib/number-field/number-field.spec.ts b/libs/components/src/lib/number-field/number-field.spec.ts index 7d25ca5f8b..1aeb976861 100644 --- a/libs/components/src/lib/number-field/number-field.spec.ts +++ b/libs/components/src/lib/number-field/number-field.spec.ts @@ -24,10 +24,6 @@ describe('vwc-number-field', () => { element.dispatchEvent(new Event('blur')); } - function setToFocused() { - element.dispatchEvent(new Event('focus')); - } - function setValidityToError(errorMessage = 'error') { element.setValidity({ badInput: true }, errorMessage); element.validate(); @@ -293,22 +289,6 @@ describe('vwc-number-field', () => { }); }); - describe('helper text', function () { - it('should render the helper text when attribute is set', async function () { - const helperTextElementWithoutText = - element.shadowRoot?.querySelector('.helper-message'); - const helperText = 'Helper Text'; - element.helperText = helperText; - await elementUpdated(element); - expect(helperTextElementWithoutText).toBeNull(); - expect( - element.shadowRoot - ?.querySelector('.helper-message') - ?.textContent?.trim() - ).toEqual(helperText); - }); - }); - describe('error message', function () { it('should add class error to base if not valid', async function () { element.dirtyValue = true; @@ -318,83 +298,6 @@ describe('vwc-number-field', () => { expect(getRootElement(element).classList.contains('error')).toEqual(true); }); - - it('should render the error message when not valid', async function () { - const errorElementWithoutText = - element.shadowRoot?.querySelector('.error-message'); - const errorMessage = 'Error Text'; - - element.dirtyValue = true; - setToBlurred(); - setValidityToError(errorMessage); - await elementUpdated(element); - - expect(errorElementWithoutText).toBeNull(); - expect( - element.shadowRoot?.querySelector('.error-message')?.textContent?.trim() - ).toEqual(errorMessage); - }); - - it('should render the error message only after a blur', async function () { - const errorMessage = 'Error Text'; - element.dirtyValue = true; - setValidityToError(errorMessage); - await elementUpdated(element); - expect(element.shadowRoot?.querySelector('.error-message')).toBeNull(); - }); - - it('should replace helper text', async function () { - element.helperText = 'helper text'; - element.dirtyValue = true; - setToBlurred(); - setValidityToError(); - await elementUpdated(element); - expect(element.shadowRoot?.querySelector('.helper-text')).toBeNull(); - }); - - it('should set error message to empty string when pristine', async function () { - setValidityToError(); - await elementUpdated(element); - expect(element.errorValidationMessage).toEqual(''); - }); - - it('should validate after a blur', async function () { - const errorMessage = 'Error Text'; - element.dirtyValue = true; - setValidityToError(errorMessage); - setToBlurred(); - await elementUpdated(element); - expect( - element.shadowRoot?.querySelector('.error-message')?.textContent?.trim() - ).toEqual(errorMessage); - }); - - it('should update error message when blurred', async function () { - setToBlurred(); - const errorMessage = 'Error Text'; - const errorMessageTwo = 'Error Text 2'; - element.dirtyValue = true; - setValidityToError(errorMessage); - await elementUpdated(element); - - setValidityToError(errorMessageTwo); - await elementUpdated(element); - - expect( - element.shadowRoot?.querySelector('.error-message')?.textContent?.trim() - ).toEqual(errorMessageTwo); - }); - - it('should change the error message only when already not valid', async function () { - setToBlurred(); - setToFocused(); - const errorMessage = 'Error Text'; - element.dirtyValue = true; - setValidityToError(errorMessage); - await elementUpdated(element); - - expect(element.shadowRoot?.querySelector('.error-message')).toBeNull(); - }); }); describe('successText', function () { @@ -406,32 +309,6 @@ describe('vwc-number-field', () => { true ); }); - - it('should not show helper text when success is shown', async function () { - element.helperText = 'help'; - element.successText = 'success'; - await elementUpdated(element); - expect(element.shadowRoot?.querySelector('.helper-text')).toBeNull(); - }); - - it('should not show error message when success is shown', async function () { - element.dirtyValue = true; - setToBlurred(); - setValidityToError('blah'); - element.successText = 'success'; - await elementUpdated(element); - expect(element.shadowRoot?.querySelector('.error-message')).toBeNull(); - }); - - it('should show success message if set', async function () { - element.successText = 'success'; - await elementUpdated(element); - expect( - element.shadowRoot - ?.querySelector('.success-message') - ?.textContent?.trim() - ).toEqual('success'); - }); }); describe('disabled', function () { diff --git a/libs/components/src/lib/number-field/number-field.template.ts b/libs/components/src/lib/number-field/number-field.template.ts index 579a7bdda7..cb22af6bd2 100644 --- a/libs/components/src/lib/number-field/number-field.template.ts +++ b/libs/components/src/lib/number-field/number-field.template.ts @@ -137,16 +137,7 @@ export const NumberFieldTemplate: (
${() => numberControlButtons(context)}
- ${when( - (x) => - !x.successText && !x.errorValidationMessage && x.helperText?.length, - getFeedbackTemplate('helper', context) - )} - ${when( - (x) => !x.successText && x.errorValidationMessage, - getFeedbackTemplate('error', context) - )} - ${when((x) => x.successText, getFeedbackTemplate('success', context))} + ${getFeedbackTemplate(context)} `; }; diff --git a/libs/components/src/lib/number-field/number-field.ts b/libs/components/src/lib/number-field/number-field.ts index 5052a0134e..f3a6c402c2 100644 --- a/libs/components/src/lib/number-field/number-field.ts +++ b/libs/components/src/lib/number-field/number-field.ts @@ -1,7 +1,4 @@ -import { - applyMixins, - NumberField as FastNumberField, -} from '@microsoft/fast-foundation'; +import { NumberField as FastNumberField } from '@microsoft/fast-foundation'; import { attr } from '@microsoft/fast-element'; import type { Appearance, Shape } from '../enums'; import { @@ -15,6 +12,7 @@ import { Localized, } from '../../shared/patterns'; import { AffixIcon } from '../../shared/patterns'; +import { applyMixinsWithObservables } from '../../shared/utils/applyMixinsWithObservables'; export type NumberFieldAppearance = Extract< Appearance, @@ -48,6 +46,7 @@ function makeStep(element: NumberField, direction: number) { /** * @public * @component number-field + * @slot helper-text - Describes how to use the number-field. Alternative to the `helper-text` attribute. * @vueModel modelValue current-value input `(event.target as HTMLInputElement).value` */ @errorText @@ -125,7 +124,7 @@ export interface NumberField FormElementHelperText, FormElementSuccessText, Localized {} -applyMixins( +applyMixinsWithObservables( NumberField, Localized, AffixIcon, diff --git a/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Chromium-linux.png b/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Chromium-linux.png index 16d0fd412c..96fcca4adc 100644 Binary files a/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Chromium-linux.png and b/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Chromium-linux.png differ diff --git a/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Firefox-linux.png b/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Firefox-linux.png index 17807adcea..ec26135b36 100644 Binary files a/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Firefox-linux.png and b/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Firefox-linux.png differ diff --git a/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Safari-linux.png b/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Safari-linux.png index 7d0002ba40..61b979cb49 100644 Binary files a/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Safari-linux.png and b/libs/components/src/lib/number-field/ui.test.ts-snapshots/snapshots-number-field-Desktop-Safari-linux.png differ diff --git a/libs/components/src/lib/select/README.md b/libs/components/src/lib/select/README.md index 697e6b7ee1..f3c403e1cd 100644 --- a/libs/components/src/lib/select/README.md +++ b/libs/components/src/lib/select/README.md @@ -94,7 +94,7 @@ Set the `appearance` attribute to change the Select appearance. (`'ghost'` is typically used within a composition such as action group / toolbar). -```html preview +```html preview 200px @@ -108,7 +108,7 @@ Use the `shape` attribute to change the Select edges. - Type: `'rounded'` | `'pill'` - Default: `'rounded'` -```html preview +```html preview 200px @@ -117,7 +117,7 @@ Use the `shape` attribute to change the Select edges. ### Helper text -Add the `helper-text` to add some helper text below the select. +Add the `helper-text` to add some helper text below the select. If you need to add HTML to the helper text, use the `helper-text` slot. - Type: `string` | `undefined` - Default: `undefined` @@ -215,7 +215,7 @@ This is useful for cases in which the dropdown is obstructed by other elements ( - Type: `boolean` - Default: `false` -```html preview +```html preview 200px @@ -315,6 +315,21 @@ If set, the `icon` attribute is ignored. ``` +### Helper-Text + +The `helper-text` slot allows you to use rich content as the select's helper text. + +```html preview 230px + + + + + Please select the type of your business. + +``` + ## Dimensions ### Height (CSS Variable) diff --git a/libs/components/src/lib/select/select.scss b/libs/components/src/lib/select/select.scss index 5658b7155e..9c0c90fd74 100644 --- a/libs/components/src/lib/select/select.scss +++ b/libs/components/src/lib/select/select.scss @@ -21,10 +21,8 @@ $low-ink-color: --_low-ink-color; -@supports selector(:focus-visible) { - :host(:focus-visible) { - outline: none; - } +:host(:focus-visible) { + outline: none; } :host { @@ -132,6 +130,10 @@ $low-ink-color: --_low-ink-color; } } +.feedback-wrapper { + display: contents; +} + ::part(popup-base) { inline-size: max-content; min-inline-size: var(#{variables.$select-fixed-width}, 100%); diff --git a/libs/components/src/lib/select/select.spec.ts b/libs/components/src/lib/select/select.spec.ts index 483c878bd1..6f3ca0942e 100644 --- a/libs/components/src/lib/select/select.spec.ts +++ b/libs/components/src/lib/select/select.spec.ts @@ -141,74 +141,24 @@ describe('vwc-select', () => { }); }); - describe('helper text', function () { - it('should render the helper text when attribute is set on select', async function () { - const helperTextElementWithoutText = - element.shadowRoot?.querySelector('.helper-text'); - const helperText = 'Helper Text'; - element.helperText = helperText; - await elementUpdated(element); - expect(helperTextElementWithoutText).toBeNull(); - expect( - element.shadowRoot - ?.querySelector('.helper-message') - ?.textContent?.trim() - ).toEqual(helperText); - }); - }); - - describe('success Text', () => { + describe('success text', () => { it('should add success class to base when successText is set', async function () { - (element as any).successText = 'success'; + element.successText = 'success'; await elementUpdated(element); expect( getControlElement(element).classList.contains('success') ).toBeTruthy(); }); - - it('should show success text when successText is set', async function () { - (element as any).successText = 'success'; - await elementUpdated(element); - expect( - element.shadowRoot - ?.querySelector('.success-message') - ?.textContent?.trim() - ).toEqual('success'); - }); - - it('should remove success text when undefined', async function () { - (element as any).successText = 'success'; - await elementUpdated(element); - (element as any).successText = undefined; - await elementUpdated(element); - expect(element.shadowRoot?.querySelector('.success-message')).toBeNull(); - }); }); - describe('error Text', () => { + describe('error text', () => { it('should add error class to base when errorText is set', async function () { - (element as any).errorText = 'error'; + element.errorText = 'error'; await elementUpdated(element); expect( getControlElement(element).classList.contains('error') ).toBeTruthy(); }); - - it('should show error text when errorText is set', async function () { - (element as any).errorText = 'error'; - await elementUpdated(element); - expect( - element.shadowRoot?.querySelector('.error-message')?.textContent?.trim() - ).toEqual('error'); - }); - - it('should remove error text when undefined', async function () { - (element as any).errorText = 'error'; - await elementUpdated(element); - (element as any).errorText = undefined; - await elementUpdated(element); - expect(element.shadowRoot?.querySelector('.error-message')).toBeNull(); - }); }); describe('disabled', function () { @@ -421,7 +371,7 @@ describe('vwc-select', () => { it('should prevent focusin from firing before click event', async () => { element.innerHTML = ` - `; await elementUpdated(element); @@ -639,6 +589,19 @@ describe('vwc-select', () => { }); }); + describe('feedback messages', () => { + it('should ignore events when triggered on feedback messages', async () => { + element.helperText = 'helper text'; + await elementUpdated(element); + + element + .shadowRoot!.querySelector('.helper-message')! + .dispatchEvent(new Event('click', { bubbles: true, composed: true })); + + expect(element.open).toBe(false); + }); + }); + describe('a11y', () => { it('should pass html a11y test', async () => { element.innerHTML = ` diff --git a/libs/components/src/lib/select/select.template.ts b/libs/components/src/lib/select/select.template.ts index ee4077796b..737b81aed5 100644 --- a/libs/components/src/lib/select/select.template.ts +++ b/libs/components/src/lib/select/select.template.ts @@ -1,4 +1,5 @@ import { + type ExecutionContext, html, ref, slotted, @@ -106,18 +107,26 @@ function renderControl(context: ElementDefinitionContext) { property: 'slottedOptions', })}> - + - ${when((x) => x.helperText?.length, getFeedbackTemplate('helper', context))} - ${when( - (x) => !x.successText && x.errorValidationMessage, - getFeedbackTemplate('error', context) - )} - ${when((x) => x.successText, getFeedbackTemplate('success', context))} `; } +/** + * Ignore events that originate from feedback, e.g. a click on link + */ +function ifNotFromFeedback( + handler: (x: Select, event: E) => void +) { + return (x: Select, c: ExecutionContext) => { + if (!c.event.composedPath().includes(x._feedbackWrapper!)) { + return handler(x, c.event as E); + } + return true; + }; +} + /** * The template for the Select component. * @@ -128,26 +137,36 @@ export const SelectTemplate: ( context: ElementDefinitionContext, definition: FoundationElementDefinition ) => ViewTemplate` `; + return html