Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/attr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export function attr<K extends string>(proto: Record<K, attrValue>, key: K): voi
* This is automatically called as part of `@controller`. If a class uses the
* `@controller` decorator it should not call this manually.
*/
const initialized = new WeakSet<Element>()
export function initializeAttrs(instance: HTMLElement, names?: Iterable<string>): void {
if (initialized.has(instance)) return
initialized.add(instance)
if (!names) names = getAttrNames(Object.getPrototypeOf(instance))
for (const key of names) {
const value = (<Record<PropertyKey, unknown>>(<unknown>instance))[key]
Expand Down
11 changes: 10 additions & 1 deletion src/controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {initializeInstance, initializeClass} from './core.js'
import {initializeInstance, initializeClass, initializeAttributeChanged} from './core.js'
import type {CustomElement} from './custom-element.js'
/**
* Controller is a decorator to be used over a class that extends HTMLElement.
Expand All @@ -11,5 +11,14 @@ export function controller(classObject: CustomElement): void {
classObject.prototype.connectedCallback = function (this: HTMLElement) {
initializeInstance(this, connect)
}
const attributeChanged = classObject.prototype.attributeChangedCallback
classObject.prototype.attributeChangedCallback = function (
this: HTMLElement,
name: string,
oldValue: unknown,
newValue: unknown
) {
initializeAttributeChanged(this, name, oldValue, newValue, attributeChanged)
}
initializeClass(classObject)
}
13 changes: 13 additions & 0 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ export function initializeInstance(instance: HTMLElement, connect?: (this: HTMLE
if (instance.shadowRoot) bindShadow(instance.shadowRoot)
}

export function initializeAttributeChanged(
instance: HTMLElement,
name: string,
oldValue: unknown,
newValue: unknown,
attributeChangedCallback?: (this: HTMLElement, name: string, oldValue: unknown, newValue: unknown) => void
): void {
initializeAttrs(instance)
if (name !== 'data-catalyst' && attributeChangedCallback) {
attributeChangedCallback.call(instance, name, oldValue, newValue)
}
}

export function initializeClass(classObject: CustomElement): void {
defineObservedAttributes(classObject)
register(classObject)
Expand Down
32 changes: 32 additions & 0 deletions test/controller.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {controller} from '../lib/controller.js'
import {attr} from '../lib/attr.js'

describe('controller', () => {
let root
Expand Down Expand Up @@ -102,4 +103,35 @@ describe('controller', () => {
// eslint-disable-next-line github/unescaped-html-literal
root.innerHTML = '<parent-element><child-element></child-element></parent-element>'
})

describe('attrs', () => {
let attrValues = []
class AttributeTestElement extends HTMLElement {
foo = 'baz'
attributeChangedCallback() {
attrValues.push(this.getAttribute('data-foo'))
attrValues.push(this.foo)
}
}
controller(AttributeTestElement)
attr(AttributeTestElement.prototype, 'foo')

beforeEach(() => {
attrValues = []
})

it('initializes attrs as attributes in attributeChangedCallback', () => {
const el = document.createElement('attribute-test')
el.foo = 'bar'
el.attributeChangedCallback()
expect(attrValues).to.eql(['bar', 'bar'])
})

it('initializes attributes as attrs in attributeChangedCallback', () => {
const el = document.createElement('attribute-test')
el.setAttribute('data-foo', 'bar')
el.attributeChangedCallback()
expect(attrValues).to.eql(['bar', 'bar'])
})
})
})