diff --git a/package-lock.json b/package-lock.json
index b17d7c469..907810aa1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -31479,7 +31479,7 @@
},
"packages/muon": {
"name": "@muonic/muon",
- "version": "0.0.2-beta.53",
+ "version": "0.0.2-beta.54",
"license": "ISC",
"dependencies": {
"@open-wc/building-rollup": "3.0.2",
diff --git a/packages/muon/components/inputter/src/inputter-component.js b/packages/muon/components/inputter/src/inputter-component.js
index e45ad3b55..d0a44e30c 100644
--- a/packages/muon/components/inputter/src/inputter-component.js
+++ b/packages/muon/components/inputter/src/inputter-component.js
@@ -98,6 +98,29 @@ export class Inputter extends ScopedElementsMixin(ValidationMixin(MaskMixin(Muon
this._helperId = `${this._randomId}-helper`;
}
+ willUpdate(changedProperties) {
+ super.willUpdate(changedProperties);
+
+ let validationEle = this.querySelector(`#${this._id}-validation`);
+ if (!validationEle) {
+ validationEle = document.createElement('div');
+ validationEle.setAttribute('class', 'visually-hidden');
+ validationEle.setAttribute('id', `${this._id}-validation`);
+ this.appendChild(validationEle);
+ }
+ const slottedInput = this._slottedInputs[0];
+ if (this._shouldShowValidation) {
+ validationEle.setAttribute('aria-live', 'polite');
+ slottedInput?.setAttribute('aria-errormessage', `${this._id}-validation`);
+ slottedInput?.setAttribute('aria-invalid', 'true');
+ validationEle.textContent = `${this._isMultiple ? this.heading : this._slottedLabel?.textContent} ${this.validationMessage}`;
+ } else {
+ slottedInput?.removeAttribute('aria-errormessage');
+ slottedInput?.removeAttribute('aria-invalid');
+ validationEle.textContent = '';
+ }
+ }
+
_onChange(changeEvent) {
this._pristine = false;
changeEvent.stopPropagation();
@@ -211,6 +234,24 @@ export class Inputter extends ScopedElementsMixin(ValidationMixin(MaskMixin(Muon
return false;
}
+ get _multiInputHeading() {
+ return html`
+
+ `;
+ }
+
+ get __wrapperContent() {
+ return html`
+ ${this._isMultiple ? this._multiInputHeading : this._addLabel}
+ ${this._addHelper}
+
+ ${super.standardTemplate}
+ ${this._addMask}
+ ${this._addInputTypeIcon}
+
+ `;
+ }
+
/**
* Getter method to construct template for type `standard`.
* @protected
@@ -220,13 +261,11 @@ export class Inputter extends ScopedElementsMixin(ValidationMixin(MaskMixin(Muon
return html`
${this._addValidationIcon}
-
-
${this._isMultiple ? this.heading : this._slottedLabel?.textContent}
+
${this.validationMessage}
`;
diff --git a/packages/muon/tests/components/inputter/__snapshots__/inputter.test.snap.js b/packages/muon/tests/components/inputter/__snapshots__/inputter.test.snap.js
index 7d51902ec..5c80980f7 100644
--- a/packages/muon/tests/components/inputter/__snapshots__/inputter.test.snap.js
+++ b/packages/muon/tests/components/inputter/__snapshots__/inputter.test.snap.js
@@ -140,39 +140,51 @@ snapshots["Inputter text mask & validation"] =
snapshots["Inputter radio standard radio"] =
`
`;
/* end snapshot Inputter radio standard radio */
snapshots["Inputter radio radio mask"] =
`
`;
/* end snapshot Inputter radio radio mask */
snapshots["Inputter radio radio mask validation"] =
`
`;
/* end snapshot Inputter radio radio mask validation */
diff --git a/packages/muon/tests/components/inputter/inputter.test.js b/packages/muon/tests/components/inputter/inputter.test.js
index e8a43b01f..ed489efe1 100644
--- a/packages/muon/tests/components/inputter/inputter.test.js
+++ b/packages/muon/tests/components/inputter/inputter.test.js
@@ -177,8 +177,13 @@ describe('Inputter', () => {
const validationMessage = shadowRoot.querySelector('.validation .message');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Length must be between 8 and 20 characters.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Length must be between 8 and 20 characters.', 'validation message has correct value');
+ const validationId = `${inputter._id}-validation`;
+ const validationLightDOM = inputter.querySelector(`#${validationId}`);
+ // eslint-disable-next-line no-unused-expressions
+ expect(validationLightDOM).to.be.ok;
+ expect(inputElement.getAttribute('aria-errormessage')).to.be.equal(validationId);
const validationIcon = shadowRoot.querySelector('.validation .icon');
expect(validationIcon).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationIcon.name).to.equal('exclamation-circle', 'validation icon has correct value');
@@ -210,7 +215,7 @@ describe('Inputter', () => {
const validationMessage = shadowRoot.querySelector('.validation .message');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Length must be between 8 and 20 characters.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Length must be between 8 and 20 characters.', 'validation message has correct value');
const validationIcon = shadowRoot.querySelector('.validation .icon');
expect(validationIcon).to.not.be.null; // eslint-disable-line no-unused-expressions
@@ -251,7 +256,7 @@ describe('Inputter', () => {
const validationMessage = shadowRoot.querySelector('.validation .message');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
const validationIcon = shadowRoot.querySelector('.validation .icon');
expect(validationIcon).to.not.be.null; // eslint-disable-line no-unused-expressions
@@ -309,7 +314,7 @@ describe('Inputter', () => {
const validationMessage = shadowRoot.querySelector('.validation .message');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
const validationIcon = shadowRoot.querySelector('.validation .icon');
expect(validationIcon).to.not.be.null; // eslint-disable-line no-unused-expressions
@@ -319,7 +324,7 @@ describe('Inputter', () => {
await inputter.updateComplete;
expect(changeEventSpy.callCount).to.equal(3, '`change` event fired');
expect(changeEventSpy.lastCall.args[0].detail.value).to.equal('12-3', '`change` event has value `12-3`');
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Length must be at least 4 characters.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Length must be at least 4 characters.', 'validation message has correct value');
inputMask = shadowRoot.querySelector('.input-mask');
expect(inputMask.textContent).to.be.equal(' 0', '`input-mask` has correct value');
@@ -415,7 +420,7 @@ describe('Inputter', () => {
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent?.trim().replace(/\s\s+/g, ' ')).to.equal('What is your heating source? This field is required.', 'validation message has correct value');
+ expect(validationMessage.textContent?.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
const validationIcon = shadowRoot.querySelector('.validation .icon');
expect(validationIcon).to.not.be.null; // eslint-disable-line no-unused-expressions
diff --git a/packages/muon/tests/mixins/validation.test.js b/packages/muon/tests/mixins/validation.test.js
index 56344bb86..f65e8a5e2 100644
--- a/packages/muon/tests/mixins/validation.test.js
+++ b/packages/muon/tests/mixins/validation.test.js
@@ -103,7 +103,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
let validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
await fillIn(inputElement, 'hello world');
expect(formElement.value).to.equal('hello world', '`value` property has value `hello world`');
@@ -113,7 +113,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Length must be between 5 and 10 characters.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Length must be between 5 and 10 characters.', 'validation message has correct value');
});
it('text validation on input', async () => {
@@ -147,7 +147,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
let validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
await fillIn(inputElement, 'hello world', 'input');
expect(formElement.value).to.equal('hello world', '`value` property has value `hello world`');
@@ -157,7 +157,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Length must be between 5 and 10 characters.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Length must be between 5 and 10 characters.', 'validation message has correct value');
});
it('text native validation', async () => {
@@ -191,7 +191,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
let validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ').toLowerCase()).contains('input label this field is required', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ').toLowerCase()).contains('this field is required', 'validation message has correct value');
await fillIn(inputElement, 'test validation');
expect(formElement.value).to.equal('test validation', '`value` property has value `test validation`');
@@ -234,7 +234,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
let validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
await fillIn(inputElement, '56');
expect(formElement.value).to.equal('56', '`value` property has value `56`');
@@ -244,7 +244,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label match the pattern.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('match the pattern.', 'validation message has correct value');
});
@@ -309,7 +309,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
const validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('What is your heating source? This field is required.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
});
it('checkbox validation', async () => {
@@ -341,7 +341,7 @@ describe('form-element-validation', () => {
expect(changeEventSpy.callCount).to.equal(1, '`change` event fired');
const validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('What is your heating source? This field is required.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
});
it('select validation', async () => {
@@ -373,7 +373,7 @@ describe('form-element-validation', () => {
expect(changeEventSpy.callCount).to.equal(1, '`change` event fired');
const validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('What is your heating source? This field is required.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
});
it('date validation', async () => {
@@ -408,7 +408,7 @@ describe('form-element-validation', () => {
let validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
await fillIn(inputElement, '10/11/2021');
await formElement.updateComplete;
@@ -418,6 +418,6 @@ describe('form-element-validation', () => {
validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
- expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Date must be on or after 11/11/2021.', 'validation message has correct value');
+ expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Date must be on or after 11/11/2021.', 'validation message has correct value');
});
});