Skip to content

Commit

Permalink
Merge pull request #156 from alexpdraper/define-inherited-attrs
Browse files Browse the repository at this point in the history
Check for inherited attrs in initializeAttrs
  • Loading branch information
koddsson authored Aug 5, 2021
2 parents a5d05b9 + 359d361 commit b49f128
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 6 deletions.
20 changes: 16 additions & 4 deletions src/attr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function attr<K extends string>(proto: Record<K, attrValue>, key: K): voi
* `@controller` decorator it should not call this manually.
*/
export function initializeAttrs(instance: HTMLElement, names?: Iterable<string>): void {
if (!names) names = attrs.get(Object.getPrototypeOf(instance)) || []
if (!names) names = getAttrNames(Object.getPrototypeOf(instance))
for (const key of names) {
const value = (<Record<PropertyKey, unknown>>(<unknown>instance))[key]
const name = attrToAttributeName(key)
Expand Down Expand Up @@ -73,6 +73,19 @@ export function initializeAttrs(instance: HTMLElement, names?: Iterable<string>)
}
}

function getAttrNames(classObjectProto: Record<PropertyKey, unknown>): Set<string> {
const names: Set<string> = new Set()
let proto: Record<PropertyKey, unknown> | typeof HTMLElement = classObjectProto

while (proto && proto !== HTMLElement) {
const attrNames = attrs.get(<Record<PropertyKey, unknown>>proto) || []
for (const name of attrNames) names.add(name)
proto = Object.getPrototypeOf(proto)
}

return names
}

function attrToAttributeName(name: string): string {
return `data-${name.replace(/([A-Z]($|[a-z]))/g, '-$1')}`.replace(/--/g, '-').toLowerCase()
}
Expand All @@ -81,9 +94,8 @@ export function defineObservedAttributes(classObject: CustomElement): void {
let observed = classObject.observedAttributes || []
Object.defineProperty(classObject, 'observedAttributes', {
get() {
const attrMap = attrs.get(classObject.prototype)
if (!attrMap) return observed
return attrMap.map(attrToAttributeName).concat(observed)
const attrMap = getAttrNames(classObject.prototype)
return [...attrMap].map(attrToAttributeName).concat(observed)
},
set(attributes: string[]) {
observed = attributes
Expand Down
30 changes: 28 additions & 2 deletions test/attr.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,15 @@ describe('initializeAttrs', () => {

describe('attr', () => {
class AttrTestElement extends HTMLElement {}
attr(AttrTestElement.prototype, 'foo')
attr(AttrTestElement.prototype, 'bar')
window.customElements.define('attr-test-element', AttrTestElement)

class ExtendedAttrTestElement extends AttrTestElement {}
attr(ExtendedAttrTestElement.prototype, 'baz')
window.customElements.define('extended-attr-test-element', ExtendedAttrTestElement)

it('populates the "default" list for initializeAttrs', () => {
attr(AttrTestElement.prototype, 'foo')
attr(AttrTestElement.prototype, 'bar')
const instance = document.createElement('attr-test-element')
instance.foo = 'hello'
initializeAttrs(instance)
Expand All @@ -169,6 +173,20 @@ describe('attr', () => {
expect(instance.getAttribute('data-foo')).to.equal('hello')
expect(instance.getAttribute('data-bar')).to.equal('')
})

it('includes attrs from extended elements', () => {
const instance = document.createElement('extended-attr-test-element')
instance.bar = 'hello'
instance.baz = 'world'
initializeAttrs(instance)
expect(instance).to.have.property('foo', '')
expect(instance).to.have.property('bar', 'hello')
expect(instance).to.have.property('baz', 'world')
expect(instance.getAttributeNames()).to.eql(['data-baz', 'data-foo', 'data-bar'])
expect(instance.getAttribute('data-foo')).to.equal('')
expect(instance.getAttribute('data-bar')).to.equal('hello')
expect(instance.getAttribute('data-baz')).to.equal('world')
})
})

describe('defineObservedAttributes', () => {
Expand Down Expand Up @@ -200,4 +218,12 @@ describe('defineObservedAttributes', () => {
TestElement.observedAttributes = ['a', 'b', 'c']
expect(TestElement.observedAttributes).to.eql(['data-foo', 'a', 'b', 'c'])
})

it('will reflect values from extended elements', () => {
class TestElement extends HTMLElement {}
class ExtendedTestElement extends TestElement {}
defineObservedAttributes(ExtendedTestElement)
attr(TestElement.prototype, 'foo')
expect(ExtendedTestElement.observedAttributes).to.eql(['data-foo'])
})
})

0 comments on commit b49f128

Please sign in to comment.