element-internals-polyfill
Advanced tools
Comparing version 0.0.5 to 0.0.6
@@ -5,2 +5,9 @@ # Changelog | ||
### [0.0.6](https://github.com/calebdwilliams/element-internals-polyfill/compare/v0.0.5...v0.0.6) (2020-04-14) | ||
### Bug Fixes | ||
* **polyfill:** fix setValidity ([ee18f41](https://github.com/calebdwilliams/element-internals-polyfill/commit/ee18f4118b2d54ea482f6c7cdb4ae3ccb6ca6830)) | ||
### [0.0.5](https://github.com/calebdwilliams/element-internals-polyfill/compare/v0.0.4...v0.0.5) (2019-11-16) | ||
@@ -7,0 +14,0 @@ |
@@ -1,199 +0,234 @@ | ||
const refMap = new WeakMap(); | ||
const validityMap = new WeakMap(); | ||
const hiddenInputMap = new WeakMap(); | ||
const internalsMap = new WeakMap(); | ||
const validationMessageMap = new WeakMap(); | ||
(function () { | ||
'use strict'; | ||
const observerConfig = { attributes: true }; | ||
const refMap = new WeakMap(); | ||
const validityMap = new WeakMap(); | ||
const hiddenInputMap = new WeakMap(); | ||
const internalsMap = new WeakMap(); | ||
const validationMessageMap = new WeakMap(); | ||
const observer = new MutationObserver(mutationsList => { | ||
for (const mutation of mutationsList) { | ||
const { attributeName, target } = mutation; | ||
const observerConfig = { attributes: true }; | ||
if (attributeName === 'disabled' && target.constructor.formAssociated) { | ||
if (target.formDisabledCallback) { | ||
target.formDisabledCallback.bind(target)(); | ||
const observer = new MutationObserver(mutationsList => { | ||
for (const mutation of mutationsList) { | ||
const { attributeName, target } = mutation; | ||
if (attributeName === 'disabled' && target.constructor.formAssociated) { | ||
if (target.formDisabledCallback) { | ||
target.formDisabledCallback.apply(target); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
const getHostRoot = node => { | ||
if (node instanceof Document) { | ||
return node; | ||
} | ||
let parent = node.parentNode; | ||
if (parent && parent.toString() !== '[object ShadowRoot]') { | ||
parent = getHostRoot(parent); | ||
} | ||
return parent; | ||
}; | ||
const getHostRoot = node => { | ||
if (node instanceof Document) { | ||
return node; | ||
} | ||
let parent = node.parentNode; | ||
if (parent && parent.toString() !== '[object ShadowRoot]') { | ||
parent = getHostRoot(parent); | ||
} | ||
return parent; | ||
}; | ||
const initRef = (ref, internals) => { | ||
ref.toggleAttribute('form-associated-custom-element', true); | ||
const input = document.createElement('input'); | ||
input.type = 'hidden'; | ||
input.name = ref.getAttribute('name'); | ||
ref.after(input); | ||
hiddenInputMap.set(internals, input); | ||
return observer.observe(ref, observerConfig); | ||
}; | ||
const initRef = (ref, internals) => { | ||
ref.toggleAttribute('form-associated-custom-element', true); | ||
const input = document.createElement('input'); | ||
input.type = 'hidden'; | ||
input.name = ref.getAttribute('name'); | ||
ref.after(input); | ||
hiddenInputMap.set(internals, input); | ||
return observer.observe(ref, observerConfig); | ||
}; | ||
const initLabels = (ref, labels) => { | ||
Array.from(labels).forEach(label => | ||
label.addEventListener('click', ref.focus.bind(ref))); | ||
const firstLabelId = `${labels[0].htmlFor}_Label`; | ||
labels[0].id = firstLabelId; | ||
ref.setAttribute('aria-describedby', firstLabelId); | ||
}; | ||
const initLabels = (ref, labels) => { | ||
if (labels.length) { | ||
Array.from(labels).forEach(label => | ||
label.addEventListener('click', ref.focus.bind(ref))); | ||
const firstLabelId = `${labels[0].htmlFor}_Label`; | ||
labels[0].id = firstLabelId; | ||
ref.setAttribute('aria-describedby', firstLabelId); | ||
} | ||
}; | ||
const initForm = (ref, form, internals) => { | ||
form.addEventListener('submit', event => { | ||
if (internals.checkValidity() === false) { | ||
event.stopImmediatePropagation(); | ||
event.stopPropagation(); | ||
event.preventDefault(); | ||
const initForm = (ref, form, internals) => { | ||
if (form) { | ||
form.addEventListener('submit', event => { | ||
if (internals.checkValidity() === false) { | ||
event.stopImmediatePropagation(); | ||
event.stopPropagation(); | ||
event.preventDefault(); | ||
} | ||
}); | ||
form.addEventListener('reset', () => { | ||
if (ref.constructor.formAssociated && ref.formResetCallback) { | ||
ref.formResetCallback(); | ||
} | ||
}); | ||
} | ||
}); | ||
}; | ||
form.addEventListener('reset', () => { | ||
if (ref.constructor.formAssociated && ref.formResetCallback) { | ||
ref.formResetCallback(); | ||
const findParentForm = elem => { | ||
let parent = elem.parentNode; | ||
if (parent && parent.tagName !== 'FORM') { | ||
parent = findParentForm(parent); | ||
} else if (!parent && elem.toString() === '[object ShadowRoot]') { | ||
parent = findParentForm(parent.host); | ||
} | ||
}); | ||
}; | ||
return parent; | ||
}; | ||
const findParentForm = elem => { | ||
let parent = elem.parentNode; | ||
if (parent && parent.tagName !== 'FORM') { | ||
parent = findParentForm(parent); | ||
} else if (!parent && elem.toString() === '[object ShadowRoot]') { | ||
parent = findParentForm(parent.host); | ||
class ValidityState { | ||
constructor() { | ||
this.badInput = false; | ||
this.customError = false; | ||
this.patternMismatch = false; | ||
this.rangeOverflow = false; | ||
this.rangeUnderflow = false; | ||
this.stepMismatch = false; | ||
this.tooLong = false; | ||
this.tooShort = false; | ||
this.typeMismatch = false; | ||
this.valid = true; | ||
this.valueMissing = false; | ||
Object.seal(this); | ||
} | ||
} | ||
return parent; | ||
}; | ||
const setValid = validityObject => { | ||
validityObject.badInput = false; | ||
validityObject.customError = false; | ||
validityObject.patternMismatch = false; | ||
validityObject.rangeOverflow = false; | ||
validityObject.rangeUnderflow = false; | ||
validityObject.stepMismatch = false; | ||
validityObject.tooLong = false; | ||
validityObject.tooShort = false; | ||
validityObject.typeMismatch = false; | ||
validityObject.valid = true; | ||
validityObject.valueMissing = false; | ||
return validityObject; | ||
}; | ||
class ValidityState { | ||
constructor() { | ||
this.badInput = false; | ||
this.customError = false; | ||
this.patternMismatch = false; | ||
this.rangeOverflow = false; | ||
this.rangeUnderflow = false; | ||
this.stepMismatch = false; | ||
this.tooLong = false; | ||
this.tooShort = false; | ||
this.typeMismatch = false; | ||
this.valid = true; | ||
this.valueMissing = false; | ||
const reconcileValidty = (validityObject, newState) => { | ||
validityObject.valid = isValid(newState); | ||
Object.keys(newState).forEach(key => validityObject[key] = newState[key]); | ||
return validityObject; | ||
}; | ||
Object.seal(this); | ||
} | ||
} | ||
const isValid = validityState => { | ||
let valid = true; | ||
for (let key in validityState) { | ||
if (key !== 'valid' && validityState[key] !== false) { | ||
valid = false; | ||
} | ||
} | ||
return valid; | ||
}; | ||
class ElementInternals { | ||
constructor(ref) { | ||
const validity = new ValidityState(); | ||
refMap.set(this, ref); | ||
validityMap.set(this, validity); | ||
internalsMap.set(ref, this); | ||
const { labels, form } = this; | ||
Object.seal(this); | ||
class ElementInternals { | ||
constructor(ref) { | ||
if (!ref || !ref.tagName || ref.tagName.indexOf('-') === -1) { | ||
throw new TypeError('Illegal constructor'); | ||
} | ||
const validity = new ValidityState(); | ||
refMap.set(this, ref); | ||
validityMap.set(this, validity); | ||
internalsMap.set(ref, this); | ||
const { labels, form } = this; | ||
Object.seal(this); | ||
initRef(ref, this); | ||
initLabels(ref, labels); | ||
initForm(ref, form, this); | ||
} | ||
initRef(ref, this); | ||
initLabels(ref, labels); | ||
initForm(ref, form, this); | ||
} | ||
checkValidity() { | ||
const validity = validityMap.get(this); | ||
return validity.valid; | ||
} | ||
checkValidity() { | ||
const validity = validityMap.get(this); | ||
return validity.valid; | ||
} | ||
get form() { | ||
const ref = refMap.get(this); | ||
let form; | ||
if (ref && ref.constructor.formAssociated === true) { | ||
form = findParentForm(ref); | ||
get form() { | ||
const ref = refMap.get(this); | ||
let form; | ||
if (ref && ref.constructor.formAssociated === true) { | ||
form = findParentForm(ref); | ||
} | ||
return form; | ||
} | ||
return form; | ||
} | ||
get labels() { | ||
const ref = refMap.get(this); | ||
const id = ref.getAttribute('id'); | ||
const hostRoot = getHostRoot(ref); | ||
return hostRoot.querySelectorAll(`[for=${id}]`); | ||
} | ||
get labels() { | ||
const ref = refMap.get(this); | ||
const id = ref.getAttribute('id'); | ||
const hostRoot = getHostRoot(ref); | ||
return hostRoot.querySelectorAll(`[for=${id}]`); | ||
} | ||
reportValidity() { | ||
// TODO: Figure out how to polyfill this | ||
} | ||
reportValidity() { | ||
// TODO: Figure out how to polyfill this | ||
} | ||
setFormValue(value) { | ||
if (!this.form) { | ||
return; | ||
setFormValue(value) { | ||
if (!this.form) { | ||
return undefined; | ||
} | ||
const hiddenInput = hiddenInputMap.get(this); | ||
hiddenInput.value = value; | ||
} | ||
const hiddenInput = hiddenInputMap.get(this); | ||
hiddenInput.value = value; | ||
} | ||
setValidity(validityChanges = {}, validationMessage = '') { | ||
const validity = validityMap.get(this); | ||
if (Object.keys(validityChanges).length === 0) { | ||
console.log('yes'); | ||
validity.valid = true; | ||
for (const key in validity) { | ||
if (key !== 'valid') { | ||
validity[key] = false; | ||
} | ||
setValidity(validityChanges, validationMessage) { | ||
if (!validityChanges) { | ||
throw new TypeError('Failed to execute \'setValidity\' on \'ElementInternals\': 1 argument required, but only 0 present.'); | ||
} | ||
} else { | ||
for (const key in validityChanges) { | ||
if (validityChanges.hasOwnProperty(key)) { | ||
const value = validityChanges[key]; | ||
validity[key] = validityChanges[key]; | ||
const validity = validityMap.get(this); | ||
if (Object.keys(validityChanges).length === 0) { | ||
setValid(validity); | ||
} | ||
const check = { ...validity, ...validityChanges }; | ||
delete check.valid; | ||
const { valid } = reconcileValidty(validity, check); | ||
if (value === true && key !== 'valid') { | ||
validity.valid = false; | ||
} | ||
} | ||
if (!valid && !validationMessage) { | ||
throw new DOMException(`Failed to execute 'setValidity' on 'ElementInternals': The second argument should not be empty if one or more flags in the first argument are true.`); | ||
} | ||
validationMessageMap.set(this, valid ? '' : validationMessage); | ||
this.reportValidity(); | ||
} | ||
validationMessageMap.set(this, validationMessage); | ||
get validationMessage() { | ||
return validationMessageMap.get(this); | ||
} | ||
this.reportValidity(); | ||
} | ||
get validity() { | ||
const validity = validityMap.get(this); | ||
return validity; | ||
} | ||
get validationMessage() { | ||
return validationMessageMap.get(this); | ||
get willValidate() { | ||
const ref = refMap.get(this); | ||
if (ref.disabled || ref.hasAttribute('disabled')) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
} | ||
get validity() { | ||
const validity = validityMap.get(this); | ||
return validity; | ||
} | ||
if (!window.ElementInternals) { | ||
window.ElementInternals = ElementInternals; | ||
get willValidate() { | ||
const ref = refMap.get(this); | ||
if (ref.disabled || ref.hasAttribute('disabled')) { | ||
return false; | ||
} | ||
return true; | ||
Object.defineProperty(HTMLElement.prototype, 'attachInternals', { | ||
get() { | ||
return () => { | ||
if (this.tagName.indexOf('-') === -1) { | ||
throw new Error(`Failed to execute 'attachInternals' on 'HTMLElement': Unable to attach ElementInternals to non-custom elements.`); | ||
} | ||
return new ElementInternals(this); | ||
}; | ||
} | ||
}); | ||
} | ||
} | ||
if (!window.ElementInternals) { | ||
window.ElementInternals = ElementInternals; | ||
Object.defineProperty(HTMLElement.prototype, 'attachInternals', { | ||
get() { | ||
return () => { | ||
return new ElementInternals(this); | ||
}; | ||
} | ||
}); | ||
} | ||
export { ElementInternals }; | ||
}()); |
{ | ||
"name": "element-internals-polyfill", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"description": "A polyfill for the element internals specification", | ||
@@ -12,2 +12,4 @@ "main": "/dist/element-internals.js", | ||
"test": "karma start", | ||
"test:watch": "npm run test -- --watch", | ||
"test:coverage": "npm run test -- --coverage", | ||
"start": "rollup -c --watch --environment BUILD:dev", | ||
@@ -42,4 +44,23 @@ "build": "rollup -c --environment BUILD:production", | ||
"devDependencies": { | ||
"@babel/core": "^7.9.0", | ||
"@babel/preset-env": "^7.9.5", | ||
"@open-wc/testing-helpers": "^1.7.1", | ||
"@rollup/plugin-node-resolve": "^7.1.3", | ||
"babel-plugin-istanbul": "^6.0.0", | ||
"babel-plugin-transform-async-to-promises": "^0.8.15", | ||
"karma": "^5.0.1", | ||
"karma-chrome-launcher": "^3.1.0", | ||
"karma-coverage-istanbul-reporter": "^2.1.1", | ||
"karma-detect-browsers": "^2.3.3", | ||
"karma-edge-launcher": "^0.4.2", | ||
"karma-firefox-launcher": "^1.3.0", | ||
"karma-ie-launcher": "^1.0.0", | ||
"karma-jasmine": "^3.1.1", | ||
"karma-rollup-preprocessor": "^7.0.5", | ||
"karma-safari-launcher": "^1.0.0", | ||
"karma-safarinative-launcher": "^1.1.0", | ||
"rollup": "^1.27.0", | ||
"rollup-plugin-livereload": "^1.0.4", | ||
"rollup-plugin-babel": "^4.4.0", | ||
"rollup-plugin-commonjs": "^10.1.0", | ||
"rollup-plugin-livereload": "^1.2.0", | ||
"rollup-plugin-serve": "^1.0.1", | ||
@@ -46,0 +67,0 @@ "standard-version": "^7.0.0" |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
10617
204
23