mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 12:09:26 +00:00
prettier
This commit is contained in:
@@ -206,43 +206,44 @@ describe('<wa-button>', () => {
|
||||
expect(submitter.formNoValidate).to.be.true;
|
||||
});
|
||||
|
||||
it("should only submit button name / value pair when the form is submitted", async () => {
|
||||
const form = await fixture<HTMLFormElement>(html`<form>
|
||||
<wa-button type="submit" name="btn-1" value="value-1">Button 1</wa-button>
|
||||
<wa-button type="submit" name="btn-2" value="value-2">Button 2</wa-button>
|
||||
</form>`);
|
||||
it('should only submit button name / value pair when the form is submitted', async () => {
|
||||
const form = await fixture<HTMLFormElement>(
|
||||
html`<form>
|
||||
<wa-button type="submit" name="btn-1" value="value-1">Button 1</wa-button>
|
||||
<wa-button type="submit" name="btn-2" value="value-2">Button 2</wa-button>
|
||||
</form>`
|
||||
);
|
||||
|
||||
let formData = new FormData(form)
|
||||
let submitter: null | HTMLButtonElement = document.createElement("button")
|
||||
let formData = new FormData(form);
|
||||
let submitter: null | HTMLButtonElement = document.createElement('button');
|
||||
|
||||
form.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
formData = new FormData(form);
|
||||
submitter = e.submitter as HTMLButtonElement;
|
||||
});
|
||||
|
||||
form.addEventListener("submit", (e) => {
|
||||
e.preventDefault()
|
||||
formData = new FormData(form)
|
||||
submitter = e.submitter as HTMLButtonElement
|
||||
})
|
||||
expect(formData.get('btn-1')).to.be.null;
|
||||
expect(formData.get('btn-2')).to.be.null;
|
||||
|
||||
expect(formData.get("btn-1")).to.be.null
|
||||
expect(formData.get("btn-2")).to.be.null
|
||||
form.querySelector('wa-button')?.click();
|
||||
await aTimeout(0);
|
||||
|
||||
form.querySelector("wa-button")?.click()
|
||||
await aTimeout(0)
|
||||
expect(formData.get('btn-1')).to.be.null;
|
||||
expect(formData.get('btn-2')).to.be.null;
|
||||
|
||||
expect(formData.get("btn-1")).to.be.null
|
||||
expect(formData.get("btn-2")).to.be.null
|
||||
expect(submitter.name).to.equal('btn-1');
|
||||
expect(submitter.value).to.equal('value-1');
|
||||
|
||||
expect(submitter.name).to.equal("btn-1")
|
||||
expect(submitter.value).to.equal("value-1")
|
||||
form.querySelectorAll('wa-button')[1]?.click();
|
||||
await aTimeout(0);
|
||||
|
||||
form.querySelectorAll("wa-button")[1]?.click()
|
||||
await aTimeout(0)
|
||||
expect(formData.get('btn-1')).to.be.null;
|
||||
expect(formData.get('btn-2')).to.be.null;
|
||||
|
||||
expect(formData.get("btn-1")).to.be.null
|
||||
expect(formData.get("btn-2")).to.be.null
|
||||
|
||||
expect(submitter.name).to.equal("btn-2")
|
||||
expect(submitter.value).to.equal("value-2")
|
||||
})
|
||||
expect(submitter.name).to.equal('btn-2');
|
||||
expect(submitter.value).to.equal('value-2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when using methods', () => {
|
||||
|
||||
@@ -188,7 +188,7 @@ describe('<wa-checkbox>', () => {
|
||||
|
||||
it('should be valid when required and checked', async () => {
|
||||
const checkbox = await fixture<HTMLFormElement>(html` <wa-checkbox required checked></wa-checkbox> `);
|
||||
await checkbox.updateComplete
|
||||
await checkbox.updateComplete;
|
||||
expect(checkbox.checkValidity()).to.be.true;
|
||||
});
|
||||
|
||||
|
||||
@@ -48,13 +48,11 @@ import type { CSSResultGroup } from 'lit';
|
||||
* @cssproperty --box-shadow - The shadow effects around the edges of the checkbox.
|
||||
* @cssproperty --toggle-size - The size of the checkbox.
|
||||
*/
|
||||
@customElement("wa-checkbox")
|
||||
@customElement('wa-checkbox')
|
||||
export default class WaCheckbox extends WebAwesomeFormAssociated {
|
||||
static styles: CSSResultGroup = [componentStyles, styles];
|
||||
static get validators () {
|
||||
return [
|
||||
GroupRequiredValidator(),
|
||||
]
|
||||
static get validators() {
|
||||
return [GroupRequiredValidator()];
|
||||
}
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'help-text');
|
||||
@@ -88,7 +86,7 @@ export default class WaCheckbox extends WebAwesomeFormAssociated {
|
||||
@property({ type: Boolean, reflect: true }) indeterminate = false;
|
||||
|
||||
/** The default value of the form control. Primarily used for resetting the form control. */
|
||||
@property({ type: Boolean, reflect: true, attribute: "checked" }) defaultChecked = false;
|
||||
@property({ type: Boolean, reflect: true, attribute: 'checked' }) defaultChecked = false;
|
||||
|
||||
/**
|
||||
* By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
|
||||
@@ -123,24 +121,24 @@ export default class WaCheckbox extends WebAwesomeFormAssociated {
|
||||
this.emit('wa-focus');
|
||||
}
|
||||
|
||||
@watch(["defaultChecked"])
|
||||
handleDefaultCheckedChange () {
|
||||
@watch(['defaultChecked'])
|
||||
handleDefaultCheckedChange() {
|
||||
if (!this.hasInteracted && this.checked !== this.defaultChecked) {
|
||||
this.checked = this.defaultChecked
|
||||
this.value = this.checked ? this.value || 'on' : null
|
||||
this.checked = this.defaultChecked;
|
||||
this.value = this.checked ? this.value || 'on' : null;
|
||||
// These @watch() commands seem to override the base element checks for changes, so we need to setValue for the form and and updateValidity()
|
||||
this.setValue(this.value, this.value);
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
}
|
||||
}
|
||||
|
||||
@watch(["value", "checked"], { waitUntilFirstUpdate: true })
|
||||
handleValueOrCheckedChange () {
|
||||
this.value = this.checked ? this.value || 'on' : null
|
||||
@watch(['value', 'checked'], { waitUntilFirstUpdate: true })
|
||||
handleValueOrCheckedChange() {
|
||||
this.value = this.checked ? this.value || 'on' : null;
|
||||
|
||||
// These @watch() commands seem to override the base element checks for changes, so we need to setValue for the form and and updateValidity()
|
||||
this.setValue(this.value, this.value);
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
@watch(['checked', 'indeterminate'], { waitUntilFirstUpdate: true })
|
||||
@@ -150,13 +148,13 @@ export default class WaCheckbox extends WebAwesomeFormAssociated {
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
formResetCallback () {
|
||||
formResetCallback() {
|
||||
// Evaluate checked before the super call because of our watcher on value.
|
||||
super.formResetCallback()
|
||||
this.checked = this.defaultChecked
|
||||
this.value = this.checked ? this.value || 'on' : null
|
||||
super.formResetCallback();
|
||||
this.checked = this.defaultChecked;
|
||||
this.value = this.checked ? this.value || 'on' : null;
|
||||
this.setValue(this.value, this.value);
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
/** Simulates a click on the checkbox. */
|
||||
|
||||
@@ -91,14 +91,12 @@ declare const EyeDropper: EyeDropperConstructor;
|
||||
* @cssproperty --slider-handle-size - The diameter of the slider's handle.
|
||||
* @cssproperty --swatch-size - The size of each predefined color swatch.
|
||||
*/
|
||||
@customElement("wa-color-picker")
|
||||
@customElement('wa-color-picker')
|
||||
export default class WaColorPicker extends WebAwesomeFormAssociated {
|
||||
static styles: CSSResultGroup = [componentStyles, styles];
|
||||
|
||||
static get validators () {
|
||||
return [
|
||||
RequiredValidator(),
|
||||
]
|
||||
static get validators() {
|
||||
return [RequiredValidator()];
|
||||
}
|
||||
|
||||
private isSafeValue = false;
|
||||
@@ -109,15 +107,15 @@ export default class WaColorPicker extends WebAwesomeFormAssociated {
|
||||
|
||||
// @TODO: This is a hacky way to show the "Please fill out this field", do we want the old behavior where it opens the dropdown?
|
||||
// or is the new behavior okay?
|
||||
get validationTarget () {
|
||||
get validationTarget() {
|
||||
// This puts the popup on the element only if the color picker is expanded.
|
||||
if (!this.inline && this.dropdown?.open) {
|
||||
return this.input
|
||||
return this.input;
|
||||
}
|
||||
|
||||
// This puts popup on the colorpicker itself without needing to expand it to show the input.
|
||||
// This is necessary because form submissions expect the "anchor" to be currently shown.
|
||||
return this.trigger
|
||||
return this.trigger;
|
||||
}
|
||||
|
||||
@query('.color-dropdown') dropdown: WaDropdown;
|
||||
@@ -138,10 +136,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociated {
|
||||
* in a specific format, use the `getFormattedValue()` method. The value is submitted as a name/value pair with form
|
||||
* data.
|
||||
*/
|
||||
@property({attribute: false}) value = '';
|
||||
@property({ attribute: false }) value = '';
|
||||
|
||||
/** The default value of the form control. Primarily used for resetting the form control. */
|
||||
@property({attribute: "value", reflect: true}) defaultValue = '';
|
||||
@property({ attribute: 'value', reflect: true }) defaultValue = '';
|
||||
|
||||
/**
|
||||
* The color picker's label. This will not be displayed, but it will be announced by assistive devices. If you need to
|
||||
@@ -620,12 +618,12 @@ export default class WaColorPicker extends WebAwesomeFormAssociated {
|
||||
private handleAfterHide() {
|
||||
this.previewButton.classList.remove('color-picker__preview-color--copied');
|
||||
// Update validity so we get a new anchor.
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
private handleAfterShow() {
|
||||
// Update validity so we get a new anchor.
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
private handleEyeDropper() {
|
||||
@@ -694,7 +692,6 @@ export default class WaColorPicker extends WebAwesomeFormAssociated {
|
||||
handleValueChange(oldValue: string | undefined, newValue: string) {
|
||||
this.isEmpty = !newValue;
|
||||
|
||||
|
||||
if (!newValue) {
|
||||
this.hue = 0;
|
||||
this.saturation = 0;
|
||||
@@ -787,13 +784,29 @@ export default class WaColorPicker extends WebAwesomeFormAssociated {
|
||||
if (!this.disabled) {
|
||||
// By standards we have to emit a `wa-invalid` event here synchronously.
|
||||
// this.formControlController.emitInvalidEvent();
|
||||
this.emit("wa-invalid")
|
||||
this.emit('wa-invalid');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.reportValidity()
|
||||
return super.reportValidity();
|
||||
}
|
||||
|
||||
formStateRestoreCallback(...args: Parameters<WebAwesomeFormAssociated['formStateRestoreCallback']>) {
|
||||
const [value, reason] = args;
|
||||
const oldValue = this.value;
|
||||
super.formStateRestoreCallback(value, reason);
|
||||
|
||||
this.handleValueChange(oldValue, this.value);
|
||||
}
|
||||
|
||||
formResetCallback() {
|
||||
const oldValue = this.value;
|
||||
this.value = this.defaultValue;
|
||||
this.handleValueChange(oldValue, this.value);
|
||||
|
||||
super.formResetCallback();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
|
||||
import { aTimeout, elementUpdated, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { getFormControls, serialize } from '../../../dist/webawesome.js';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands'; // must come from the same module
|
||||
import { isSafari } from '../../internal/test.js';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js'; // must come from the same module
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type WaInput from './input.js';
|
||||
import { isSafari } from '../../internal/test.js';
|
||||
|
||||
describe('<wa-input>', () => {
|
||||
it('should pass accessibility tests', async () => {
|
||||
@@ -168,16 +168,18 @@ describe('<wa-input>', () => {
|
||||
});
|
||||
|
||||
it('should not add a value to the form if disabled', async () => {
|
||||
const form = await fixture<HTMLFormElement>(html` <form><wa-input name="name" disabled required></wa-input></form>`);
|
||||
const el = form.querySelector("wa-input")!
|
||||
el.value = "blah"
|
||||
const form = await fixture<HTMLFormElement>(
|
||||
html` <form><wa-input name="name" disabled required></wa-input></form>`
|
||||
);
|
||||
const el = form.querySelector('wa-input')!;
|
||||
el.value = 'blah';
|
||||
await el.updateComplete;
|
||||
|
||||
expect(new FormData(form).get("name")).to.equal(null)
|
||||
expect(new FormData(form).get('name')).to.equal(null);
|
||||
el.disabled = false;
|
||||
await el.updateComplete;
|
||||
// Should be invalid while enabled
|
||||
expect(new FormData(form).get("name")).to.equal("blah")
|
||||
expect(new FormData(form).get('name')).to.equal('blah');
|
||||
});
|
||||
|
||||
it('should receive the correct validation attributes ("states") when valid', async () => {
|
||||
@@ -577,25 +579,25 @@ describe('<wa-input>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("Should be invalid if the pattern is invalid", async () => {
|
||||
it('Should be invalid if the pattern is invalid', async () => {
|
||||
const el = await fixture<WaInput>(html` <wa-input required pattern="1234"></wa-input> `);
|
||||
|
||||
el.formControl.focus();
|
||||
await el.updateComplete;
|
||||
expect(el.checkValidity()).to.be.false
|
||||
expect(el.checkValidity()).to.be.false;
|
||||
|
||||
await aTimeout(10)
|
||||
await sendKeys({ type: "1234" })
|
||||
await el.updateComplete
|
||||
await aTimeout(10)
|
||||
await aTimeout(10);
|
||||
await sendKeys({ type: '1234' });
|
||||
await el.updateComplete;
|
||||
await aTimeout(10);
|
||||
|
||||
// For some reason this is only required in Safari.
|
||||
if (isSafari) {
|
||||
el.setCustomValidity("")
|
||||
el.setCustomValidity('');
|
||||
}
|
||||
|
||||
expect(el.checkValidity()).to.be.true
|
||||
})
|
||||
expect(el.checkValidity()).to.be.true;
|
||||
});
|
||||
|
||||
runFormControlBaseTests('wa-input');
|
||||
});
|
||||
|
||||
@@ -55,17 +55,15 @@ import type { CSSResultGroup } from 'lit';
|
||||
* @cssproperty --border-width - The width of the input's borders. Expects a single value.
|
||||
* @cssproperty --box-shadow - The shadow effects around the edges of the input.
|
||||
*/
|
||||
@customElement("wa-input")
|
||||
@customElement('wa-input')
|
||||
export default class WaInput extends WebAwesomeFormAssociated {
|
||||
static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
|
||||
|
||||
static get validators () {
|
||||
return [
|
||||
MirrorValidator()
|
||||
]
|
||||
static get validators() {
|
||||
return [MirrorValidator()];
|
||||
}
|
||||
|
||||
assumeInteractionOn = ['wa-blur', 'wa-input']
|
||||
assumeInteractionOn = ['wa-blur', 'wa-input'];
|
||||
private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
@@ -97,10 +95,10 @@ export default class WaInput extends WebAwesomeFormAssociated {
|
||||
@property() name = '';
|
||||
|
||||
/** The current value of the input, submitted as a name/value pair with form data. */
|
||||
@property({attribute: false}) value = '';
|
||||
@property({ attribute: false }) value = '';
|
||||
|
||||
/** The default value of the form control. Primarily used for resetting the form control. */
|
||||
@property({attribute: "value", reflect: true}) defaultValue = '';
|
||||
@property({ attribute: 'value', reflect: true }) defaultValue = '';
|
||||
|
||||
/** The input's size. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
@@ -143,7 +141,7 @@ export default class WaInput extends WebAwesomeFormAssociated {
|
||||
* to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
|
||||
* the same document or shadow root for this to work.
|
||||
*/
|
||||
@property({ reflect: true }) form = null
|
||||
@property({ reflect: true }) form = null;
|
||||
|
||||
/** Makes the input a required field. */
|
||||
@property({ type: Boolean, reflect: true }) required = false;
|
||||
@@ -279,7 +277,7 @@ export default class WaInput extends WebAwesomeFormAssociated {
|
||||
// See https://github.com/shoelace-style/shoelace/pull/988
|
||||
//
|
||||
if (!event.defaultPrevented && !event.isComposing) {
|
||||
this.getForm()?.requestSubmit(null)
|
||||
this.getForm()?.requestSubmit(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -294,7 +292,7 @@ export default class WaInput extends WebAwesomeFormAssociated {
|
||||
// If step changes, the value may become invalid so we need to recheck after the update. We set the new step
|
||||
// imperatively so we don't have to wait for the next render to report the updated validity.
|
||||
this.input.step = String(this.step);
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
/** Sets focus on the input. */
|
||||
@@ -361,19 +359,19 @@ export default class WaInput extends WebAwesomeFormAssociated {
|
||||
}
|
||||
}
|
||||
|
||||
formStateRestoreCallback (...args: Parameters<WebAwesomeFormAssociated["formStateRestoreCallback"]>) {
|
||||
const [value, reason] = args
|
||||
super.formStateRestoreCallback(value, reason)
|
||||
formStateRestoreCallback(...args: Parameters<WebAwesomeFormAssociated['formStateRestoreCallback']>) {
|
||||
const [value, reason] = args;
|
||||
super.formStateRestoreCallback(value, reason);
|
||||
|
||||
/** @ts-expect-error Type widening issue due to what a formStateRestoreCallback can accept. */
|
||||
this.input.value = value
|
||||
this.input.value = value;
|
||||
}
|
||||
|
||||
formResetCallback () {
|
||||
formResetCallback() {
|
||||
this.input.value = this.defaultValue;
|
||||
this.value = this.defaultValue;
|
||||
|
||||
super.formResetCallback()
|
||||
super.formResetCallback();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { LitElement } from 'lit';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import { WebAwesomeFormAssociated } from '../../internal/webawesome-element.js';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
@@ -30,7 +30,7 @@ import type { CSSResultGroup } from 'lit';
|
||||
* @csspart label - The container that wraps the radio button's label.
|
||||
* @csspart suffix - The container that wraps the suffix.
|
||||
*/
|
||||
@customElement("wa-radio-button")
|
||||
@customElement('wa-radio-button')
|
||||
export default class WaRadioButton extends WebAwesomeFormAssociated {
|
||||
static styles: CSSResultGroup = [componentStyles, styles];
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class WaRadioButton extends WebAwesomeFormAssociated {
|
||||
* it easier to style in button groups.
|
||||
*/
|
||||
@property({ type: Boolean, reflect: true }) checked = false;
|
||||
@property({ type: Boolean, attribute: "default-checked" }) defaultChecked = false;
|
||||
@property({ type: Boolean, attribute: 'default-checked' }) defaultChecked = false;
|
||||
|
||||
/** The radio's value. When selected, the radio group will receive this value. */
|
||||
@property({ attribute: false }) value: string;
|
||||
@@ -66,10 +66,10 @@ export default class WaRadioButton extends WebAwesomeFormAssociated {
|
||||
/**
|
||||
* The string pointing to a form's id.
|
||||
*/
|
||||
@property({ reflect: true }) form: string | null = null
|
||||
@property({ reflect: true }) form: string | null = null;
|
||||
|
||||
/** Needed for Form Validation. Without it we get a console error. */
|
||||
static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }
|
||||
static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
@@ -151,7 +151,6 @@ export default class WaRadioButton extends WebAwesomeFormAssociated {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'wa-radio-button': WaRadioButton;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { aTimeout, expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing';
|
||||
import { clickOnElement } from '../../internal/test.js';
|
||||
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
|
||||
import { sendKeys } from '@web/test-runner-commands';
|
||||
@@ -218,9 +218,9 @@ describe('when submitting a form', () => {
|
||||
const radio = form.querySelectorAll('wa-radio')[1];
|
||||
radio.click();
|
||||
|
||||
await form.querySelector("wa-radio-group")?.updateComplete
|
||||
await form.querySelector('wa-radio-group')?.updateComplete;
|
||||
|
||||
const formData = new FormData(form)
|
||||
const formData = new FormData(form);
|
||||
expect(formData.get('a')).to.equal('2');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import '../button-group/button-group.js';
|
||||
import '../radio/radio.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html, LitElement } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { RequiredValidator } from '../../internal/validators/required-validator.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import { WebAwesomeFormAssociated } from '../../internal/webawesome-element.js';
|
||||
@@ -38,13 +38,12 @@ import type WaRadioButton from '../radio-button/radio-button.js';
|
||||
* @csspart button-group - The button group that wraps radio buttons.
|
||||
* @csspart button-group__base - The button group's `base` part.
|
||||
*/
|
||||
@customElement('wa-radio-group')
|
||||
export default class WaRadioGroup extends WebAwesomeFormAssociated {
|
||||
static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
|
||||
|
||||
static get validators () {
|
||||
return [
|
||||
RequiredValidator()
|
||||
]
|
||||
static get validators() {
|
||||
return [RequiredValidator()];
|
||||
}
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
|
||||
@@ -66,7 +65,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociated {
|
||||
@property({ reflect: true }) name = null;
|
||||
|
||||
@property({ attribute: false }) value: string | null = null;
|
||||
@property({ attribute: "value", reflect: true }) defaultValue: string | null = null;
|
||||
@property({ attribute: 'value', reflect: true }) defaultValue: string | null = null;
|
||||
|
||||
/** The radio group's size. This size will be applied to all child radios and radio buttons. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
@@ -78,29 +77,33 @@ export default class WaRadioGroup extends WebAwesomeFormAssociated {
|
||||
* We need this because if we don't have it, FormValidation yells at us that it's "not focusable".
|
||||
* If we use `this.tabIndex = -1` we can't focus the radio inside.
|
||||
*/
|
||||
static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }
|
||||
static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.addEventListener("keydown", this.handleKeyDown)
|
||||
this.addEventListener('keydown', this.handleKeyDown);
|
||||
this.addEventListener('click', this.handleRadioClick);
|
||||
}
|
||||
|
||||
private handleRadioClick = (e: Event) => {
|
||||
const clickedRadio = (e.target as HTMLElement).closest<WaRadio | WaRadioButton>("wa-radio, wa-radio-button")
|
||||
const clickedRadio = (e.target as HTMLElement).closest<WaRadio | WaRadioButton>('wa-radio, wa-radio-button');
|
||||
|
||||
if (!clickedRadio) return
|
||||
if (clickedRadio.disabled) { return }
|
||||
if (!clickedRadio) return;
|
||||
if (clickedRadio.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldValue = this.value
|
||||
this.value = clickedRadio.value
|
||||
const oldValue = this.value;
|
||||
this.value = clickedRadio.value;
|
||||
clickedRadio.checked = true;
|
||||
|
||||
const radios = this.getAllRadios()
|
||||
const radios = this.getAllRadios();
|
||||
const hasButtonGroup = radios.some(radio => radio.tagName.toLowerCase() === 'wa-radio-button');
|
||||
for (const radio of radios) {
|
||||
if (clickedRadio === radio) { continue }
|
||||
if (clickedRadio === radio) {
|
||||
continue;
|
||||
}
|
||||
|
||||
radio.checked = false;
|
||||
|
||||
@@ -115,7 +118,6 @@ export default class WaRadioGroup extends WebAwesomeFormAssociated {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private getAllRadios() {
|
||||
return [...this.querySelectorAll<WaRadio | WaRadioButton>('wa-radio, wa-radio-button')];
|
||||
}
|
||||
@@ -141,9 +143,9 @@ export default class WaRadioGroup extends WebAwesomeFormAssociated {
|
||||
radio.size = this.size;
|
||||
|
||||
if (!radio.disabled && radio.value === this.value) {
|
||||
radio.checked = true
|
||||
radio.checked = true;
|
||||
} else {
|
||||
radio.checked = false
|
||||
radio.checked = false;
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -195,13 +197,13 @@ export default class WaRadioGroup extends WebAwesomeFormAssociated {
|
||||
* We use the first available radio as the validationTarget similar to native HTML that shows the validation popup on
|
||||
* the first radio element.
|
||||
*/
|
||||
get validationTarget () {
|
||||
get validationTarget() {
|
||||
return this.querySelector<WaRadio | WaRadioButton>(':is(wa-radio, wa-radio-button):not([disabled])') || undefined;
|
||||
}
|
||||
|
||||
@watch("value")
|
||||
handleValueChange () {
|
||||
this.syncRadioElements()
|
||||
@watch('value')
|
||||
handleValueChange() {
|
||||
this.syncRadioElements();
|
||||
}
|
||||
|
||||
@watch('size', { waitUntilFirstUpdate: true })
|
||||
@@ -209,12 +211,12 @@ export default class WaRadioGroup extends WebAwesomeFormAssociated {
|
||||
this.syncRadios();
|
||||
}
|
||||
|
||||
formResetCallback (...args: Parameters<WebAwesomeFormAssociated["formResetCallback"]>) {
|
||||
this.value = this.defaultValue
|
||||
formResetCallback(...args: Parameters<WebAwesomeFormAssociated['formResetCallback']>) {
|
||||
this.value = this.defaultValue;
|
||||
|
||||
super.formResetCallback(...args)
|
||||
super.formResetCallback(...args);
|
||||
|
||||
this.syncRadioElements()
|
||||
this.syncRadioElements();
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent) {
|
||||
@@ -222,19 +224,21 @@ export default class WaRadioGroup extends WebAwesomeFormAssociated {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
event.preventDefault();
|
||||
|
||||
const radios = this.getAllRadios().filter(radio => !radio.disabled);
|
||||
|
||||
if (radios.length <= 0) { return }
|
||||
if (radios.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldValue = this.value
|
||||
const oldValue = this.value;
|
||||
|
||||
const checkedRadio = radios.find(radio => radio.checked) ?? radios[0];
|
||||
const incr = event.key === ' ' ? 0 : ['ArrowUp', 'ArrowLeft'].includes(event.key) ? -1 : 1;
|
||||
let index = radios.indexOf(checkedRadio) + incr;
|
||||
|
||||
if (!index) index = 0
|
||||
if (!index) index = 0;
|
||||
|
||||
if (index < 0) {
|
||||
index = radios.length - 1;
|
||||
@@ -254,7 +258,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociated {
|
||||
}
|
||||
});
|
||||
|
||||
this.value = radios[index].value
|
||||
this.value = radios[index].value;
|
||||
radios[index].checked = true;
|
||||
|
||||
if (!hasButtonGroup) {
|
||||
@@ -277,9 +281,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociated {
|
||||
const hasHelpTextSlot = this.hasSlotController.test('help-text');
|
||||
const hasLabel = this.label ? true : !!hasLabelSlot;
|
||||
const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
|
||||
const defaultSlot = html`
|
||||
<slot @slotchange=${this.syncRadios}></slot>
|
||||
`;
|
||||
const defaultSlot = html` <slot @slotchange=${this.syncRadios}></slot> `;
|
||||
|
||||
return html`
|
||||
<fieldset
|
||||
@@ -331,3 +333,8 @@ export default class WaRadioGroup extends WebAwesomeFormAssociated {
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'wa-radio-group': WaRadioGroup;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import '../icon/icon.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { html } from 'lit';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import { WebAwesomeFormAssociated } from '../../internal/webawesome-element.js';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
@@ -38,7 +38,7 @@ import type { CSSResultGroup } from 'lit';
|
||||
* @cssproperty --checked-icon-scale - The size of the checked icon relative to the radio.
|
||||
* @cssproperty --toggle-size - The size of the radio.
|
||||
*/
|
||||
@customElement("wa-radio")
|
||||
@customElement('wa-radio')
|
||||
export default class WaRadio extends WebAwesomeFormAssociated {
|
||||
static styles: CSSResultGroup = [componentStyles, styles];
|
||||
|
||||
@@ -48,11 +48,11 @@ export default class WaRadio extends WebAwesomeFormAssociated {
|
||||
/**
|
||||
* The string pointing to a form's id.
|
||||
*/
|
||||
@property({ reflect: true }) form: string | null = null
|
||||
@property({ reflect: true }) form: string | null = null;
|
||||
|
||||
/** The radio's value. When selected, the radio group will receive this value. */
|
||||
@property({ attribute: false }) value: string;
|
||||
@property({ reflect: true, attribute: "value" }) defaultValue: string = ""
|
||||
@property({ reflect: true, attribute: 'value' }) defaultValue: string = '';
|
||||
|
||||
/**
|
||||
* The radio's size. When used inside a radio group, the size will be determined by the radio group's size so this
|
||||
@@ -86,14 +86,14 @@ export default class WaRadio extends WebAwesomeFormAssociated {
|
||||
|
||||
private setInitialAttributes() {
|
||||
this.setAttribute('role', 'radio');
|
||||
this.tabIndex = 0
|
||||
this.tabIndex = 0;
|
||||
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
|
||||
}
|
||||
|
||||
@watch('checked')
|
||||
handleCheckedChange() {
|
||||
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
||||
this.tabIndex = this.checked ? 0 : -1
|
||||
this.tabIndex = this.checked ? 0 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,4 +148,3 @@ declare global {
|
||||
'wa-radio': WaRadio;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,14 +43,12 @@ import type { CSSResultGroup } from 'lit';
|
||||
* @cssproperty --track-height - The height of the track.
|
||||
* @cssproperty --track-active-offset - The point of origin of the active track.
|
||||
*/
|
||||
@customElement("wa-range")
|
||||
@customElement('wa-range')
|
||||
export default class WaRange extends WebAwesomeFormAssociated {
|
||||
static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
|
||||
|
||||
static get validators () {
|
||||
return [
|
||||
MirrorValidator()
|
||||
]
|
||||
static get validators() {
|
||||
return [MirrorValidator()];
|
||||
}
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
|
||||
@@ -71,7 +69,7 @@ export default class WaRange extends WebAwesomeFormAssociated {
|
||||
@property({ attribute: false, type: Number }) value = 0;
|
||||
|
||||
/** The default value of the form control. Primarily used for resetting the form control. */
|
||||
@property({ type: Number, attribute: "value", reflect: true }) defaultValue = 0;
|
||||
@property({ type: Number, attribute: 'value', reflect: true }) defaultValue = 0;
|
||||
|
||||
/** The range's label. If you need to display HTML, use the `label` slot instead. */
|
||||
@property() label = '';
|
||||
@@ -191,7 +189,7 @@ export default class WaRange extends WebAwesomeFormAssociated {
|
||||
// min, max, and step properly
|
||||
this.input.value = this.value.toString();
|
||||
this.value = parseFloat(this.input.value);
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
|
||||
this.syncRange();
|
||||
}
|
||||
|
||||
@@ -118,9 +118,9 @@ describe('<wa-select>', () => {
|
||||
</wa-select>
|
||||
`);
|
||||
|
||||
expect(el.value).to.equal("option-1")
|
||||
expect(el.defaultValue).to.equal("option-1")
|
||||
expect(el.displayInput.value).to.equal("Option 1")
|
||||
expect(el.value).to.equal('option-1');
|
||||
expect(el.defaultValue).to.equal('option-1');
|
||||
expect(el.displayInput.value).to.equal('Option 1');
|
||||
|
||||
const secondOption = el.querySelectorAll<WaOption>('wa-option')[1];
|
||||
const changeHandler = sinon.spy();
|
||||
|
||||
@@ -3,11 +3,11 @@ import '../popup/popup.js';
|
||||
import '../tag/tag.js';
|
||||
import { animateTo, stopAnimations } from '../../internal/animate.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { RequiredValidator } from '../../internal/validators/required-validator.js';
|
||||
import { scrollIntoView } from '../../internal/scroll.js';
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
@@ -74,16 +74,14 @@ import type WaPopup from '../popup/popup.js';
|
||||
* @cssproperty --border-width - The width of the select's borders, including the listbox.
|
||||
* @cssproperty --box-shadow - The shadow effects around the edges of the select's combobox.
|
||||
*/
|
||||
@customElement("wa-select")
|
||||
@customElement('wa-select')
|
||||
export default class WaSelect extends WebAwesomeFormAssociated {
|
||||
static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
|
||||
|
||||
assumeInteractionOn =['wa-blur', 'wa-input']
|
||||
assumeInteractionOn = ['wa-blur', 'wa-input'];
|
||||
|
||||
static get validators () {
|
||||
return [
|
||||
RequiredValidator()
|
||||
]
|
||||
static get validators() {
|
||||
return [RequiredValidator()];
|
||||
}
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
|
||||
@@ -99,8 +97,8 @@ export default class WaSelect extends WebAwesomeFormAssociated {
|
||||
@query('.select__listbox') listbox: HTMLSlotElement;
|
||||
|
||||
/** Where to anchor native constraint validation */
|
||||
get validationTarget () {
|
||||
return this.valueInput
|
||||
get validationTarget() {
|
||||
return this.valueInput;
|
||||
}
|
||||
|
||||
@state() private hasFocus = false;
|
||||
@@ -122,29 +120,31 @@ export default class WaSelect extends WebAwesomeFormAssociated {
|
||||
private _defaultValue: string | string[] = '';
|
||||
|
||||
@property({
|
||||
attribute: "value",
|
||||
attribute: 'value',
|
||||
reflect: true,
|
||||
converter: {
|
||||
fromAttribute: (value: string) => value.split(" "),
|
||||
toAttribute: (value: string | string[]) => Array.isArray(value) ? value.join(' ') : value
|
||||
fromAttribute: (value: string) => value.split(' '),
|
||||
toAttribute: (value: string | string[]) => (Array.isArray(value) ? value.join(' ') : value)
|
||||
}
|
||||
})
|
||||
// @ts-expect-error defaultValue () is a property on the host, but is being used a getter / setter here.
|
||||
set defaultValue(val: string | string[]) {
|
||||
// For some reason this can go off before we've fully updated. So check the attribute too.
|
||||
const isMultiple = this.multiple || this.hasAttribute("multiple")
|
||||
const isMultiple = this.multiple || this.hasAttribute('multiple');
|
||||
|
||||
if (!isMultiple && Array.isArray(val)) {
|
||||
val = val.join(" ")
|
||||
val = val.join(' ');
|
||||
}
|
||||
this._defaultValue = val
|
||||
this._defaultValue = val;
|
||||
|
||||
if (!this.hasInteracted) {
|
||||
this.value = this.defaultValue
|
||||
if (!this.hasInteracted) {
|
||||
this.value = this.defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
get defaultValue() { return this._defaultValue; }
|
||||
get defaultValue() {
|
||||
return this._defaultValue;
|
||||
}
|
||||
|
||||
/** The select's size. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
@@ -235,12 +235,11 @@ export default class WaSelect extends WebAwesomeFormAssociated {
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
if (!this.hasInteracted) {
|
||||
this.value = this.defaultValue
|
||||
if (!this.hasInteracted) {
|
||||
this.value = this.defaultValue;
|
||||
}
|
||||
})
|
||||
});
|
||||
// Because this is a form control, it shouldn't be opened initially
|
||||
this.open = false;
|
||||
}
|
||||
@@ -627,7 +626,7 @@ export default class WaSelect extends WebAwesomeFormAssociated {
|
||||
|
||||
// Update validity
|
||||
this.updateComplete.then(() => {
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
});
|
||||
}
|
||||
protected get tags() {
|
||||
@@ -643,7 +642,7 @@ export default class WaSelect extends WebAwesomeFormAssociated {
|
||||
return html`<wa-tag>+${this.selectedOptions.length - index}</wa-tag>`;
|
||||
}
|
||||
return html``;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@watch('disabled', { waitUntilFirstUpdate: true })
|
||||
@@ -662,7 +661,7 @@ export default class WaSelect extends WebAwesomeFormAssociated {
|
||||
|
||||
// Select only the options that match the new value
|
||||
this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
@watch('open', { waitUntilFirstUpdate: true })
|
||||
@@ -740,10 +739,10 @@ export default class WaSelect extends WebAwesomeFormAssociated {
|
||||
this.displayInput.blur();
|
||||
}
|
||||
|
||||
formResetCallback () {
|
||||
formResetCallback() {
|
||||
this.value = this.defaultValue;
|
||||
super.formResetCallback()
|
||||
this.handleValueChange()
|
||||
super.formResetCallback();
|
||||
this.handleValueChange();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { live } from 'lit/directives/live.js';
|
||||
import { MirrorValidator } from '../../internal/validators/mirror-validator.js';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import { WebAwesomeFormAssociated } from '../../internal/webawesome-element.js';
|
||||
import componentStyles from '../../styles/component.styles.js';
|
||||
@@ -41,14 +41,12 @@ import type { CSSResultGroup } from 'lit';
|
||||
* @cssproperty --border-width - The width of the textarea's borders.
|
||||
* @cssproperty --box-shadow - The shadow effects around the edges of the textarea.
|
||||
*/
|
||||
@customElement("wa-textarea")
|
||||
@customElement('wa-textarea')
|
||||
export default class WaTextarea extends WebAwesomeFormAssociated {
|
||||
static formAssociated = true;
|
||||
static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
|
||||
static get validators() {
|
||||
return [
|
||||
MirrorValidator()
|
||||
];
|
||||
return [MirrorValidator()];
|
||||
}
|
||||
|
||||
assumeInteractionOn = ['wa-blur', 'wa-input'];
|
||||
@@ -146,12 +144,12 @@ export default class WaTextarea extends WebAwesomeFormAssociated {
|
||||
@property() inputmode: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';
|
||||
|
||||
/** The default value of the form control. Primarily used for resetting the form control. */
|
||||
@property({ reflect: true, attribute: "value" }) defaultValue: string = ''
|
||||
@property({ reflect: true, attribute: 'value' }) defaultValue: string = '';
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.value = this.defaultValue
|
||||
this.value = this.defaultValue;
|
||||
this.resizeObserver = new ResizeObserver(() => this.setTextareaHeight());
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
@@ -266,19 +264,19 @@ export default class WaTextarea extends WebAwesomeFormAssociated {
|
||||
}
|
||||
}
|
||||
|
||||
formStateRestoreCallback (...args: Parameters<WebAwesomeFormAssociated["formStateRestoreCallback"]>) {
|
||||
const [value, reason] = args
|
||||
super.formStateRestoreCallback(value, reason)
|
||||
formStateRestoreCallback(...args: Parameters<WebAwesomeFormAssociated['formStateRestoreCallback']>) {
|
||||
const [value, reason] = args;
|
||||
super.formStateRestoreCallback(value, reason);
|
||||
|
||||
/** @ts-expect-error Type widening issue due to what a formStateRestoreCallback can accept. */
|
||||
this.input.value = value
|
||||
this.input.value = value;
|
||||
}
|
||||
|
||||
formResetCallback () {
|
||||
formResetCallback() {
|
||||
this.input.value = this.defaultValue;
|
||||
this.value = this.defaultValue;
|
||||
|
||||
super.formResetCallback()
|
||||
super.formResetCallback();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -83,7 +83,7 @@ export class FormControlController implements ReactiveController {
|
||||
|
||||
return input.closest('form');
|
||||
},
|
||||
name: input => input.name || "",
|
||||
name: input => input.name || '',
|
||||
value: input => input.value,
|
||||
defaultValue: input => input.defaultValue,
|
||||
disabled: input => input.disabled ?? false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { sendMouse } from '@web/test-runner-commands';
|
||||
|
||||
export const isSafari = navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('HeadlessChrome')
|
||||
export const isSafari = navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('HeadlessChrome');
|
||||
|
||||
function determineMousePosition(el: Element, position: string, offsetX: number, offsetY: number) {
|
||||
const { x, y, width, height } = el.getBoundingClientRect();
|
||||
|
||||
@@ -4,74 +4,73 @@ import type { Validator } from '../webawesome-element.js';
|
||||
// https://codepen.io/paramagicdev/pen/eYorwrz
|
||||
export const GroupRequiredValidator = (): Validator => {
|
||||
const obj: Validator = {
|
||||
observedAttributes: ["required"],
|
||||
message (element) {
|
||||
const tagName = element.tagName.toLowerCase()
|
||||
if (tagName === "wa-checkbox") {
|
||||
return "Please check this box if you want to proceed" // @TODO: Add a translation.
|
||||
observedAttributes: ['required'],
|
||||
message(element) {
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
if (tagName === 'wa-checkbox') {
|
||||
return 'Please check this box if you want to proceed'; // @TODO: Add a translation.
|
||||
}
|
||||
if (tagName === "wa-radio") {
|
||||
return "Please select one of these options" // @TODO: Add a translation.
|
||||
if (tagName === 'wa-radio') {
|
||||
return 'Please select one of these options'; // @TODO: Add a translation.
|
||||
}
|
||||
|
||||
return "Please provide select a value for this group" // Not sure what to do here?
|
||||
return 'Please provide select a value for this group'; // Not sure what to do here?
|
||||
},
|
||||
checkValidity (this: typeof GroupRequiredValidator, element) {
|
||||
const validity: ReturnType<Validator["checkValidity"]> = {
|
||||
message: "",
|
||||
checkValidity(this: typeof GroupRequiredValidator, element) {
|
||||
const validity: ReturnType<Validator['checkValidity']> = {
|
||||
message: '',
|
||||
isValid: true,
|
||||
invalidKeys: []
|
||||
}
|
||||
};
|
||||
|
||||
const markInvalid = () => {
|
||||
validity.message = typeof obj.message === "function" ? obj.message(element) : (obj.message || "")
|
||||
validity.isValid = false
|
||||
validity.invalidKeys.push("valueMissing")
|
||||
}
|
||||
validity.message = typeof obj.message === 'function' ? obj.message(element) : obj.message || '';
|
||||
validity.isValid = false;
|
||||
validity.invalidKeys.push('valueMissing');
|
||||
};
|
||||
|
||||
const isRequired = element.required ?? element.hasAttribute("required")
|
||||
const isRequired = element.required ?? element.hasAttribute('required');
|
||||
|
||||
// Always valid if the element isn't required.
|
||||
// Always valid if no name.
|
||||
if (!isRequired) {
|
||||
return validity
|
||||
return validity;
|
||||
}
|
||||
|
||||
// If there's no name, we just check if the individual element has a value.
|
||||
if (!element.name || !element.getAttribute("name")) {
|
||||
const value = element.value
|
||||
if (!element.name || !element.getAttribute('name')) {
|
||||
const value = element.value;
|
||||
|
||||
let isEmpty = !value
|
||||
let isEmpty = !value;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
isEmpty = value.length === 0
|
||||
isEmpty = value.length === 0;
|
||||
}
|
||||
|
||||
if (isEmpty) {
|
||||
markInvalid()
|
||||
markInvalid();
|
||||
}
|
||||
|
||||
return validity
|
||||
return validity;
|
||||
}
|
||||
|
||||
const form = element.getForm()
|
||||
const form = element.getForm();
|
||||
|
||||
// Can't evaluate if there is no form.
|
||||
if (!form) {
|
||||
return validity
|
||||
return validity;
|
||||
}
|
||||
|
||||
|
||||
const formDataValue = new FormData(form).get(element.name)
|
||||
const formDataValue = new FormData(form).get(element.name);
|
||||
|
||||
// Can't do !formDataValue because we don't want "false" to trigger. False could technically be valid.
|
||||
if (formDataValue === null || formDataValue === undefined || formDataValue === "") {
|
||||
markInvalid()
|
||||
if (formDataValue === null || formDataValue === undefined || formDataValue === '') {
|
||||
markInvalid();
|
||||
}
|
||||
|
||||
return validity
|
||||
return validity;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return obj
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
@@ -16,12 +16,12 @@ export const MirrorValidator = (): Validator => {
|
||||
};
|
||||
|
||||
if (!formControl) {
|
||||
return validity
|
||||
return validity;
|
||||
}
|
||||
|
||||
let isValid = true
|
||||
let isValid = true;
|
||||
|
||||
if ("checkValidity" in formControl) {
|
||||
if ('checkValidity' in formControl) {
|
||||
isValid = formControl.checkValidity();
|
||||
}
|
||||
|
||||
@@ -31,15 +31,14 @@ export const MirrorValidator = (): Validator => {
|
||||
|
||||
validity.isValid = false;
|
||||
|
||||
if ("validationMessage" in formControl) {
|
||||
if ('validationMessage' in formControl) {
|
||||
validity.message = formControl.validationMessage;
|
||||
}
|
||||
|
||||
|
||||
// For some reason formControl doesn't have "validity", so chalk it up to customError
|
||||
if (!("validity" in formControl)) {
|
||||
validity.invalidKeys.push("customError");
|
||||
return validity
|
||||
if (!('validity' in formControl)) {
|
||||
validity.invalidKeys.push('customError');
|
||||
return validity;
|
||||
}
|
||||
|
||||
for (const key in formControl.validity) {
|
||||
@@ -56,5 +55,5 @@ export const MirrorValidator = (): Validator => {
|
||||
|
||||
return validity;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,39 +2,39 @@ import type { Validator } from '../webawesome-element.js';
|
||||
|
||||
export const RequiredValidator = (): Validator => {
|
||||
const obj: Validator = {
|
||||
observedAttributes: ["required"],
|
||||
message: "Please fill out this field", // @TODO: Add a translation.
|
||||
checkValidity (element) {
|
||||
const validity: ReturnType<Validator["checkValidity"]> = {
|
||||
message: "",
|
||||
observedAttributes: ['required'],
|
||||
message: 'Please fill out this field', // @TODO: Add a translation.
|
||||
checkValidity(element) {
|
||||
const validity: ReturnType<Validator['checkValidity']> = {
|
||||
message: '',
|
||||
isValid: true,
|
||||
invalidKeys: []
|
||||
}
|
||||
};
|
||||
|
||||
const isRequired = element.required ?? element.hasAttribute("required")
|
||||
const isRequired = element.required ?? element.hasAttribute('required');
|
||||
|
||||
// Always true if the element isn't required.
|
||||
if (!isRequired) {
|
||||
return validity
|
||||
return validity;
|
||||
}
|
||||
|
||||
const value = element.value
|
||||
const value = element.value;
|
||||
|
||||
let isEmpty = !value
|
||||
let isEmpty = !value;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
isEmpty = value.length === 0
|
||||
isEmpty = value.length === 0;
|
||||
}
|
||||
|
||||
if (isEmpty) {
|
||||
validity.message = typeof obj.message === "function" ? obj.message(element) : (obj.message || "")
|
||||
validity.isValid = false
|
||||
validity.invalidKeys.push("valueMissing")
|
||||
validity.message = typeof obj.message === 'function' ? obj.message(element) : obj.message || '';
|
||||
validity.isValid = false;
|
||||
validity.invalidKeys.push('valueMissing');
|
||||
}
|
||||
|
||||
return validity
|
||||
return validity;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return obj
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
@@ -94,16 +94,14 @@ export default class WebAwesomeElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
export interface Validator<
|
||||
T extends WebAwesomeFormAssociated = WebAwesomeFormAssociated
|
||||
> {
|
||||
export interface Validator<T extends WebAwesomeFormAssociated = WebAwesomeFormAssociated> {
|
||||
observedAttributes?: string[];
|
||||
checkValidity: (element: T) => {
|
||||
message: string;
|
||||
isValid: boolean;
|
||||
invalidKeys: Exclude<keyof ValidityState, 'valid'>[];
|
||||
};
|
||||
message?: (string | ((element: T) => string));
|
||||
message?: string | ((element: T) => string);
|
||||
}
|
||||
|
||||
export interface WebAwesomeFormControl extends WebAwesomeElement {
|
||||
@@ -184,7 +182,7 @@ export class WebAwesomeFormAssociated
|
||||
assumeInteractionOn: string[] = ['wa-input'];
|
||||
|
||||
// Additional
|
||||
formControl?: HTMLElement & {value: unknown} | HTMLInputElement | HTMLTextAreaElement;
|
||||
formControl?: (HTMLElement & { value: unknown }) | HTMLInputElement | HTMLTextAreaElement;
|
||||
|
||||
validators: Validator[] = [];
|
||||
|
||||
@@ -193,7 +191,7 @@ export class WebAwesomeFormAssociated
|
||||
@property({ state: true }) hasInteracted: boolean = false;
|
||||
|
||||
// This works around a limitation in Safari. It is a hacky way for us to preserve customErrors generated by the user.
|
||||
__manualCustomError = false
|
||||
__manualCustomError = false;
|
||||
|
||||
private emittedEvents: string[] = [];
|
||||
|
||||
@@ -203,22 +201,21 @@ export class WebAwesomeFormAssociated
|
||||
try {
|
||||
this.internals = this.attachInternals();
|
||||
} catch (_e) {
|
||||
console.error('Element internals are not supported in your browser. Consider using a polyfill');
|
||||
// console.error('Element internals are not supported in your browser. Consider using a polyfill');
|
||||
}
|
||||
|
||||
const ctor = this.constructor as typeof LitElement;
|
||||
|
||||
if (ctor.properties?.disabled?.reflect === true) {
|
||||
console.warn(`The following element has their "disabled" property set to reflect.`);
|
||||
console.warn(this);
|
||||
console.warn('For further reading: https://github.com/whatwg/html/issues/8365');
|
||||
// console.warn(`The following element has their "disabled" property set to reflect.`);
|
||||
// console.warn(this);
|
||||
// console.warn('For further reading: https://github.com/whatwg/html/issues/8365');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
|
||||
// eslint-disable-next-line
|
||||
this.addEventListener('invalid', this.emitInvalid);
|
||||
@@ -229,9 +226,9 @@ export class WebAwesomeFormAssociated
|
||||
});
|
||||
}
|
||||
|
||||
firstUpdated (...args: Parameters<LitElement["firstUpdated"]>) {
|
||||
super.firstUpdated(...args)
|
||||
this.updateValidity()
|
||||
firstUpdated(...args: Parameters<LitElement['firstUpdated']>) {
|
||||
super.firstUpdated(...args);
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
emitInvalid = (e: Event) => {
|
||||
@@ -241,27 +238,25 @@ export class WebAwesomeFormAssociated
|
||||
};
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("defaultValue")) {
|
||||
if (!this.hasInteracted){
|
||||
this.value = this.defaultValue
|
||||
if (changedProperties.has('defaultValue')) {
|
||||
if (!this.hasInteracted) {
|
||||
this.value = this.defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
changedProperties.has('value')
|
||||
) {
|
||||
if (changedProperties.has('value')) {
|
||||
if (this.hasInteracted && this.value !== this.defaultValue) {
|
||||
this.valueHasChanged = true
|
||||
this.valueHasChanged = true;
|
||||
}
|
||||
|
||||
const value = this.value
|
||||
const value = this.value;
|
||||
|
||||
// Accounts for the snowflake case on `<wa-select>`
|
||||
if (Array.isArray(value)) {
|
||||
if (this.name) {
|
||||
const formData = new FormData()
|
||||
const formData = new FormData();
|
||||
for (const val of value) {
|
||||
formData.append(this.name, val as string)
|
||||
formData.append(this.name, val as string);
|
||||
}
|
||||
this.setValue(formData, formData);
|
||||
}
|
||||
@@ -270,7 +265,7 @@ export class WebAwesomeFormAssociated
|
||||
}
|
||||
}
|
||||
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
super.willUpdate(changedProperties);
|
||||
}
|
||||
|
||||
@@ -335,10 +330,10 @@ export class WebAwesomeFormAssociated
|
||||
|
||||
this.internals.setValidity(flags, message, anchor || undefined);
|
||||
|
||||
this.setCustomStates()
|
||||
this.setCustomStates();
|
||||
}
|
||||
|
||||
setCustomStates () {
|
||||
setCustomStates() {
|
||||
const required = Boolean(this.required);
|
||||
const isValid = this.internals.validity.valid;
|
||||
const hasInteracted = this.hasInteracted;
|
||||
@@ -358,17 +353,17 @@ export class WebAwesomeFormAssociated
|
||||
*/
|
||||
setCustomValidity(message: string) {
|
||||
if (!message) {
|
||||
this.__manualCustomError = false
|
||||
this.__manualCustomError = false;
|
||||
this.setValidity({});
|
||||
return;
|
||||
}
|
||||
|
||||
this.__manualCustomError = true
|
||||
this.__manualCustomError = true;
|
||||
this.setValidity({ customError: true }, message, this.validationTarget);
|
||||
}
|
||||
|
||||
formResetCallback() {
|
||||
this.resetValidity()
|
||||
this.resetValidity();
|
||||
this.hasInteracted = false;
|
||||
this.valueHasChanged = false;
|
||||
this.emittedEvents = [];
|
||||
@@ -378,20 +373,20 @@ export class WebAwesomeFormAssociated
|
||||
formDisabledCallback(isDisabled: boolean) {
|
||||
this.disabled = isDisabled;
|
||||
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the browser is trying to restore element’s state to state in which case reason is “restore”, or when the browser is trying to fulfill autofill on behalf of user in which case reason is “autocomplete”. In the case of “restore”, state is a string, File, or FormData object previously set as the second argument to setFormValue.
|
||||
*/
|
||||
formStateRestoreCallback(state: string | File | FormData | null, reason: "autocomplete" | "restore") {
|
||||
this.value = state
|
||||
formStateRestoreCallback(state: string | File | FormData | null, reason: 'autocomplete' | 'restore') {
|
||||
this.value = state;
|
||||
|
||||
if (reason === "restore") {
|
||||
this.resetValidity()
|
||||
if (reason === 'restore') {
|
||||
this.resetValidity();
|
||||
}
|
||||
|
||||
this.updateValidity()
|
||||
this.updateValidity();
|
||||
}
|
||||
|
||||
setValue(...args: Parameters<typeof this.internals.setFormValue>) {
|
||||
@@ -410,18 +405,18 @@ export class WebAwesomeFormAssociated
|
||||
/**
|
||||
* Reset validity is a way of removing manual custom errors and native validation.
|
||||
*/
|
||||
resetValidity () {
|
||||
this.setCustomValidity("")
|
||||
this.setValidity({})
|
||||
resetValidity() {
|
||||
this.setCustomValidity('');
|
||||
this.setValidity({});
|
||||
}
|
||||
|
||||
updateValidity() {
|
||||
if (
|
||||
this.disabled
|
||||
|| this.hasAttribute('disabled')
|
||||
|| !this.willValidate //
|
||||
this.disabled ||
|
||||
this.hasAttribute('disabled') ||
|
||||
!this.willValidate //
|
||||
) {
|
||||
this.resetValidity()
|
||||
this.resetValidity();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user