From b015dcf1b49f0d6248440d1e9e8f8dce5190709a Mon Sep 17 00:00:00 2001 From: nebarf Date: Thu, 7 Jul 2022 08:49:51 +0200 Subject: [PATCH] Bind this to custom attribute converter methods --- .changeset/old-tables-own.md | 5 ++ .../reactive-element/src/reactive-element.ts | 10 ++- .../src/test/reactive-element_test.ts | 66 +++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 .changeset/old-tables-own.md diff --git a/.changeset/old-tables-own.md b/.changeset/old-tables-own.md new file mode 100644 index 0000000000..a9ee8e7451 --- /dev/null +++ b/.changeset/old-tables-own.md @@ -0,0 +1,5 @@ +--- +'@lit/reactive-element': patch +--- + +Bind this to custom attribute converter methods diff --git a/packages/reactive-element/src/reactive-element.ts b/packages/reactive-element/src/reactive-element.ts index a5d58f32f0..36f50ca2e1 100644 --- a/packages/reactive-element/src/reactive-element.ts +++ b/packages/reactive-element/src/reactive-element.ts @@ -1069,9 +1069,11 @@ export abstract class ReactiveElement this.constructor as typeof ReactiveElement ).__attributeNameForProperty(name, options); if (attr !== undefined && options.reflect === true) { + const converter = options.converter; const toAttribute = - (options.converter as ComplexAttributeConverter)?.toAttribute ?? - defaultConverter.toAttribute; + (converter as ComplexAttributeConverter)?.toAttribute?.bind( + converter + ) ?? defaultConverter.toAttribute; const attrValue = toAttribute!(value, options.type); if ( DEV_MODE && @@ -1119,7 +1121,9 @@ export abstract class ReactiveElement const options = ctor.getPropertyOptions(propName); const converter = options.converter; const fromAttribute = - (converter as ComplexAttributeConverter)?.fromAttribute ?? + (converter as ComplexAttributeConverter)?.fromAttribute?.bind( + converter + ) ?? (typeof converter === 'function' ? (converter as (value: string | null, type?: unknown) => unknown) : null) ?? diff --git a/packages/reactive-element/src/test/reactive-element_test.ts b/packages/reactive-element/src/test/reactive-element_test.ts index b998a24af9..aca7c53abf 100644 --- a/packages/reactive-element/src/test/reactive-element_test.ts +++ b/packages/reactive-element/src/test/reactive-element_test.ts @@ -302,6 +302,72 @@ suite('ReactiveElement', () => { assert.equal(el.getAttribute('foo'), 'toAttribute: FooType'); }); + test('property option `converter` can use a class instance', async () => { + class IntegerAttributeConverter + implements ComplexAttributeConverter + { + private _defaultValue: Number; + + constructor(defaultValue: Number) { + this._defaultValue = defaultValue; + } + + toAttribute(value: Number, _type?: unknown): unknown { + return `${value}`; + } + + fromAttribute(value: string | null, _type?: unknown): Number { + if (!value) { + return this._defaultValue; + } + + const parsedValue = Number.parseInt(value, 10); + if (isNaN(parsedValue)) { + return this._defaultValue; + } + return parsedValue; + } + } + + const defaultIntAttrConverterVal = 1; + + class E extends ReactiveElement { + static override get properties() { + return { + num: { + type: Number, + converter: new IntegerAttributeConverter( + defaultIntAttrConverterVal + ), + reflect: true, + }, + }; + } + + num?: number; + } + + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + + assert.equal(el.getAttribute('num'), null); + assert.equal(el.num, undefined); + + el.setAttribute('num', 'notANumber'); + await el.updateComplete; + assert.equal(el.num, defaultIntAttrConverterVal); + + el.num = 10; + await el.updateComplete; + assert.equal(el.getAttribute('num'), '10'); + + el.setAttribute('num', '5'); + await el.updateComplete; + assert.equal(el.num, 5); + }); + test('property/attribute values when attributes removed', async () => { class E extends ReactiveElement { static override get properties() {