Skip to content

Commit 4cc4631

Browse files
feat(validation anchor): add validation anchor behavior
1 parent 0f27d3f commit 4cc4631

7 files changed

+63
-38
lines changed

Diff for: karma.conf.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ module.exports = config => {
2626
usePhantomJS: false,
2727
preferHeadless: true,
2828
postDetection(availableBrowsers) {
29-
return availableBrowsers.filter(browser => browser !== 'SafariTechPreview');
29+
console.log(availableBrowsers)
30+
return availableBrowsers.filter(browser => !['ChromeCanaryHeadless', 'SafariTechPreview'].includes(browser));
3031
}
3132
},
3233

Diff for: package-lock.json

+21-32
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"rollup-plugin-commonjs": "^10.1.0",
6969
"rollup-plugin-livereload": "^1.2.0",
7070
"rollup-plugin-serve": "^1.0.1",
71-
"sinon": "^9.0.2",
71+
"sinon": "^9.2.4",
7272
"standard-version": "^9.0.0",
7373
"tsc": "^1.20150623.0",
7474
"typescript": "^4.0.3"

Diff for: src/element-internals.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
formElementsMap,
88
refValueMap,
99
hiddenInputMap,
10-
shadowRootMap
10+
shadowRootMap,
11+
validationAnchorMap
1112
} from './maps';
1213
import { initAom } from './aom';
1314
import { getHostRoot, initRef, initLabels, initForm, findParentForm, createHiddenInput, removeHiddenInputs } from './utils';
@@ -114,7 +115,17 @@ export class ElementInternals implements IElementInternals {
114115

115116
/** Will report the elements validity state */
116117
reportValidity(): boolean {
117-
return this.checkValidity();
118+
const valid = this.checkValidity();
119+
const anchor = validationAnchorMap.get(this);
120+
const ref = refMap.get(this);
121+
if (anchor && !ref.constructor['formAssociated']) {
122+
throw new DOMException(`Failed to execute 'setValidity' on 'ElementInternals': The target element is not a form-associated custom element.`);
123+
}
124+
if (!valid && anchor) {
125+
ref.focus();
126+
anchor.focus();
127+
}
128+
return valid;
118129
}
119130

120131
/** Sets the element's value within the form */
@@ -153,6 +164,7 @@ export class ElementInternals implements IElementInternals {
153164
if (!validityChanges) {
154165
throw new TypeError('Failed to execute \'setValidity\' on \'ElementInternals\': 1 argument required, but only 0 present.');
155166
}
167+
validationAnchorMap.set(this, anchor);
156168
const validity = validityMap.get(this);
157169
if (Object.keys(validityChanges).length === 0) {
158170
setValid(validity);

Diff for: src/maps.ts

+3
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,6 @@ export const upgradeMap = new WeakMap<Element, IElementInternals>();
3737

3838
/** Save references to shadow roots for inclusion in internals instance */
3939
export const shadowRootMap = new WeakMap<ICustomElement, ShadowRoot>();
40+
41+
/** Save a refernce to the internals' validation anchor */
42+
export const validationAnchorMap = new WeakMap<IElementInternals, HTMLElement>();

Diff for: test/ElementInternals.test.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@ class CustomElement extends HTMLElement {
1919
super();
2020
const root = this.attachShadow({ mode: 'open' });
2121
this.internals = this.attachInternals();
22-
root.innerHTML = 'html';
22+
root.innerHTML = '<input>';
2323

2424
this._value = '';
2525
}
2626

27+
connectedCallback() {
28+
this.tabIndex = -1;
29+
this.input = this.shadowRoot.querySelector('input');
30+
}
31+
2732
set disabled(disabled) {
2833
this._disabled = disabled;
2934
this.toggleAttribute('disabled', disabled);
@@ -340,6 +345,14 @@ describe('The ElementInternals polyfill', () => {
340345

341346
it('saves a reference to all shadow roots', () => {
342347
expect(internals.shadowRoot).to.equal(el.shadowRoot);
348+
});;
349+
350+
it('will focus the element if validated with anchor', async () => {
351+
internals.setValidity({
352+
customError: true
353+
}, 'Error message', el.input);
354+
internals.reportValidity();
355+
expect(document.activeElement).to.equal(el);
343356
});
344357
});
345358

Diff for: test/polyfilledBrowsers.test.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {
22
aTimeout,
3-
elementUpdated,
43
expect,
54
fixture,
65
fixtureCleanup,
76
html,
87
} from '@open-wc/testing';
8+
import { spy } from 'sinon';
99
import '../dist/index.js';
1010

1111
describe('ElementInternals polyfill behavior', () => {
@@ -60,6 +60,13 @@ describe('ElementInternals polyfill behavior', () => {
6060
super();
6161
this.internals = this.attachInternals();
6262
}
63+
64+
connectedCallback() {
65+
this.tabIndex = -1;
66+
const root = this.attachShadow({ mode: 'open' });
67+
root.innerHTML = `<input />`;
68+
this.input = root.querySelector('input');
69+
}
6370
}
6471

6572
customElements.define('form-associated', FormAssociated);

0 commit comments

Comments
 (0)