Skip to content
101 changes: 101 additions & 0 deletions examples/bugs/158-zombie/array.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<html>

<head>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<script type="module">
import {
TemplateElement,
defineElement,
html
} from "../../../index.js";
/*this works > 1.2.3*/

class OuterElement extends TemplateElement {
properties() {
return { selection: new Set([]) };
}

events() {
return {
button: {
click: (e) => {
if (this.selection.has(e.target.dataset.index)) {
this.selection.delete(e.target.dataset.index);
} else {
this.selection.add(e.target.dataset.index);
}
this.requestUpdate();
}
}
};
}

template() {
return html`
<div>
<nav class="border p-2 flex gap-2">
<button class="p-2 border border-red-500" data-index="d1">d1</button>
<button class="p-2 border border-red-500" data-index="d2">d2</button>
<button class="p-2 border border-red-500" data-index="d3">d3</button>
<button class="p-2 border border-red-500" data-index="d4">d4</button>
</nav>

<h2>set values:</h2>

<div class="border border-green p-2 flex gap-2">
${[...this.selection]?.join(",")}
</div>

<h2>zombies:</h2>
<div class="border border-green p-2 flex gap-2">
${[...this.selection].map(
(itemIndex) =>
html`
<zombie-element label="${itemIndex}"></zombie-element>
`
)}
</div>

<h2>dotted zombies:</h2>
<div class="border border-green p-2 flex gap-2">
${[...this.selection].map(
(itemIndex) =>
html`
<zombie-element
.label="${itemIndex}"
></zombie-element>
`
)}
</div>
</div>
`;
}
}

defineElement("outer-element", OuterElement);

class ZombieElement extends TemplateElement {
properties() {
return { label: "default", other: "other" };
}

template() {
return html`
<div class="text-blue-500 text-2xl">${this.label}</div>
`;
}
}

defineElement("zombie-element", ZombieElement);




</script>

<body>
<outer-element></outer-element>
</body>

</html>
110 changes: 110 additions & 0 deletions examples/bugs/158-zombie/properties.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script type="module">
import {
TemplateElement,
html,
defineElement,
} from '../../../index.js';

class MyElement extends TemplateElement {
properties() {
return {
items: null,
};
}

templateInner() {
if (!this.items) {
return null;
}
// WORKS
return html`
<inner-element id="regular" class="border border-green-500" items="${this.items}"></inner-element>
`;
}

templateInnerBroken() {
if (!this.items) {
// return html`<strong>fooobarrrrr</strong>`;
return null;
}
// DOES NOT WORK
return html`
<inner-element id="dot" .items="${this.items}" attr="${this.items}"></inner-element>
`;
}

template() {
return html`
<div>
<div>outer items: ${this.items?.toString()}</div>
<div class="border border-red-500 p-4 mt-4">
${this.templateInnerBroken()}
</div>
</div>
`;
}
}

defineElement('my-element', MyElement);

class InnerElement extends TemplateElement {
properties() {
return {
items: null,
attr: null,
};
}

connectedCallback() {
super.connectedCallback();
console.log('inner:connectedCallback', this.getAttribute('id'), this.items, this.ownerDocument);
}

connected() {
console.log('inner:connected', this.getAttribute('id'), this.items, this.ownerDocument);
}

disconnectedCallback() {
super.disconnectedCallback();
console.log('inner:disconnectedCallback', this.getAttribute('id'), this.items, this.ownerDocument);
}

disconnected() {
console.log('inner:disconnected', this.getAttribute('id'), this.items, this.ownerDocument);
}


template() {
return html`
<div class="border p-4">
${this.items?.toString()} <br>
${this.attr?.toString()}
</div>
`;
}
}

defineElement('inner-element', InnerElement);

setTimeout(() => {
//outer.items = [1, 2, 3, 4, 5];
outer.items = 'fooobar';
}, 1000);
// TODO this would also work ...
//outer.data = { items: [1, 2, 3, 4, 5] };
// outer.items = "fooobar";


</script>
</head>

<body>
<my-element id="outer"></my-element>
</body>


</html>

24 changes: 12 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 1 addition & 9 deletions src/BaseElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,6 @@ class BaseElement extends HTMLElement {
// release dom references
this.$refs = {};

// clear state
this._state = {};

// cancel pending updates
if (this._batchUpdate) {
cancelAnimationFrame(this._batchUpdate);
Expand Down Expand Up @@ -298,7 +295,7 @@ class BaseElement extends HTMLElement {
// registered or connected. To avoid such timing issues we check
// if a value was set for that specific property on the
// prototype before assigning a default value
const value = this[prop] || this.properties()[prop];
const value = this[prop] ?? this.properties()[prop];
this.defineProperty(prop, value);
});
}
Expand All @@ -312,11 +309,6 @@ class BaseElement extends HTMLElement {
* @param {boolean} isAttribute
*/
defineProperty(property, value, isAttribute = false) {
if (this._state.hasOwnProperty(property)) {
// property has already been defined as an attribute nothing to do here
return;
}

// if property did not come from an attribute but has the option to reflect // enabled or custom fn
if (!isAttribute && this._options.propertyOptions[property]?.reflect) {
this.reflectProperty({ property: property, newValue: value });
Expand Down
15 changes: 14 additions & 1 deletion test/unit/attributes-to-properties.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
import { fixture, defineCE, assert } from '@open-wc/testing';
import { BaseElement } from '../../src/BaseElement';

const tag = defineCE(class extends BaseElement {});
const tag = defineCE(
class extends BaseElement {
properties() {
return {
firstname: 'John',
};
}
},
);

describe('attributes-to-properties', () => {
it('reflects lowercase attributes as lowercase properties', async () => {
Expand All @@ -24,4 +32,9 @@ describe('attributes-to-properties', () => {
assert.isTrue(el.hasOwnProperty('dashCase'));
assert.equal(el.dashCase, 'Hello');
});

it('prefers attribute values over the default values from the properties map for default/initial state', async () => {
const el = await fixture(`<${tag} firstname="Jane"></${tag}>`);
assert.equal(el.firstname, 'Jane');
});
});