@joist/element 4.2.3-next.2 → 4.2.3-next.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/package.json +4 -2
  2. package/src/lib/attr.test.ts +69 -18
  3. package/src/lib/attr.ts +5 -6
  4. package/src/lib/element.test.ts +33 -25
  5. package/src/lib/element.ts +21 -27
  6. package/src/lib/listen.test.ts +75 -0
  7. package/src/lib/metadata.ts +5 -7
  8. package/target/lib/attr.d.ts +1 -1
  9. package/target/lib/attr.js +3 -4
  10. package/target/lib/attr.js.map +1 -1
  11. package/target/lib/attr.test.js +90 -17
  12. package/target/lib/attr.test.js.map +1 -1
  13. package/target/lib/element.js +16 -17
  14. package/target/lib/element.js.map +1 -1
  15. package/target/lib/element.test.js +68 -0
  16. package/target/lib/element.test.js.map +1 -1
  17. package/target/lib/listen.test.js +97 -0
  18. package/target/lib/listen.test.js.map +1 -1
  19. package/target/lib/metadata.d.ts +4 -2
  20. package/target/lib/metadata.js.map +1 -1
  21. package/src/lib/templating/README.md +0 -406
  22. package/src/lib/templating/bind.ts +0 -40
  23. package/src/lib/templating/define.ts +0 -5
  24. package/src/lib/templating/elements/async.element.test.ts +0 -90
  25. package/src/lib/templating/elements/async.element.ts +0 -122
  26. package/src/lib/templating/elements/for.element.test.ts +0 -221
  27. package/src/lib/templating/elements/for.element.ts +0 -189
  28. package/src/lib/templating/elements/if.element.test.ts +0 -90
  29. package/src/lib/templating/elements/if.element.ts +0 -93
  30. package/src/lib/templating/elements/props.element.test.ts +0 -62
  31. package/src/lib/templating/elements/props.element.ts +0 -80
  32. package/src/lib/templating/elements/scope.ts +0 -45
  33. package/src/lib/templating/elements/value.element.test.ts +0 -20
  34. package/src/lib/templating/elements/value.element.ts +0 -41
  35. package/src/lib/templating/events.ts +0 -21
  36. package/src/lib/templating/token.test.ts +0 -74
  37. package/src/lib/templating/token.ts +0 -34
  38. package/src/lib/templating.ts +0 -2
  39. package/target/lib/templating/bind.d.ts +0 -1
  40. package/target/lib/templating/bind.js +0 -30
  41. package/target/lib/templating/bind.js.map +0 -1
  42. package/target/lib/templating/define.d.ts +0 -5
  43. package/target/lib/templating/define.js +0 -6
  44. package/target/lib/templating/define.js.map +0 -1
  45. package/target/lib/templating/elements/async.element.d.ts +0 -17
  46. package/target/lib/templating/elements/async.element.js +0 -115
  47. package/target/lib/templating/elements/async.element.js.map +0 -1
  48. package/target/lib/templating/elements/async.element.test.d.ts +0 -1
  49. package/target/lib/templating/elements/async.element.test.js +0 -75
  50. package/target/lib/templating/elements/async.element.test.js.map +0 -1
  51. package/target/lib/templating/elements/for.element.d.ts +0 -24
  52. package/target/lib/templating/elements/for.element.js +0 -186
  53. package/target/lib/templating/elements/for.element.js.map +0 -1
  54. package/target/lib/templating/elements/for.element.test.d.ts +0 -2
  55. package/target/lib/templating/elements/for.element.test.js +0 -153
  56. package/target/lib/templating/elements/for.element.test.js.map +0 -1
  57. package/target/lib/templating/elements/if.element.d.ts +0 -12
  58. package/target/lib/templating/elements/if.element.js +0 -85
  59. package/target/lib/templating/elements/if.element.js.map +0 -1
  60. package/target/lib/templating/elements/if.element.test.d.ts +0 -1
  61. package/target/lib/templating/elements/if.element.test.js +0 -78
  62. package/target/lib/templating/elements/if.element.test.js.map +0 -1
  63. package/target/lib/templating/elements/props.element.d.ts +0 -11
  64. package/target/lib/templating/elements/props.element.js +0 -92
  65. package/target/lib/templating/elements/props.element.js.map +0 -1
  66. package/target/lib/templating/elements/props.element.test.d.ts +0 -1
  67. package/target/lib/templating/elements/props.element.test.js +0 -53
  68. package/target/lib/templating/elements/props.element.test.js.map +0 -1
  69. package/target/lib/templating/elements/scope.d.ts +0 -13
  70. package/target/lib/templating/elements/scope.js +0 -59
  71. package/target/lib/templating/elements/scope.js.map +0 -1
  72. package/target/lib/templating/elements/value.element.d.ts +0 -9
  73. package/target/lib/templating/elements/value.element.js +0 -56
  74. package/target/lib/templating/elements/value.element.js.map +0 -1
  75. package/target/lib/templating/elements/value.element.test.d.ts +0 -1
  76. package/target/lib/templating/elements/value.element.test.js +0 -16
  77. package/target/lib/templating/elements/value.element.test.js.map +0 -1
  78. package/target/lib/templating/events.d.ts +0 -12
  79. package/target/lib/templating/events.js +0 -10
  80. package/target/lib/templating/events.js.map +0 -1
  81. package/target/lib/templating/token.d.ts +0 -8
  82. package/target/lib/templating/token.js +0 -27
  83. package/target/lib/templating/token.js.map +0 -1
  84. package/target/lib/templating/token.test.d.ts +0 -1
  85. package/target/lib/templating/token.test.js +0 -56
  86. package/target/lib/templating/token.test.js.map +0 -1
  87. package/target/lib/templating.d.ts +0 -2
  88. package/target/lib/templating.js +0 -3
  89. package/target/lib/templating.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joist/element",
3
- "version": "4.2.3-next.2",
3
+ "version": "4.2.3-next.21",
4
4
  "type": "module",
5
5
  "main": "./target/lib.js",
6
6
  "module": "./target/lib.js",
@@ -13,7 +13,9 @@
13
13
  "src",
14
14
  "target"
15
15
  ],
16
- "sideEffects": false,
16
+ "sideEffects": [
17
+ "**/define.js"
18
+ ],
17
19
  "description": "Intelligently apply styles to WebComponents",
18
20
  "repository": {
19
21
  "type": "git",
@@ -1,4 +1,5 @@
1
- import { expect } from "chai";
1
+ import { expect, assert } from "chai";
2
+ import { effect, observe } from "@joist/observable";
2
3
 
3
4
  import { attr } from "./attr.js";
4
5
  import { element } from "./element.js";
@@ -138,23 +139,6 @@ it("should throw an error for symbols with no description", async () => {
138
139
  }).to.throw("Cannot handle Symbol property without description");
139
140
  });
140
141
 
141
- it("should not reflect property to attribute", async () => {
142
- @element({
143
- tagName: "attr-test-5",
144
- })
145
- class MyElement extends HTMLElement {
146
- @attr({ reflect: false })
147
- accessor value = "foo";
148
- }
149
-
150
- const el = new MyElement();
151
- el.value = "bar";
152
-
153
- expect(el.value).to.equal("bar");
154
-
155
- expect(el.hasAttribute("value")).to.be.false;
156
- });
157
-
158
142
  it("non reflective attributes should still read new attribute values", async () => {
159
143
  @element({
160
144
  tagName: "attr-test-6",
@@ -190,3 +174,70 @@ it("should allow a manually defined attribute name", async () => {
190
174
 
191
175
  el.remove();
192
176
  });
177
+
178
+ it("should update property when attribute changes", async () => {
179
+ @element({
180
+ tagName: "attr-test-8",
181
+ })
182
+ class MyElement extends HTMLElement {
183
+ @attr()
184
+ accessor value = "foo";
185
+
186
+ @attr()
187
+ accessor count = 0;
188
+
189
+ @attr()
190
+ accessor enabled = false;
191
+ }
192
+
193
+ const el = new MyElement();
194
+ document.body.append(el);
195
+
196
+ // Test string property
197
+ el.setAttribute("value", "bar");
198
+ expect(el.value).to.equal("bar");
199
+
200
+ // Test number property
201
+ el.setAttribute("count", "42");
202
+ expect(el.count).to.equal(42);
203
+
204
+ // Test boolean property
205
+ el.setAttribute("enabled", "");
206
+ expect(el.enabled).to.equal(true);
207
+
208
+ el.removeAttribute("enabled");
209
+ expect(el.enabled).to.equal(false);
210
+
211
+ el.remove();
212
+ });
213
+
214
+ it("setters should be called when attributes change", async () => {
215
+ let callCount = 0;
216
+
217
+ @element({
218
+ tagName: "attr-test-9",
219
+ })
220
+ class MyElement extends HTMLElement {
221
+ @attr()
222
+ @observe()
223
+ accessor value = "foo";
224
+
225
+ @effect()
226
+ onValueChange() {
227
+ callCount++;
228
+ }
229
+ }
230
+
231
+ const el = new MyElement();
232
+ document.body.append(el);
233
+
234
+ el.setAttribute("value", "bar");
235
+
236
+ // needs to wait for the mutation observer to run
237
+ await Promise.resolve();
238
+ await Promise.resolve();
239
+
240
+ assert.equal(callCount, 1);
241
+
242
+ el.remove();
243
+ });
package/src/lib/attr.ts CHANGED
@@ -6,9 +6,9 @@ export interface AttrOpts {
6
6
  reflect?: boolean;
7
7
  }
8
8
 
9
- export function attr(opts?: AttrOpts) {
10
- return function attrDecorator<This extends HTMLElement>(
11
- { get, set }: ClassAccessorDecoratorTarget<This, unknown>,
9
+ export function attr<This extends HTMLElement>(opts?: AttrOpts) {
10
+ return function attrDecorator(
11
+ base: ClassAccessorDecoratorTarget<This, unknown>,
12
12
  ctx: ClassAccessorDecoratorContext<This>,
13
13
  ): ClassAccessorDecoratorResult<This, any> {
14
14
  const attrName = opts?.name ?? parseAttrName(ctx.name);
@@ -19,8 +19,7 @@ export function attr(opts?: AttrOpts) {
19
19
  propName: ctx.name,
20
20
  observe: opts?.observed ?? true,
21
21
  reflect,
22
- getPropValue: get,
23
- setPropValue: set,
22
+ access: base,
24
23
  });
25
24
 
26
25
  return {
@@ -43,7 +42,7 @@ export function attr(opts?: AttrOpts) {
43
42
  }
44
43
  }
45
44
 
46
- set.call(this, value);
45
+ base.set.call(this, value);
47
46
  },
48
47
  };
49
48
  };
@@ -34,31 +34,39 @@ it("should write default value to attribute", async () => {
34
34
  el.remove();
35
35
  });
36
36
 
37
- // TODO: Figure out test
38
- // it('should register attributes', async () => {
39
- // @element({
40
- // tagName: 'element-2'
41
- // })
42
- // class MyElement extends HTMLElement {
43
- // @attr()
44
- // accessor value1 = 'hello'; // no attribute
45
-
46
- // @attr()
47
- // accessor value2 = 0; // number
48
-
49
- // @attr()
50
- // accessor value3 = true; // boolean
51
-
52
- // @attr({ observed: false }) // should be filtered out
53
- // accessor value4 = 'hello world';
54
- // }
55
-
56
- // expect(Reflect.get(MyElement, 'observedAttributes')).to.deep.equal([
57
- // 'value1',
58
- // 'value2',
59
- // 'value3'
60
- // ]);
61
- // });
37
+ it("should register attributes", async () => {
38
+ const observedAttrs: string[] = [];
39
+
40
+ @element({
41
+ tagName: "element-2",
42
+ })
43
+ class MyElement extends HTMLElement {
44
+ @attr()
45
+ accessor value1 = "hello";
46
+
47
+ @attr()
48
+ accessor value2 = 0;
49
+
50
+ @attr()
51
+ accessor value3 = true;
52
+
53
+ @attr({ observed: false })
54
+ accessor value4 = "hello world";
55
+
56
+ attributeChangedCallback(name: string) {
57
+ observedAttrs.push(name);
58
+ }
59
+ }
60
+
61
+ const el = new MyElement();
62
+
63
+ el.setAttribute("value1", "foo");
64
+ el.setAttribute("value2", "1");
65
+ el.setAttribute("value3", "false");
66
+ el.setAttribute("value4", "bar");
67
+
68
+ expect(observedAttrs).to.deep.equal(["value1", "value2", "value3"]);
69
+ });
62
70
 
63
71
  it("should attach shadow root when the shadow property exists", async () => {
64
72
  @element({
@@ -47,28 +47,24 @@ export function element<T extends ElementConstructor>(opts?: ElementOpts) {
47
47
  }
48
48
  }
49
49
 
50
- attributeChangedCallback(
51
- name: string,
52
- oldValue: string,
53
- newValue: string,
54
- ) {
50
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
55
51
  const attr = meta.attrs.get(name);
56
52
  const cbs = meta.attrChanges.get(name);
57
53
 
58
54
  if (attr) {
59
55
  if (oldValue !== newValue) {
60
- const ogValue = attr.getPropValue.call(this);
56
+ const sourceValue = attr.access.get.call(this);
57
+ let value: string | number | boolean = newValue;
61
58
 
62
- if (newValue === "") {
59
+ if (typeof sourceValue === "boolean") {
63
60
  // treat as boolean
64
- attr.setPropValue.call(this, true);
65
- } else if (typeof ogValue === "number") {
61
+ value = newValue !== null;
62
+ } else if (typeof sourceValue === "number") {
66
63
  // treat as number
67
- attr.setPropValue.call(this, Number(newValue));
68
- } else {
69
- // treat as string
70
- attr.setPropValue.call(this, newValue);
64
+ value = Number(newValue);
71
65
  }
66
+
67
+ attr.access.set.call(this, value);
72
68
  }
73
69
 
74
70
  if (cbs) {
@@ -86,13 +82,13 @@ export function element<T extends ElementConstructor>(opts?: ElementOpts) {
86
82
  }
87
83
 
88
84
  connectedCallback() {
89
- if (this.isConnected) {
85
+ if (!this.#abortController) {
86
+ this.#abortController = new AbortController();
87
+
90
88
  for (const { event, cb, selector } of meta.listeners) {
91
89
  const root = selector(this);
92
90
 
93
91
  if (root) {
94
- this.#abortController = new AbortController();
95
-
96
92
  root.addEventListener(event, cb.bind(this), {
97
93
  signal: this.#abortController.signal,
98
94
  });
@@ -100,12 +96,12 @@ export function element<T extends ElementConstructor>(opts?: ElementOpts) {
100
96
  throw new Error(`could not add listener to ${root}`);
101
97
  }
102
98
  }
99
+ }
103
100
 
104
- reflectAttributeValues(this, meta.attrs);
101
+ reflectAttributeValues(this, meta.attrs);
105
102
 
106
- if (super.connectedCallback) {
107
- super.connectedCallback();
108
- }
103
+ if (super.connectedCallback) {
104
+ super.connectedCallback();
109
105
  }
110
106
  }
111
107
 
@@ -126,13 +122,10 @@ export function element<T extends ElementConstructor>(opts?: ElementOpts) {
126
122
  };
127
123
  }
128
124
 
129
- function reflectAttributeValues<T extends HTMLElement>(
130
- el: T,
131
- attrs: AttrMetadata,
132
- ) {
133
- for (const [attrName, { getPropValue, reflect }] of attrs) {
125
+ function reflectAttributeValues<T extends HTMLElement>(el: T, attrs: AttrMetadata) {
126
+ for (const [attrName, { access, reflect }] of attrs) {
134
127
  if (reflect) {
135
- const value = getPropValue.call(el);
128
+ const value = access.get.call(el);
136
129
 
137
130
  // reflect values back to attributes
138
131
  if (value !== null && value !== undefined && value !== "") {
@@ -143,7 +136,8 @@ function reflectAttributeValues<T extends HTMLElement>(
143
136
  el.setAttribute(attrName, "");
144
137
  }
145
138
  }
146
- } else {
139
+ } else if (!el.hasAttribute(attrName)) {
140
+ // only set parent attribute if it doesn't exist
147
141
  // set key/value attribute
148
142
  const strValue = String(value);
149
143
 
@@ -102,3 +102,78 @@ describe("@listen()", () => {
102
102
  el.remove();
103
103
  });
104
104
  });
105
+
106
+ it("should remove event listeners during cleanup", () => {
107
+ let clickCount = 0;
108
+
109
+ @element({
110
+ tagName: "listener-cleanup",
111
+ shadowDom: [],
112
+ })
113
+ class MyElement extends HTMLElement {
114
+ @listen("click")
115
+ onClick1() {
116
+ clickCount++;
117
+ }
118
+
119
+ @listen("click")
120
+ onClick2() {
121
+ clickCount++;
122
+ }
123
+ }
124
+
125
+ const el = new MyElement();
126
+ document.body.append(el);
127
+
128
+ // First click should increment counter
129
+ el.shadowRoot?.dispatchEvent(new Event("click"));
130
+ assert.equal(clickCount, 2);
131
+
132
+ // Remove element which should cleanup listeners
133
+ el.remove();
134
+
135
+ // Second click after removal should not increment counter
136
+ el.shadowRoot?.dispatchEvent(new Event("click"));
137
+ assert.equal(clickCount, 2);
138
+ });
139
+
140
+ it("should not add event listeners multiple times when element is moved", () => {
141
+ let clickCount = 0;
142
+
143
+ @element({
144
+ tagName: "listener-move",
145
+ shadowDom: [],
146
+ })
147
+ class MyElement extends HTMLElement {
148
+ @listen("click")
149
+ onClick() {
150
+ clickCount++;
151
+ }
152
+ }
153
+
154
+ const el = new MyElement();
155
+ const container1 = document.createElement("div");
156
+ const container2 = document.createElement("div");
157
+
158
+ document.body.append(container1);
159
+ document.body.append(container2);
160
+
161
+ // Add to first container
162
+ container1.append(el);
163
+
164
+ // Click should increment once
165
+ el.shadowRoot?.dispatchEvent(new Event("click"));
166
+ assert.equal(clickCount, 1);
167
+
168
+ // Move to second container
169
+ container2.append(el);
170
+
171
+ // Click should still only increment once
172
+ el.shadowRoot?.dispatchEvent(new Event("click"));
173
+ assert.equal(clickCount, 2);
174
+
175
+ // Cleanup
176
+ el.remove();
177
+ container1.remove();
178
+ container2.remove();
179
+ });
@@ -4,8 +4,10 @@ export interface AttrDef {
4
4
  propName: string | symbol;
5
5
  observe: boolean;
6
6
  reflect: boolean;
7
- getPropValue: () => unknown;
8
- setPropValue: (value: unknown) => void;
7
+ access: {
8
+ get: () => unknown;
9
+ set: (value: unknown) => void;
10
+ };
9
11
  }
10
12
 
11
13
  export type ListenerSelector<T> = (el: T) => EventTarget | null;
@@ -16,11 +18,7 @@ export interface Listener<T> {
16
18
  selector: ListenerSelector<T>;
17
19
  }
18
20
 
19
- export type AttrChangedCallback = (
20
- name: string,
21
- oldValue: string,
22
- newValue: string,
23
- ) => void;
21
+ export type AttrChangedCallback = (name: string, oldValue: string, newValue: string) => void;
24
22
 
25
23
  export class AttrMetadata extends Map<string, AttrDef> {}
26
24
  export class AttrChangeMetadata extends Map<string, Set<AttrChangedCallback>> {}
@@ -3,4 +3,4 @@ export interface AttrOpts {
3
3
  observed?: boolean;
4
4
  reflect?: boolean;
5
5
  }
6
- export declare function attr(opts?: AttrOpts): <This extends HTMLElement>({ get, set }: ClassAccessorDecoratorTarget<This, unknown>, ctx: ClassAccessorDecoratorContext<This>) => ClassAccessorDecoratorResult<This, any>;
6
+ export declare function attr<This extends HTMLElement>(opts?: AttrOpts): (base: ClassAccessorDecoratorTarget<This, unknown>, ctx: ClassAccessorDecoratorContext<This>) => ClassAccessorDecoratorResult<This, any>;
@@ -1,6 +1,6 @@
1
1
  import { metadataStore } from "./metadata.js";
2
2
  export function attr(opts) {
3
- return function attrDecorator({ get, set }, ctx) {
3
+ return function attrDecorator(base, ctx) {
4
4
  const attrName = opts?.name ?? parseAttrName(ctx.name);
5
5
  const meta = metadataStore.read(ctx.metadata);
6
6
  const reflect = opts?.reflect ?? true;
@@ -8,8 +8,7 @@ export function attr(opts) {
8
8
  propName: ctx.name,
9
9
  observe: opts?.observed ?? true,
10
10
  reflect,
11
- getPropValue: get,
12
- setPropValue: set,
11
+ access: base,
13
12
  });
14
13
  return {
15
14
  set(value) {
@@ -31,7 +30,7 @@ export function attr(opts) {
31
30
  }
32
31
  }
33
32
  }
34
- set.call(this, value);
33
+ base.set.call(this, value);
35
34
  },
36
35
  };
37
36
  };
@@ -1 +1 @@
1
- {"version":3,"file":"attr.js","sourceRoot":"","sources":["../../src/lib/attr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAQ9C,MAAM,UAAU,IAAI,CAAC,IAAe;IAClC,OAAO,SAAS,aAAa,CAC3B,EAAE,GAAG,EAAE,GAAG,EAA+C,EACzD,GAAwC;QAExC,MAAM,QAAQ,GAAG,IAAI,EAAE,IAAI,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAO,GAAG,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,IAAI,CAAC;QAEtC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE;YACvB,QAAQ,EAAE,GAAG,CAAC,IAAI;YAClB,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,IAAI;YAC/B,OAAO;YACP,YAAY,EAAE,GAAG;YACjB,YAAY,EAAE,GAAG;SAClB,CAAC,CAAC;QAEH,OAAO;YACL,GAAG,CAAC,KAAc;gBAChB,IAAI,OAAO,EAAE,CAAC;oBACZ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;wBACnB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;4BACjC,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;wBAClC,CAAC;oBACH,CAAC;yBAAM,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;wBAC3B,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;4BAChC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;wBACjC,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACN,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;wBAE/B,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,QAAQ,EAAE,CAAC;4BAC7C,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;wBACxC,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACxB,CAAC;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,GAAoB;IACzC,IAAI,KAAa,CAAC;IAElB,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YACpB,KAAK,GAAG,GAAG,CAAC,WAAW,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,GAAG,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC"}
1
+ {"version":3,"file":"attr.js","sourceRoot":"","sources":["../../src/lib/attr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAQ9C,MAAM,UAAU,IAAI,CAA2B,IAAe;IAC5D,OAAO,SAAS,aAAa,CAC3B,IAAiD,EACjD,GAAwC;QAExC,MAAM,QAAQ,GAAG,IAAI,EAAE,IAAI,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAO,GAAG,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,IAAI,CAAC;QAEtC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE;YACvB,QAAQ,EAAE,GAAG,CAAC,IAAI;YAClB,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,IAAI;YAC/B,OAAO;YACP,MAAM,EAAE,IAAI;SACb,CAAC,CAAC;QAEH,OAAO;YACL,GAAG,CAAC,KAAc;gBAChB,IAAI,OAAO,EAAE,CAAC;oBACZ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;wBACnB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;4BACjC,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;wBAClC,CAAC;oBACH,CAAC;yBAAM,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;wBAC3B,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;4BAChC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;wBACjC,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACN,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;wBAE/B,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,QAAQ,EAAE,CAAC;4BAC7C,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;wBACxC,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC7B,CAAC;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,GAAoB;IACzC,IAAI,KAAa,CAAC;IAElB,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YACpB,KAAK,GAAG,GAAG,CAAC,WAAW,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,GAAG,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { __esDecorate, __propKey, __runInitializers } from "tslib";
2
- import { expect } from "chai";
2
+ import { expect, assert } from "chai";
3
+ import { effect, observe } from "@joist/observable";
3
4
  import { attr } from "./attr.js";
4
5
  import { element } from "./element.js";
5
6
  it("should read and parse the correct values", () => {
@@ -288,10 +289,10 @@ it("should throw an error for symbols with no description", async () => {
288
289
  new MyElement();
289
290
  }).to.throw("Cannot handle Symbol property without description");
290
291
  });
291
- it("should not reflect property to attribute", async () => {
292
+ it("non reflective attributes should still read new attribute values", async () => {
292
293
  let MyElement = (() => {
293
294
  let _classDecorators = [element({
294
- tagName: "attr-test-5",
295
+ tagName: "attr-test-6",
295
296
  })];
296
297
  let _classDescriptor;
297
298
  let _classExtraInitializers = [];
@@ -322,14 +323,13 @@ it("should not reflect property to attribute", async () => {
322
323
  return MyElement = _classThis;
323
324
  })();
324
325
  const el = new MyElement();
325
- el.value = "bar";
326
+ el.setAttribute("value", "bar");
326
327
  expect(el.value).to.equal("bar");
327
- expect(el.hasAttribute("value")).to.be.false;
328
328
  });
329
- it("non reflective attributes should still read new attribute values", async () => {
329
+ it("should allow a manually defined attribute name", async () => {
330
330
  let MyElement = (() => {
331
331
  let _classDecorators = [element({
332
- tagName: "attr-test-6",
332
+ tagName: "attr-test-7",
333
333
  })];
334
334
  let _classDescriptor;
335
335
  let _classExtraInitializers = [];
@@ -342,14 +342,16 @@ it("non reflective attributes should still read new attribute values", async ()
342
342
  static { _classThis = this; }
343
343
  static {
344
344
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
345
- _value_decorators = [attr({ reflect: false })];
345
+ _value_decorators = [attr({
346
+ name: "aria-label",
347
+ })];
346
348
  __esDecorate(this, null, _value_decorators, { kind: "accessor", name: "value", static: false, private: false, access: { has: obj => "value" in obj, get: obj => obj.value, set: (obj, value) => { obj.value = value; } }, metadata: _metadata }, _value_initializers, _value_extraInitializers);
347
349
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
348
350
  MyElement = _classThis = _classDescriptor.value;
349
351
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
350
352
  __runInitializers(_classThis, _classExtraInitializers);
351
353
  }
352
- #value_accessor_storage = __runInitializers(this, _value_initializers, "foo");
354
+ #value_accessor_storage = __runInitializers(this, _value_initializers, "");
353
355
  get value() { return this.#value_accessor_storage; }
354
356
  set value(value) { this.#value_accessor_storage = value; }
355
357
  constructor() {
@@ -360,37 +362,106 @@ it("non reflective attributes should still read new attribute values", async ()
360
362
  return MyElement = _classThis;
361
363
  })();
362
364
  const el = new MyElement();
365
+ el.setAttribute("aria-label", "TEST");
366
+ document.body.append(el);
367
+ expect(el.value).to.equal("TEST");
368
+ el.remove();
369
+ });
370
+ it("should update property when attribute changes", async () => {
371
+ let MyElement = (() => {
372
+ let _classDecorators = [element({
373
+ tagName: "attr-test-8",
374
+ })];
375
+ let _classDescriptor;
376
+ let _classExtraInitializers = [];
377
+ let _classThis;
378
+ let _classSuper = HTMLElement;
379
+ let _value_decorators;
380
+ let _value_initializers = [];
381
+ let _value_extraInitializers = [];
382
+ let _count_decorators;
383
+ let _count_initializers = [];
384
+ let _count_extraInitializers = [];
385
+ let _enabled_decorators;
386
+ let _enabled_initializers = [];
387
+ let _enabled_extraInitializers = [];
388
+ var MyElement = class extends _classSuper {
389
+ static { _classThis = this; }
390
+ static {
391
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
392
+ _value_decorators = [attr()];
393
+ _count_decorators = [attr()];
394
+ _enabled_decorators = [attr()];
395
+ __esDecorate(this, null, _value_decorators, { kind: "accessor", name: "value", static: false, private: false, access: { has: obj => "value" in obj, get: obj => obj.value, set: (obj, value) => { obj.value = value; } }, metadata: _metadata }, _value_initializers, _value_extraInitializers);
396
+ __esDecorate(this, null, _count_decorators, { kind: "accessor", name: "count", static: false, private: false, access: { has: obj => "count" in obj, get: obj => obj.count, set: (obj, value) => { obj.count = value; } }, metadata: _metadata }, _count_initializers, _count_extraInitializers);
397
+ __esDecorate(this, null, _enabled_decorators, { kind: "accessor", name: "enabled", static: false, private: false, access: { has: obj => "enabled" in obj, get: obj => obj.enabled, set: (obj, value) => { obj.enabled = value; } }, metadata: _metadata }, _enabled_initializers, _enabled_extraInitializers);
398
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
399
+ MyElement = _classThis = _classDescriptor.value;
400
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
401
+ __runInitializers(_classThis, _classExtraInitializers);
402
+ }
403
+ #value_accessor_storage = __runInitializers(this, _value_initializers, "foo");
404
+ get value() { return this.#value_accessor_storage; }
405
+ set value(value) { this.#value_accessor_storage = value; }
406
+ #count_accessor_storage = (__runInitializers(this, _value_extraInitializers), __runInitializers(this, _count_initializers, 0));
407
+ get count() { return this.#count_accessor_storage; }
408
+ set count(value) { this.#count_accessor_storage = value; }
409
+ #enabled_accessor_storage = (__runInitializers(this, _count_extraInitializers), __runInitializers(this, _enabled_initializers, false));
410
+ get enabled() { return this.#enabled_accessor_storage; }
411
+ set enabled(value) { this.#enabled_accessor_storage = value; }
412
+ constructor() {
413
+ super(...arguments);
414
+ __runInitializers(this, _enabled_extraInitializers);
415
+ }
416
+ };
417
+ return MyElement = _classThis;
418
+ })();
419
+ const el = new MyElement();
420
+ document.body.append(el);
363
421
  el.setAttribute("value", "bar");
364
422
  expect(el.value).to.equal("bar");
423
+ el.setAttribute("count", "42");
424
+ expect(el.count).to.equal(42);
425
+ el.setAttribute("enabled", "");
426
+ expect(el.enabled).to.equal(true);
427
+ el.removeAttribute("enabled");
428
+ expect(el.enabled).to.equal(false);
429
+ el.remove();
365
430
  });
366
- it("should allow a manually defined attribute name", async () => {
431
+ it("setters should be called when attributes change", async () => {
432
+ let callCount = 0;
367
433
  let MyElement = (() => {
368
434
  let _classDecorators = [element({
369
- tagName: "attr-test-7",
435
+ tagName: "attr-test-9",
370
436
  })];
371
437
  let _classDescriptor;
372
438
  let _classExtraInitializers = [];
373
439
  let _classThis;
374
440
  let _classSuper = HTMLElement;
441
+ let _instanceExtraInitializers = [];
375
442
  let _value_decorators;
376
443
  let _value_initializers = [];
377
444
  let _value_extraInitializers = [];
445
+ let _onValueChange_decorators;
378
446
  var MyElement = class extends _classSuper {
379
447
  static { _classThis = this; }
380
448
  static {
381
449
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
382
- _value_decorators = [attr({
383
- name: "aria-label",
384
- })];
450
+ _value_decorators = [attr(), observe()];
451
+ _onValueChange_decorators = [effect()];
385
452
  __esDecorate(this, null, _value_decorators, { kind: "accessor", name: "value", static: false, private: false, access: { has: obj => "value" in obj, get: obj => obj.value, set: (obj, value) => { obj.value = value; } }, metadata: _metadata }, _value_initializers, _value_extraInitializers);
453
+ __esDecorate(this, null, _onValueChange_decorators, { kind: "method", name: "onValueChange", static: false, private: false, access: { has: obj => "onValueChange" in obj, get: obj => obj.onValueChange }, metadata: _metadata }, null, _instanceExtraInitializers);
386
454
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
387
455
  MyElement = _classThis = _classDescriptor.value;
388
456
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
389
457
  __runInitializers(_classThis, _classExtraInitializers);
390
458
  }
391
- #value_accessor_storage = __runInitializers(this, _value_initializers, "");
459
+ #value_accessor_storage = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _value_initializers, "foo"));
392
460
  get value() { return this.#value_accessor_storage; }
393
461
  set value(value) { this.#value_accessor_storage = value; }
462
+ onValueChange() {
463
+ callCount++;
464
+ }
394
465
  constructor() {
395
466
  super(...arguments);
396
467
  __runInitializers(this, _value_extraInitializers);
@@ -399,9 +470,11 @@ it("should allow a manually defined attribute name", async () => {
399
470
  return MyElement = _classThis;
400
471
  })();
401
472
  const el = new MyElement();
402
- el.setAttribute("aria-label", "TEST");
403
473
  document.body.append(el);
404
- expect(el.value).to.equal("TEST");
474
+ el.setAttribute("value", "bar");
475
+ await Promise.resolve();
476
+ await Promise.resolve();
477
+ assert.equal(callCount, 1);
405
478
  el.remove();
406
479
  });
407
480
  //# sourceMappingURL=attr.test.js.map