Skip to content

Commit

Permalink
feat(property): sync attribute value for primitive types
Browse files Browse the repository at this point in the history
  • Loading branch information
smalluban committed May 6, 2021
1 parent bb863c7 commit 4337203
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 5 deletions.
16 changes: 14 additions & 2 deletions docs/core-features/property.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,26 +82,38 @@ const MyElement = {

## Attribute Fallback

The property factory for all types except `object` and `undefined` creates fallback connection to attribute by the dashed version of the property key. The value from existing attribute is used **only once when an element connects for the first time to the document**.
The property factory for all types except `object` and `undefined` creates fallback connection to attribute by the dashed version of the property key. The value from existing attribute is used for setting the value **only once when an element connects for the first time to the document**. However, the primitive values (strings, numbers and booleans) are set back to the corresponding attribute when property changes. It means, that for those types, the attribute is in sync with the property (but not the opposite) - it allows using property values as host styling selectors.

Still, if you want to update property value you must assert new value to the property:

```javascript
const MyElement = {
someValue: 0,
render: () => html`
<slot></slot>
`.css`
:host([some-value="100"]) { color: red }
`,
};
```

Attributes should be used only for setting static values in HTML templates:

```html
<my-element some-value="100"></my-element>
```

Attributes should be used to set static values in HTML templates:
Use property assertion to dynamically change the value:

```javascript
// WRONG - it won't change property value
myElement.setAttribute('some-value', 1000);

// CORRECT - it will change property value
myElement.someValue = 1000;

// after next RAF (by the observe() API) for primitive types
myElement.getAttribute("some-value") === 1000 // returns true
```

### Booleans
Expand Down
23 changes: 21 additions & 2 deletions src/property.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const objectTransform = value => {
};

export default function property(value, connect) {
const attrs = new WeakMap();
const type = typeof value;
let transform = defaultTransform;

Expand Down Expand Up @@ -37,12 +38,13 @@ export default function property(value, connect) {

return {
get: (host, val = value) => val,
set: (host, val, oldValue) => transform(val, oldValue),
set: (host, val, oldValue = value) => transform(val, oldValue),
connect:
type !== "object" && type !== "undefined"
? (host, key, invalidate) => {
if (host[key] === value) {
if (!attrs.has(host)) {
const attrName = camelToDash(key);
attrs.set(host, attrName);

if (host.hasAttribute(attrName)) {
const attrValue = host.getAttribute(attrName);
Expand All @@ -54,5 +56,22 @@ export default function property(value, connect) {
return connect && connect(host, key, invalidate);
}
: connect,
observe:
type !== "object" &&
type !== "undefined" &&
((host, val) => {
const attrName = attrs.get(host);

const attrValue = host.getAttribute(attrName);
const nextValue = val === true ? "" : val;

if (nextValue === attrValue) return;

if (!val) {
host.removeAttribute(attrName);
} else {
host.setAttribute(attrName, nextValue);
}
}),
};
}
15 changes: 14 additions & 1 deletion test/spec/property.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test } from "../helpers.js";
import { test, resolveRaf } from "../helpers.js";
import define from "../../src/define.js";
import property from "../../src/property.js";

Expand Down Expand Up @@ -84,6 +84,19 @@ describe("property:", () => {
expect(el.value).toBe("5");
}),
);

it("updates attribute value from primitive types", () =>
empty(el => {
el.stringProp = "test";
el.boolProp = true;
el.numberProp = 0;
return resolveRaf(() => {
expect(el.getAttribute("string-prop")).toBe("test");
expect(el.hasAttribute("bool-prop")).toBe(true);
expect(el.getAttribute("bool-prop")).toBe("");
expect(el.getAttribute("number-prop")).toBe("0");
});
}));
});

describe("string type", () => {
Expand Down

0 comments on commit 4337203

Please sign in to comment.