@joist/element 4.2.3-next.5 → 4.2.3-next.7

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 (76) hide show
  1. package/package.json +2 -5
  2. package/src/lib/element.ts +13 -23
  3. package/src/lib/listen.test.ts +75 -0
  4. package/target/lib/element.js +12 -14
  5. package/target/lib/element.js.map +1 -1
  6. package/target/lib/listen.test.js +97 -0
  7. package/target/lib/listen.test.js.map +1 -1
  8. package/src/lib/templating/README.md +0 -406
  9. package/src/lib/templating/bind.ts +0 -40
  10. package/src/lib/templating/define.ts +0 -5
  11. package/src/lib/templating/elements/async.element.test.ts +0 -90
  12. package/src/lib/templating/elements/async.element.ts +0 -122
  13. package/src/lib/templating/elements/for.element.test.ts +0 -221
  14. package/src/lib/templating/elements/for.element.ts +0 -188
  15. package/src/lib/templating/elements/if.element.test.ts +0 -90
  16. package/src/lib/templating/elements/if.element.ts +0 -93
  17. package/src/lib/templating/elements/props.element.test.ts +0 -62
  18. package/src/lib/templating/elements/props.element.ts +0 -80
  19. package/src/lib/templating/elements/scope.ts +0 -45
  20. package/src/lib/templating/elements/value.element.test.ts +0 -20
  21. package/src/lib/templating/elements/value.element.ts +0 -41
  22. package/src/lib/templating/events.ts +0 -21
  23. package/src/lib/templating/token.test.ts +0 -74
  24. package/src/lib/templating/token.ts +0 -34
  25. package/src/lib/templating.ts +0 -2
  26. package/target/lib/templating/bind.d.ts +0 -1
  27. package/target/lib/templating/bind.js +0 -30
  28. package/target/lib/templating/bind.js.map +0 -1
  29. package/target/lib/templating/define.d.ts +0 -5
  30. package/target/lib/templating/define.js +0 -6
  31. package/target/lib/templating/define.js.map +0 -1
  32. package/target/lib/templating/elements/async.element.d.ts +0 -17
  33. package/target/lib/templating/elements/async.element.js +0 -115
  34. package/target/lib/templating/elements/async.element.js.map +0 -1
  35. package/target/lib/templating/elements/async.element.test.d.ts +0 -1
  36. package/target/lib/templating/elements/async.element.test.js +0 -75
  37. package/target/lib/templating/elements/async.element.test.js.map +0 -1
  38. package/target/lib/templating/elements/for.element.d.ts +0 -24
  39. package/target/lib/templating/elements/for.element.js +0 -189
  40. package/target/lib/templating/elements/for.element.js.map +0 -1
  41. package/target/lib/templating/elements/for.element.test.d.ts +0 -2
  42. package/target/lib/templating/elements/for.element.test.js +0 -153
  43. package/target/lib/templating/elements/for.element.test.js.map +0 -1
  44. package/target/lib/templating/elements/if.element.d.ts +0 -12
  45. package/target/lib/templating/elements/if.element.js +0 -85
  46. package/target/lib/templating/elements/if.element.js.map +0 -1
  47. package/target/lib/templating/elements/if.element.test.d.ts +0 -1
  48. package/target/lib/templating/elements/if.element.test.js +0 -78
  49. package/target/lib/templating/elements/if.element.test.js.map +0 -1
  50. package/target/lib/templating/elements/props.element.d.ts +0 -11
  51. package/target/lib/templating/elements/props.element.js +0 -92
  52. package/target/lib/templating/elements/props.element.js.map +0 -1
  53. package/target/lib/templating/elements/props.element.test.d.ts +0 -1
  54. package/target/lib/templating/elements/props.element.test.js +0 -53
  55. package/target/lib/templating/elements/props.element.test.js.map +0 -1
  56. package/target/lib/templating/elements/scope.d.ts +0 -13
  57. package/target/lib/templating/elements/scope.js +0 -59
  58. package/target/lib/templating/elements/scope.js.map +0 -1
  59. package/target/lib/templating/elements/value.element.d.ts +0 -9
  60. package/target/lib/templating/elements/value.element.js +0 -56
  61. package/target/lib/templating/elements/value.element.js.map +0 -1
  62. package/target/lib/templating/elements/value.element.test.d.ts +0 -1
  63. package/target/lib/templating/elements/value.element.test.js +0 -16
  64. package/target/lib/templating/elements/value.element.test.js.map +0 -1
  65. package/target/lib/templating/events.d.ts +0 -12
  66. package/target/lib/templating/events.js +0 -10
  67. package/target/lib/templating/events.js.map +0 -1
  68. package/target/lib/templating/token.d.ts +0 -8
  69. package/target/lib/templating/token.js +0 -27
  70. package/target/lib/templating/token.js.map +0 -1
  71. package/target/lib/templating/token.test.d.ts +0 -1
  72. package/target/lib/templating/token.test.js +0 -56
  73. package/target/lib/templating/token.test.js.map +0 -1
  74. package/target/lib/templating.d.ts +0 -2
  75. package/target/lib/templating.js +0 -3
  76. package/target/lib/templating.js.map +0 -1
@@ -1,221 +0,0 @@
1
- import "./for.element.js";
2
- import "./value.element.js";
3
-
4
- import { fixtureSync, html } from "@open-wc/testing";
5
- import { assert } from "chai";
6
-
7
- import type { JoistValueEvent } from "../events.js";
8
-
9
- it("should iterate over an iterable", () => {
10
- const element = fixtureSync(html`
11
- <div
12
- @joist::value=${(e: JoistValueEvent) => {
13
- e.update({
14
- oldValue: null,
15
- newValue: new Set([
16
- { id: "123", label: "Hello" },
17
- { id: "456", label: "World" },
18
- ]),
19
- });
20
- }}
21
- >
22
- <ul>
23
- <j-for bind="items" key="id">
24
- <template>
25
- <li>
26
- <j-value bind="each.value.label"></j-value>
27
- </li>
28
- </template>
29
- </j-for>
30
- </ul>
31
- </div>
32
- `);
33
-
34
- const listItems = element.querySelectorAll("li");
35
-
36
- assert.equal(listItems.length, 2);
37
- assert.equal(listItems[0].textContent?.trim(), "Hello");
38
- assert.equal(listItems[1].textContent?.trim(), "World");
39
- });
40
-
41
- it("should handle empty arrays", () => {
42
- const element = fixtureSync(html`
43
- <div
44
- @joist::value=${(e: JoistValueEvent) => {
45
- e.update({
46
- oldValue: null,
47
- newValue: [],
48
- });
49
- }}
50
- >
51
- <j-for bind="items">
52
- <template>
53
- <div>Item</div>
54
- </template>
55
- </j-for>
56
- </div>
57
- `);
58
-
59
- assert.equal(element.querySelectorAll("div").length, 0);
60
- });
61
-
62
- it("should update when items are added or removed", () => {
63
- const element = fixtureSync(html`
64
- <div
65
- @joist::value=${(e: JoistValueEvent) => {
66
- // Initial items
67
- e.update({
68
- oldValue: null,
69
- newValue: [
70
- { id: "1", text: "First" },
71
- { id: "2", text: "Second" },
72
- ],
73
- });
74
-
75
- // Add an item
76
- e.update({
77
- oldValue: null,
78
- newValue: [
79
- { id: "1", text: "First" },
80
- { id: "2", text: "Second" },
81
- { id: "3", text: "Third" },
82
- ],
83
- });
84
-
85
- // Remove an item
86
- e.update({
87
- oldValue: null,
88
- newValue: [
89
- { id: "1", text: "First" },
90
- { id: "3", text: "Third" },
91
- ],
92
- });
93
- }}
94
- >
95
- <j-for bind="items" key="id">
96
- <template>
97
- <j-value bind="each.value.text"></j-value>
98
- </template>
99
- </j-for>
100
- </div>
101
- `);
102
-
103
- const items = element.querySelectorAll("j-value");
104
- assert.equal(items.length, 2);
105
- assert.equal(items[0].textContent?.trim(), "First");
106
- assert.equal(items[1].textContent?.trim(), "Third");
107
- });
108
-
109
- it("should provide index and position information", () => {
110
- const element = fixtureSync(html`
111
- <div
112
- @joist::value=${(e: JoistValueEvent) => {
113
- e.update({
114
- oldValue: null,
115
- newValue: ["A", "B", "C"],
116
- });
117
- }}
118
- >
119
- <j-for bind="items">
120
- <template>
121
- <j-value bind="each.value"></j-value>
122
- (index: <j-value bind="each.index"></j-value>,
123
- position: <j-value bind="each.position"></j-value>)
124
- </template>
125
- </j-for>
126
- </div>
127
- `);
128
-
129
- const items = element.querySelectorAll("j-for-scope");
130
- assert.equal(items.length, 3);
131
- assert.equal(
132
- items[0].textContent?.trim().replaceAll("\n", "").replaceAll(" ", ""),
133
- "A(index:0,position:1)",
134
- );
135
- assert.equal(
136
- items[1].textContent?.trim().replaceAll("\n", "").replaceAll(" ", ""),
137
- "B(index:1,position:2)",
138
- );
139
- assert.equal(
140
- items[2].textContent?.trim().replaceAll("\n", "").replaceAll(" ", ""),
141
- "C(index:2,position:3)",
142
- );
143
- });
144
-
145
- // it("should handle nested j-for elements", () => {
146
- // const element = fixtureSync(html`
147
- // <div
148
- // @joist::value=${(e: JoistValueEvent) => {
149
- // e.update({
150
- // oldValue: null,
151
- // newValue: [
152
- // { id: "1", items: ["A", "B"] },
153
- // { id: "2", items: ["C", "D"] },
154
- // ],
155
- // });
156
- // }}
157
- // >
158
- // <j-for bind="groups" key="id">
159
- // <template>
160
- // <div class="group">
161
- // <j-for bind="each.value.items">
162
- // <template>
163
- // <j-value class="child" bind="each.value"></j-value>
164
- // </template>
165
- // </j-for>
166
- // </div>
167
- // </template>
168
- // </j-for>
169
- // </div>
170
- // `);
171
-
172
- // const groups = element.querySelectorAll(".group");
173
- // assert.equal(groups.length, 2);
174
-
175
- // const items = element.querySelectorAll(".child");
176
- // assert.equal(items.length, 4);
177
- // assert.equal(items[0].textContent?.trim(), "A");
178
- // assert.equal(items[1].textContent?.trim(), "B");
179
- // assert.equal(items[2].textContent?.trim(), "C");
180
- // assert.equal(items[3].textContent?.trim(), "D");
181
- // });
182
-
183
- it("should maintain DOM order when items are reordered", () => {
184
- const element = fixtureSync(html`
185
- <div
186
- @joist::value=${(e: JoistValueEvent) => {
187
- // Initial order
188
- e.update({
189
- oldValue: null,
190
- newValue: [
191
- { id: "1", text: "First" },
192
- { id: "2", text: "Second" },
193
- { id: "3", text: "Third" },
194
- ],
195
- });
196
-
197
- // Reorder items
198
- e.update({
199
- oldValue: null,
200
- newValue: [
201
- { id: "3", text: "Third" },
202
- { id: "1", text: "First" },
203
- { id: "2", text: "Second" },
204
- ],
205
- });
206
- }}
207
- >
208
- <j-for bind="items" key="id">
209
- <template>
210
- <j-value bind="each.value.text"></j-value>
211
- </template>
212
- </j-for>
213
- </div>
214
- `);
215
-
216
- const items = element.querySelectorAll("j-value");
217
- assert.equal(items.length, 3);
218
- assert.equal(items[0].textContent?.trim(), "Third");
219
- assert.equal(items[1].textContent?.trim(), "First");
220
- assert.equal(items[2].textContent?.trim(), "Second");
221
- });
@@ -1,188 +0,0 @@
1
- import { attr } from "../../attr.js";
2
- import { element } from "../../element.js";
3
- import { query } from "../../query.js";
4
- import { css, html } from "../../tags.js";
5
-
6
- import { bind } from "../bind.js";
7
- import { JoistValueEvent } from "../events.js";
8
- import { JToken } from "../token.js";
9
-
10
- declare global {
11
- interface HTMLElementTagNameMap {
12
- "j-for": JositForElement;
13
- "j-for-scope": JForScope;
14
- }
15
- }
16
-
17
- export interface EachCtx<T> {
18
- value: T | null;
19
- index: number | null;
20
- position: number | null;
21
- }
22
-
23
- @element({
24
- tagName: "j-for-scope",
25
- // prettier-ignore
26
- shadowDom: [css`:host{display: contents;}`, html`<slot></slot>`],
27
- })
28
- export class JForScope<T = unknown> extends HTMLElement {
29
- @bind()
30
- accessor each: EachCtx<T> = {
31
- value: null,
32
- index: null,
33
- position: null,
34
- };
35
-
36
- @attr()
37
- accessor key = "";
38
- }
39
-
40
- @element({
41
- tagName: "j-for",
42
- // prettier-ignore
43
- shadowDom: [css`:host{display:contents;}`, html`<slot></slot>`],
44
- })
45
- export class JositForElement extends HTMLElement {
46
- @attr()
47
- accessor bind = "";
48
-
49
- @attr()
50
- accessor key = "";
51
-
52
- #template = query("template", this);
53
- #items: Iterable<unknown> = [];
54
- #scopes = new Map<unknown, JForScope>();
55
-
56
- connectedCallback(): void {
57
- const template = this.#template();
58
-
59
- if (this.firstElementChild !== template) {
60
- throw new Error("The first Node in j-for needs to be a template");
61
- }
62
-
63
- // collect all scopes from the template to be matched against later
64
- let currentScope = template.nextElementSibling;
65
- while (currentScope instanceof JForScope) {
66
- this.#scopes.set(currentScope.key, currentScope);
67
- currentScope = currentScope.nextElementSibling;
68
- }
69
-
70
- const token = new JToken(this.bind);
71
-
72
- this.dispatchEvent(
73
- new JoistValueEvent(token, ({ newValue, oldValue }) => {
74
- if (newValue !== oldValue) {
75
- if (isIterable(newValue)) {
76
- this.#items = newValue;
77
- } else {
78
- this.#items = [];
79
- }
80
-
81
- // If there are no existing items in the DOM (template is the only child),
82
- // create all items from scratch
83
- if (template.nextSibling === null) {
84
- this.createFromEmpty();
85
- } else {
86
- // Otherwise update existing items, reusing DOM nodes where possible
87
- this.updateItems();
88
- }
89
- }
90
- }),
91
- );
92
- }
93
-
94
- // Updates the DOM by either inserting new scopes or moving existing ones
95
- // to their correct positions based on the current iteration order
96
- createFromEmpty(): void {
97
- const template = this.#template();
98
- const templateContent = template.content;
99
- const keyProperty = this.key;
100
- const fragment = document.createDocumentFragment();
101
-
102
- let index = 0;
103
- for (const value of this.#items) {
104
- const key =
105
- keyProperty && hasProperty(value, keyProperty)
106
- ? value[keyProperty]
107
- : index;
108
-
109
- const scope = new JForScope();
110
- scope.append(document.importNode(templateContent, true));
111
- scope.key = String(key);
112
- scope.each = { position: index + 1, index, value };
113
-
114
- fragment.appendChild(scope);
115
- this.#scopes.set(key, scope);
116
- index++;
117
- }
118
-
119
- this.append(fragment);
120
- }
121
-
122
- // Updates the DOM by either inserting new scopes or moving existing ones
123
- // to their correct positions based on the current iteration order
124
- updateItems(): void {
125
- const template = this.#template();
126
- const leftoverScopes = new Map<unknown, JForScope>(this.#scopes);
127
- const keyProperty = this.key; // Cache the key property
128
-
129
- let index = 0;
130
-
131
- for (const value of this.#items) {
132
- // Optimize key lookup by caching the property check
133
- const key =
134
- keyProperty && hasProperty(value, keyProperty)
135
- ? value[keyProperty]
136
- : index;
137
-
138
- let scope = leftoverScopes.get(key);
139
-
140
- if (!scope) {
141
- scope = new JForScope();
142
- scope.append(document.importNode(template.content, true));
143
- this.#scopes.set(key, scope);
144
- } else {
145
- leftoverScopes.delete(key); // Remove from map to track unused scopes
146
- }
147
-
148
- // Only update if values have changed
149
- if (scope.key !== key || scope.each.value !== value) {
150
- scope.key = String(key);
151
- scope.each = { position: index + 1, index, value };
152
- }
153
-
154
- const child = this.children[index + 1];
155
-
156
- if (child !== scope) {
157
- this.insertBefore(scope, child);
158
- }
159
-
160
- index++;
161
- }
162
-
163
- // Remove unused scopes
164
- for (const scope of leftoverScopes.values()) {
165
- scope.remove();
166
- }
167
- }
168
-
169
- disconnectedCallback(): void {
170
- for (const scope of this.#scopes.values()) {
171
- scope.remove();
172
- }
173
-
174
- this.#scopes.clear();
175
- this.#items = [];
176
- }
177
- }
178
-
179
- function isIterable<T = unknown>(obj: any): obj is Iterable<T> {
180
- return obj != null && typeof obj[Symbol.iterator] === "function";
181
- }
182
-
183
- function hasProperty(
184
- item: unknown,
185
- key: string,
186
- ): item is Record<string, unknown> {
187
- return Object.prototype.hasOwnProperty.call(item, key);
188
- }
@@ -1,90 +0,0 @@
1
- import "./if.element.js";
2
-
3
- import { fixtureSync, html } from "@open-wc/testing";
4
- import { assert } from "chai";
5
-
6
- import type { JoistValueEvent } from "../events.js";
7
-
8
- it("should render content when the bind value is truthy", () => {
9
- const element = fixtureSync(html`
10
- <div
11
- @joist::value=${(e: JoistValueEvent) => {
12
- e.update({ oldValue: null, newValue: true });
13
- }}
14
- >
15
- <j-if bind="test">
16
- <template>Visible Content</template>
17
- </j-if>
18
- </div>
19
- `);
20
-
21
- assert.equal(element.textContent?.trim(), "Visible Content");
22
- });
23
-
24
- it("should not render content when the bind value is falsy", () => {
25
- const element = fixtureSync(html`
26
- <div
27
- @joist::value=${(e: JoistValueEvent) => {
28
- e.update({ oldValue: null, newValue: true });
29
- e.update({ oldValue: null, newValue: false });
30
- }}
31
- >
32
- <j-if bind="test">
33
- <template>Visible Content</template>
34
- </j-if>
35
- </div>
36
- `);
37
-
38
- assert.equal(element.textContent?.trim(), "");
39
- });
40
-
41
- it("should handle negated tokens correctly", () => {
42
- const element = fixtureSync(html`
43
- <div
44
- @joist::value=${(e: JoistValueEvent) => {
45
- e.update({ oldValue: null, newValue: false });
46
- }}
47
- >
48
- <j-if bind="!test">
49
- <template>Visible Content</template>
50
- </j-if>
51
- </div>
52
- `);
53
-
54
- assert.equal(element.textContent?.trim(), "Visible Content");
55
- });
56
-
57
- it("should render else template when condition is falsy", () => {
58
- const element = fixtureSync(html`
59
- <div
60
- @joist::value=${(e: JoistValueEvent) => {
61
- e.update({ oldValue: null, newValue: false });
62
- }}
63
- >
64
- <j-if bind="test">
65
- <template>If Content</template>
66
- <template else>Else Content</template>
67
- </j-if>
68
- </div>
69
- `);
70
-
71
- assert.equal(element.textContent?.trim(), "Else Content");
72
- });
73
-
74
- it("should switch between if and else templates", () => {
75
- const element = fixtureSync(html`
76
- <div
77
- @joist::value=${(e: JoistValueEvent) => {
78
- e.update({ oldValue: null, newValue: false });
79
- e.update({ oldValue: false, newValue: true });
80
- }}
81
- >
82
- <j-if bind="test">
83
- <template>If Content</template>
84
- <template else>Else Content</template>
85
- </j-if>
86
- </div>
87
- `);
88
-
89
- assert.equal(element.textContent?.trim(), "If Content");
90
- });
@@ -1,93 +0,0 @@
1
- import { attr } from "../../attr.js";
2
- import { element } from "../../element.js";
3
- import { queryAll } from "../../query-all.js";
4
- import { css, html } from "../../tags.js";
5
-
6
- import { JoistValueEvent } from "../events.js";
7
- import { JToken } from "../token.js";
8
-
9
- declare global {
10
- interface HTMLElementTagNameMap {
11
- "j-if": JoistIfElement;
12
- }
13
- }
14
-
15
- @element({
16
- tagName: "j-if",
17
- // prettier-ignore
18
- shadowDom: [css`:host{display: contents;}`, html`<slot></slot>`],
19
- })
20
- export class JoistIfElement extends HTMLElement {
21
- @attr()
22
- accessor bind = "";
23
-
24
- #templates = queryAll<HTMLTemplateElement>("template", this);
25
-
26
- connectedCallback(): void {
27
- const templates = Array.from(this.#templates());
28
-
29
- if (templates.length === 0) {
30
- throw new Error("j-if requires at least one template element");
31
- }
32
-
33
- if (templates.length > 2) {
34
- throw new Error("j-if can only have two template elements (if and else)");
35
- }
36
-
37
- if (
38
- templates.length === 2 &&
39
- !templates.some((t) => t.hasAttribute("else"))
40
- ) {
41
- throw new Error(
42
- "When using two templates, one must have the else attribute",
43
- );
44
- }
45
-
46
- if (templates.length === 2 && templates[0].hasAttribute("else")) {
47
- // Swap templates to ensure if template is first
48
- [templates[0], templates[1]] = [templates[1], templates[0]];
49
- }
50
-
51
- // make sure there are no other nodes after the template
52
- this.#clean();
53
-
54
- const token = new JToken(this.bind);
55
-
56
- this.dispatchEvent(
57
- new JoistValueEvent(token, ({ newValue, oldValue }) => {
58
- if (newValue !== oldValue) {
59
- if (typeof newValue === "object" && newValue !== null) {
60
- this.apply(token.readTokenValueFrom(newValue), token.isNegated);
61
- } else {
62
- this.apply(newValue, token.isNegated);
63
- }
64
- }
65
- }),
66
- );
67
- }
68
-
69
- apply(value: unknown, isNegative: boolean): void {
70
- this.#clean();
71
-
72
- const templates = this.#templates();
73
-
74
- const shouldShowIf = isNegative ? !value : value;
75
- const templateToUse = shouldShowIf ? templates[0] : templates[1];
76
-
77
- if (templateToUse) {
78
- const content = document.importNode(templateToUse.content, true);
79
-
80
- this.appendChild(content);
81
- }
82
- }
83
-
84
- #clean(): void {
85
- while (!(this.lastChild instanceof HTMLTemplateElement)) {
86
- this.lastChild?.remove();
87
- }
88
- }
89
-
90
- disconnectedCallback(): void {
91
- this.#clean();
92
- }
93
- }
@@ -1,62 +0,0 @@
1
- import "./props.element.js";
2
-
3
- import { fixtureSync, html } from "@open-wc/testing";
4
- import { assert } from "chai";
5
-
6
- import type { JoistValueEvent } from "../events.js";
7
-
8
- it("should pass props to child", () => {
9
- const element = fixtureSync(html`
10
- <div
11
- @joist::value=${(e: JoistValueEvent) => {
12
- if (e.token.bindTo === "href") {
13
- e.update({
14
- oldValue: null,
15
- newValue: "$foo",
16
- });
17
- }
18
-
19
- if (e.token.bindTo === "target") {
20
- e.update({
21
- oldValue: null,
22
- newValue: {
23
- value: "_blank",
24
- },
25
- });
26
- }
27
- }}
28
- >
29
- <j-props>
30
- <a $href="href" $target="target.value">Hello World</a>
31
- </j-props>
32
- </div>
33
- `);
34
-
35
- const anchor = element.querySelector("a");
36
-
37
- assert.equal(anchor?.getAttribute("href"), "$foo");
38
- assert.equal(anchor?.getAttribute("target"), "_blank");
39
- });
40
-
41
- it("should pass props to specified child", () => {
42
- const element = fixtureSync(html`
43
- <div
44
- @joist::value=${(e: JoistValueEvent) => {
45
- e.update({
46
- oldValue: null,
47
- newValue: "#foo",
48
- });
49
- }}
50
- >
51
- <j-props>
52
- <a>Default</a>
53
- <a id="test" $href="href">Target</a>
54
- </j-props>
55
- </div>
56
- `);
57
-
58
- const anchor = element.querySelectorAll("a");
59
-
60
- assert.equal(anchor[0].getAttribute("href"), null);
61
- assert.equal(anchor[1].getAttribute("href"), "#foo");
62
- });