Skip to content

Commit

Permalink
[reactive-element] Bind this to custom attribute converter methods (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
nebarf committed Aug 5, 2022
1 parent ae6f680 commit 6361a4b
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changeset/old-tables-own.md
@@ -0,0 +1,6 @@
---
'@lit/reactive-element': patch
'lit': patch
---

Bind `this` to custom attribute converter methods
30 changes: 17 additions & 13 deletions packages/reactive-element/src/reactive-element.ts
Expand Up @@ -1083,10 +1083,12 @@ export abstract class ReactiveElement
this.constructor as typeof ReactiveElement
).__attributeNameForProperty(name, options);
if (attr !== undefined && options.reflect === true) {
const toAttribute =
(options.converter as ComplexAttributeConverter)?.toAttribute ??
defaultConverter.toAttribute;
const attrValue = toAttribute!(value, options.type);
const converter =
(options.converter as ComplexAttributeConverter)?.toAttribute !==
undefined
? (options.converter as ComplexAttributeConverter)
: defaultConverter;
const attrValue = converter.toAttribute!(value, options.type);
if (
DEV_MODE &&
(this.constructor as typeof ReactiveElement).enabledWarnings!.indexOf(
Expand Down Expand Up @@ -1131,17 +1133,19 @@ export abstract class ReactiveElement
// if it was just set because the attribute changed.
if (propName !== undefined && this.__reflectingProperty !== propName) {
const options = ctor.getPropertyOptions(propName);
const converter = options.converter;
const fromAttribute =
(converter as ComplexAttributeConverter)?.fromAttribute ??
(typeof converter === 'function'
? (converter as (value: string | null, type?: unknown) => unknown)
: null) ??
defaultConverter.fromAttribute;
const converter =
typeof options.converter === 'function'
? {fromAttribute: options.converter}
: options.converter?.fromAttribute !== undefined
? options.converter
: defaultConverter;
// mark state reflecting
this.__reflectingProperty = propName;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this[propName as keyof this] = fromAttribute!(value, options.type) as any;
this[propName as keyof this] = converter.fromAttribute!(
value,
options.type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any;
// mark state not reflecting
this.__reflectingProperty = null;
}
Expand Down
73 changes: 73 additions & 0 deletions packages/reactive-element/src/test/reactive-element_test.ts
Expand Up @@ -302,6 +302,79 @@ suite('ReactiveElement', () => {
assert.equal(el.getAttribute('foo'), 'toAttribute: FooType');
});

test('property option `converter` can use a class instance', async () => {
class IntegerAttributeConverter
implements ComplexAttributeConverter<Number>
{
private _defaultValue: Number;

constructor(defaultValue: Number) {
this._defaultValue = defaultValue;
}

toAttribute(value: Number, _type?: unknown): unknown {
if (!value) {
return this._defaultValue;
}
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);

el.num = undefined;
await el.updateComplete;
assert.equal(el.getAttribute('num'), `${defaultIntAttrConverterVal}`);
});

test('property/attribute values when attributes removed', async () => {
class E extends ReactiveElement {
static override get properties() {
Expand Down

0 comments on commit 6361a4b

Please sign in to comment.