Skip to content

Commit d16d885

Browse files
authored
Merge pull request #191 from github/initialize-attrs-on-attributechangedcallback
Initialize attrs on attributechangedcallback
2 parents 18387a4 + 4ba1d72 commit d16d885

File tree

4 files changed

+58
-1
lines changed

4 files changed

+58
-1
lines changed

src/attr.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ export function attr<K extends string>(proto: Record<K, attrValue>, key: K): voi
3434
* This is automatically called as part of `@controller`. If a class uses the
3535
* `@controller` decorator it should not call this manually.
3636
*/
37+
const initialized = new WeakSet<Element>()
3738
export function initializeAttrs(instance: HTMLElement, names?: Iterable<string>): void {
39+
if (initialized.has(instance)) return
40+
initialized.add(instance)
3841
if (!names) names = getAttrNames(Object.getPrototypeOf(instance))
3942
for (const key of names) {
4043
const value = (<Record<PropertyKey, unknown>>(<unknown>instance))[key]

src/controller.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {initializeInstance, initializeClass} from './core.js'
1+
import {initializeInstance, initializeClass, initializeAttributeChanged} from './core.js'
22
import type {CustomElement} from './custom-element.js'
33
/**
44
* Controller is a decorator to be used over a class that extends HTMLElement.
@@ -11,5 +11,14 @@ export function controller(classObject: CustomElement): void {
1111
classObject.prototype.connectedCallback = function (this: HTMLElement) {
1212
initializeInstance(this, connect)
1313
}
14+
const attributeChanged = classObject.prototype.attributeChangedCallback
15+
classObject.prototype.attributeChangedCallback = function (
16+
this: HTMLElement,
17+
name: string,
18+
oldValue: unknown,
19+
newValue: unknown
20+
) {
21+
initializeAttributeChanged(this, name, oldValue, newValue, attributeChanged)
22+
}
1423
initializeClass(classObject)
1524
}

src/core.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ export function initializeInstance(instance: HTMLElement, connect?: (this: HTMLE
1717
if (instance.shadowRoot) bindShadow(instance.shadowRoot)
1818
}
1919

20+
export function initializeAttributeChanged(
21+
instance: HTMLElement,
22+
name: string,
23+
oldValue: unknown,
24+
newValue: unknown,
25+
attributeChangedCallback?: (this: HTMLElement, name: string, oldValue: unknown, newValue: unknown) => void
26+
): void {
27+
initializeAttrs(instance)
28+
if (name !== 'data-catalyst' && attributeChangedCallback) {
29+
attributeChangedCallback.call(instance, name, oldValue, newValue)
30+
}
31+
}
32+
2033
export function initializeClass(classObject: CustomElement): void {
2134
defineObservedAttributes(classObject)
2235
register(classObject)

test/controller.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {controller} from '../lib/controller.js'
2+
import {attr} from '../lib/attr.js'
23

34
describe('controller', () => {
45
let root
@@ -102,4 +103,35 @@ describe('controller', () => {
102103
// eslint-disable-next-line github/unescaped-html-literal
103104
root.innerHTML = '<parent-element><child-element></child-element></parent-element>'
104105
})
106+
107+
describe('attrs', () => {
108+
let attrValues = []
109+
class AttributeTestElement extends HTMLElement {
110+
foo = 'baz'
111+
attributeChangedCallback() {
112+
attrValues.push(this.getAttribute('data-foo'))
113+
attrValues.push(this.foo)
114+
}
115+
}
116+
controller(AttributeTestElement)
117+
attr(AttributeTestElement.prototype, 'foo')
118+
119+
beforeEach(() => {
120+
attrValues = []
121+
})
122+
123+
it('initializes attrs as attributes in attributeChangedCallback', () => {
124+
const el = document.createElement('attribute-test')
125+
el.foo = 'bar'
126+
el.attributeChangedCallback()
127+
expect(attrValues).to.eql(['bar', 'bar'])
128+
})
129+
130+
it('initializes attributes as attrs in attributeChangedCallback', () => {
131+
const el = document.createElement('attribute-test')
132+
el.setAttribute('data-foo', 'bar')
133+
el.attributeChangedCallback()
134+
expect(attrValues).to.eql(['bar', 'bar'])
135+
})
136+
})
105137
})

0 commit comments

Comments
 (0)