p-elements-core 1.2.30 → 1.2.32-rc-10

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 (82) hide show
  1. package/.editorconfig +17 -17
  2. package/.gitlab-ci.yml +18 -18
  3. package/CHANGELOG.md +201 -0
  4. package/demo/sample.js +1 -1
  5. package/demo/screen.css +16 -16
  6. package/demo/theme.css +1 -0
  7. package/dist/p-elements-core-modern.js +1 -1
  8. package/dist/p-elements-core.js +1 -1
  9. package/docs/package-lock.json +6897 -6897
  10. package/docs/package.json +27 -27
  11. package/docs/src/404.md +8 -8
  12. package/docs/src/_data/demos/hello-world/hello-world.tsx +35 -35
  13. package/docs/src/_data/demos/hello-world/index.html +10 -10
  14. package/docs/src/_data/demos/hello-world/project.json +7 -7
  15. package/docs/src/_data/demos/timer/demo-timer.tsx +120 -120
  16. package/docs/src/_data/demos/timer/icons.tsx +62 -62
  17. package/docs/src/_data/demos/timer/index.html +12 -12
  18. package/docs/src/_data/demos/timer/project.json +8 -8
  19. package/docs/src/_data/global.js +13 -13
  20. package/docs/src/_data/helpers.js +19 -19
  21. package/docs/src/_includes/layouts/base.njk +30 -30
  22. package/docs/src/_includes/layouts/playground.njk +40 -40
  23. package/docs/src/_includes/partials/app-header.njk +8 -8
  24. package/docs/src/_includes/partials/head.njk +14 -14
  25. package/docs/src/_includes/partials/nav.njk +19 -19
  26. package/docs/src/_includes/partials/top-nav.njk +51 -51
  27. package/docs/src/documentation/custom-element.md +221 -221
  28. package/docs/src/documentation/decorators/bind.md +71 -71
  29. package/docs/src/documentation/decorators/custom-element-config.md +63 -63
  30. package/docs/src/documentation/decorators/property.md +83 -83
  31. package/docs/src/documentation/decorators/query.md +66 -66
  32. package/docs/src/documentation/decorators/render-property-on-set.md +60 -60
  33. package/docs/src/documentation/decorators.md +9 -9
  34. package/docs/src/documentation/reactive-properties.md +53 -53
  35. package/docs/src/index.d.ts +25 -25
  36. package/docs/src/index.md +3 -3
  37. package/docs/src/scripts/components/app-mode-switch/app-mode-switch.css +78 -78
  38. package/docs/src/scripts/components/app-mode-switch/app-mode-switch.tsx +166 -166
  39. package/docs/src/scripts/components/app-playground/app-playground.tsx +189 -189
  40. package/docs/tsconfig.json +22 -22
  41. package/index.html +15 -2
  42. package/p-elements-core.d.ts +12 -3
  43. package/package.json +11 -4
  44. package/readme.md +206 -206
  45. package/src/custom-element-controller.test.ts +226 -0
  46. package/src/custom-element-controller.ts +31 -31
  47. package/src/custom-element.test.ts +906 -0
  48. package/src/custom-element.ts +471 -188
  49. package/src/custom-style-element.ts +4 -1
  50. package/src/decorators/bind.test.ts +163 -0
  51. package/src/decorators/bind.ts +46 -46
  52. package/src/decorators/custom-element-config.ts +17 -17
  53. package/src/decorators/property.test.ts +279 -0
  54. package/src/decorators/property.ts +822 -150
  55. package/src/decorators/query.test.ts +146 -0
  56. package/src/decorators/query.ts +12 -12
  57. package/src/decorators/render-property-on-set.ts +3 -3
  58. package/src/helpers/css.test.ts +150 -0
  59. package/src/helpers/css.ts +71 -71
  60. package/src/maquette/cache.test.ts +150 -0
  61. package/src/maquette/cache.ts +35 -35
  62. package/src/maquette/dom.test.ts +263 -0
  63. package/src/maquette/dom.ts +115 -115
  64. package/src/maquette/h.test.ts +165 -0
  65. package/src/maquette/h.ts +100 -100
  66. package/src/maquette/index.ts +12 -12
  67. package/src/maquette/interfaces.ts +536 -536
  68. package/src/maquette/jsx.ts +61 -61
  69. package/src/maquette/mapping.test.ts +294 -0
  70. package/src/maquette/mapping.ts +56 -56
  71. package/src/maquette/maquette.test.ts +493 -0
  72. package/src/maquette/projection.test.ts +366 -0
  73. package/src/maquette/projection.ts +666 -666
  74. package/src/maquette/projector.test.ts +351 -0
  75. package/src/maquette/projector.ts +200 -200
  76. package/src/sample/mixin/highlight.tsx +33 -32
  77. package/src/sample/sample.tsx +167 -7
  78. package/src/test-setup.ts +85 -0
  79. package/src/test-utils.ts +223 -0
  80. package/tsconfig.json +1 -0
  81. package/vitest.config.ts +41 -0
  82. package/webpack.config.js +1 -1
@@ -1,61 +1,61 @@
1
- import { VNode, VNodeChild, VNodeProperties } from "./interfaces";
2
-
3
- declare global {
4
- function jsx(
5
- tagName: string,
6
- properties: VNodeProperties | null,
7
- ...children: (VNode | string)[]
8
- ): VNode;
9
- }
10
-
11
- let toTextVNode = (data: any): VNode => {
12
- return {
13
- vnodeSelector: "",
14
- properties: undefined,
15
- children: undefined,
16
- text: data.toString(),
17
- domNode: null,
18
- };
19
- };
20
-
21
- let appendChildren = (insertions: any[], main: VNode[]) => {
22
- for (let i = 0, length = insertions.length; i < length; i++) {
23
- let item = insertions[i];
24
- if (Array.isArray(item)) {
25
- appendChildren(item, main);
26
- } else {
27
- if (item !== null && item !== undefined && item !== false) {
28
- if (!item.hasOwnProperty("vnodeSelector")) {
29
- item = toTextVNode(item);
30
- }
31
- main.push(item);
32
- }
33
- }
34
- }
35
- };
36
-
37
- export let jsx = (
38
- tagName: string,
39
- properties: VNodeProperties | null,
40
- ...childNodes: VNodeChild[]
41
- ): VNode => {
42
- if (childNodes.length === 1 && typeof childNodes[0] === "string") {
43
- return {
44
- vnodeSelector: tagName,
45
- properties: properties || undefined,
46
- children: undefined,
47
- text: childNodes[0],
48
- domNode: null,
49
- };
50
- }
51
- let children: VNode[] = [];
52
- appendChildren(childNodes, children);
53
- return {
54
- vnodeSelector: tagName,
55
- properties: properties || undefined,
56
- children: children,
57
- text: undefined,
58
- domNode: null,
59
- };
60
- };
61
-
1
+ import { VNode, VNodeChild, VNodeProperties } from "./interfaces";
2
+
3
+ declare global {
4
+ function jsx(
5
+ tagName: string,
6
+ properties: VNodeProperties | null,
7
+ ...children: (VNode | string)[]
8
+ ): VNode;
9
+ }
10
+
11
+ let toTextVNode = (data: any): VNode => {
12
+ return {
13
+ vnodeSelector: "",
14
+ properties: undefined,
15
+ children: undefined,
16
+ text: data.toString(),
17
+ domNode: null,
18
+ };
19
+ };
20
+
21
+ let appendChildren = (insertions: any[], main: VNode[]) => {
22
+ for (let i = 0, length = insertions.length; i < length; i++) {
23
+ let item = insertions[i];
24
+ if (Array.isArray(item)) {
25
+ appendChildren(item, main);
26
+ } else {
27
+ if (item !== null && item !== undefined && item !== false) {
28
+ if (!item.hasOwnProperty("vnodeSelector")) {
29
+ item = toTextVNode(item);
30
+ }
31
+ main.push(item);
32
+ }
33
+ }
34
+ }
35
+ };
36
+
37
+ export let jsx = (
38
+ tagName: string,
39
+ properties: VNodeProperties | null,
40
+ ...childNodes: VNodeChild[]
41
+ ): VNode => {
42
+ if (childNodes.length === 1 && typeof childNodes[0] === "string") {
43
+ return {
44
+ vnodeSelector: tagName,
45
+ properties: properties || undefined,
46
+ children: undefined,
47
+ text: childNodes[0],
48
+ domNode: null,
49
+ };
50
+ }
51
+ let children: VNode[] = [];
52
+ appendChildren(childNodes, children);
53
+ return {
54
+ vnodeSelector: tagName,
55
+ properties: properties || undefined,
56
+ children: children,
57
+ text: undefined,
58
+ domNode: null,
59
+ };
60
+ };
61
+
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Tests for Maquette mapping module
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import { createMapping } from './mapping.js';
7
+
8
+ describe('Maquette mapping', () => {
9
+ describe('createMapping', () => {
10
+ interface TestSource {
11
+ id: number;
12
+ name: string;
13
+ }
14
+
15
+ interface TestTarget {
16
+ sourceId: number;
17
+ displayName: string;
18
+ }
19
+
20
+ it('should create a mapping instance', () => {
21
+ const mapping = createMapping<TestSource, TestTarget>(
22
+ (source) => source.id,
23
+ (source) => ({ sourceId: source.id, displayName: source.name }),
24
+ (source, target) => {
25
+ target.displayName = source.name;
26
+ }
27
+ );
28
+
29
+ expect(mapping).toBeDefined();
30
+ expect(mapping.results).toBeDefined();
31
+ expect(mapping.map).toBeDefined();
32
+ expect(Array.isArray(mapping.results)).toBe(true);
33
+ });
34
+
35
+ it('should create initial results from sources', () => {
36
+ const mapping = createMapping<TestSource, TestTarget>(
37
+ (source) => source.id,
38
+ (source) => ({ sourceId: source.id, displayName: source.name }),
39
+ (source, target) => {
40
+ target.displayName = source.name;
41
+ }
42
+ );
43
+
44
+ const sources = [
45
+ { id: 1, name: 'Alice' },
46
+ { id: 2, name: 'Bob' },
47
+ { id: 3, name: 'Charlie' },
48
+ ];
49
+
50
+ mapping.map(sources);
51
+
52
+ expect(mapping.results.length).toBe(3);
53
+ expect(mapping.results[0].sourceId).toBe(1);
54
+ expect(mapping.results[0].displayName).toBe('Alice');
55
+ expect(mapping.results[1].sourceId).toBe(2);
56
+ expect(mapping.results[2].sourceId).toBe(3);
57
+ });
58
+
59
+ it('should update existing results when sources change', () => {
60
+ let createCount = 0;
61
+ let updateCount = 0;
62
+
63
+ const mapping = createMapping<TestSource, TestTarget>(
64
+ (source) => source.id,
65
+ (source) => {
66
+ createCount++;
67
+ return { sourceId: source.id, displayName: source.name };
68
+ },
69
+ (source, target) => {
70
+ updateCount++;
71
+ target.displayName = source.name;
72
+ }
73
+ );
74
+
75
+ // Initial mapping
76
+ mapping.map([
77
+ { id: 1, name: 'Alice' },
78
+ { id: 2, name: 'Bob' },
79
+ ]);
80
+
81
+ expect(createCount).toBe(2);
82
+ expect(updateCount).toBe(0);
83
+
84
+ // Update with same keys but different names
85
+ mapping.map([
86
+ { id: 1, name: 'Alice Updated' },
87
+ { id: 2, name: 'Bob Updated' },
88
+ ]);
89
+
90
+ expect(createCount).toBe(2); // No new creates
91
+ expect(updateCount).toBe(2); // Both updated
92
+ expect(mapping.results[0].displayName).toBe('Alice Updated');
93
+ expect(mapping.results[1].displayName).toBe('Bob Updated');
94
+ });
95
+
96
+ it('should add new items', () => {
97
+ let createCount = 0;
98
+
99
+ const mapping = createMapping<TestSource, TestTarget>(
100
+ (source) => source.id,
101
+ (source) => {
102
+ createCount++;
103
+ return { sourceId: source.id, displayName: source.name };
104
+ },
105
+ (source, target) => {
106
+ target.displayName = source.name;
107
+ }
108
+ );
109
+
110
+ mapping.map([{ id: 1, name: 'Alice' }]);
111
+ expect(createCount).toBe(1);
112
+
113
+ mapping.map([
114
+ { id: 1, name: 'Alice' },
115
+ { id: 2, name: 'Bob' },
116
+ { id: 3, name: 'Charlie' },
117
+ ]);
118
+
119
+ expect(createCount).toBe(3);
120
+ expect(mapping.results.length).toBe(3);
121
+ expect(mapping.results[2].sourceId).toBe(3);
122
+ });
123
+
124
+ it('should remove items', () => {
125
+ const mapping = createMapping<TestSource, TestTarget>(
126
+ (source) => source.id,
127
+ (source) => ({ sourceId: source.id, displayName: source.name }),
128
+ (source, target) => {
129
+ target.displayName = source.name;
130
+ }
131
+ );
132
+
133
+ mapping.map([
134
+ { id: 1, name: 'Alice' },
135
+ { id: 2, name: 'Bob' },
136
+ { id: 3, name: 'Charlie' },
137
+ ]);
138
+
139
+ expect(mapping.results.length).toBe(3);
140
+
141
+ mapping.map([{ id: 2, name: 'Bob' }]);
142
+
143
+ expect(mapping.results.length).toBe(1);
144
+ expect(mapping.results[0].sourceId).toBe(2);
145
+ });
146
+
147
+ it('should handle reordering of items', () => {
148
+ let createCount = 0;
149
+ let updateCount = 0;
150
+
151
+ const mapping = createMapping<TestSource, TestTarget>(
152
+ (source) => source.id,
153
+ (source) => {
154
+ createCount++;
155
+ return { sourceId: source.id, displayName: source.name };
156
+ },
157
+ (source, target) => {
158
+ updateCount++;
159
+ target.displayName = source.name;
160
+ }
161
+ );
162
+
163
+ mapping.map([
164
+ { id: 1, name: 'Alice' },
165
+ { id: 2, name: 'Bob' },
166
+ { id: 3, name: 'Charlie' },
167
+ ]);
168
+
169
+ const originalResults = [...mapping.results];
170
+
171
+ // Reverse order
172
+ mapping.map([
173
+ { id: 3, name: 'Charlie' },
174
+ { id: 2, name: 'Bob' },
175
+ { id: 1, name: 'Alice' },
176
+ ]);
177
+
178
+ // Should reuse existing results, not create new ones
179
+ expect(createCount).toBe(3);
180
+ expect(mapping.results.length).toBe(3);
181
+ expect(mapping.results[0].sourceId).toBe(3);
182
+ expect(mapping.results[1].sourceId).toBe(2);
183
+ expect(mapping.results[2].sourceId).toBe(1);
184
+
185
+ // Results should be reordered, not recreated
186
+ expect(mapping.results[0]).toBe(originalResults[2]);
187
+ expect(mapping.results[1]).toBe(originalResults[1]);
188
+ expect(mapping.results[2]).toBe(originalResults[0]);
189
+ });
190
+
191
+ it('should handle mixed add, remove, reorder operations', () => {
192
+ const mapping = createMapping<TestSource, TestTarget>(
193
+ (source) => source.id,
194
+ (source) => ({ sourceId: source.id, displayName: source.name }),
195
+ (source, target) => {
196
+ target.displayName = source.name;
197
+ }
198
+ );
199
+
200
+ mapping.map([
201
+ { id: 1, name: 'Alice' },
202
+ { id: 2, name: 'Bob' },
203
+ { id: 3, name: 'Charlie' },
204
+ ]);
205
+
206
+ mapping.map([
207
+ { id: 4, name: 'David' }, // New
208
+ { id: 2, name: 'Bob' }, // Existing, reordered
209
+ { id: 5, name: 'Eve' }, // New
210
+ ]);
211
+
212
+ expect(mapping.results.length).toBe(3);
213
+ expect(mapping.results[0].sourceId).toBe(4);
214
+ expect(mapping.results[1].sourceId).toBe(2);
215
+ expect(mapping.results[2].sourceId).toBe(5);
216
+ });
217
+
218
+ it('should handle string keys', () => {
219
+ interface StringKeySource {
220
+ key: string;
221
+ value: number;
222
+ }
223
+
224
+ const mapping = createMapping<StringKeySource, { val: number }>(
225
+ (source) => source.key,
226
+ (source) => ({ val: source.value }),
227
+ (source, target) => {
228
+ target.val = source.value;
229
+ }
230
+ );
231
+
232
+ mapping.map([
233
+ { key: 'a', value: 1 },
234
+ { key: 'b', value: 2 },
235
+ ]);
236
+
237
+ expect(mapping.results.length).toBe(2);
238
+ expect(mapping.results[0].val).toBe(1);
239
+ expect(mapping.results[1].val).toBe(2);
240
+ });
241
+
242
+ it('should pass correct index to createResult and updateResult', () => {
243
+ const createdIndexes: number[] = [];
244
+ const updatedIndexes: number[] = [];
245
+
246
+ const mapping = createMapping<TestSource, TestTarget>(
247
+ (source) => source.id,
248
+ (source, index) => {
249
+ createdIndexes.push(index);
250
+ return { sourceId: source.id, displayName: source.name };
251
+ },
252
+ (source, target, index) => {
253
+ updatedIndexes.push(index);
254
+ target.displayName = source.name;
255
+ }
256
+ );
257
+
258
+ mapping.map([
259
+ { id: 1, name: 'Alice' },
260
+ { id: 2, name: 'Bob' },
261
+ { id: 3, name: 'Charlie' },
262
+ ]);
263
+
264
+ expect(createdIndexes).toEqual([0, 1, 2]);
265
+
266
+ mapping.map([
267
+ { id: 1, name: 'Alice' },
268
+ { id: 2, name: 'Bob' },
269
+ { id: 3, name: 'Charlie' },
270
+ ]);
271
+
272
+ expect(updatedIndexes).toEqual([0, 1, 2]);
273
+ });
274
+
275
+ it('should handle empty arrays', () => {
276
+ const mapping = createMapping<TestSource, TestTarget>(
277
+ (source) => source.id,
278
+ (source) => ({ sourceId: source.id, displayName: source.name }),
279
+ (source, target) => {
280
+ target.displayName = source.name;
281
+ }
282
+ );
283
+
284
+ mapping.map([]);
285
+ expect(mapping.results.length).toBe(0);
286
+
287
+ mapping.map([{ id: 1, name: 'Alice' }]);
288
+ expect(mapping.results.length).toBe(1);
289
+
290
+ mapping.map([]);
291
+ expect(mapping.results.length).toBe(0);
292
+ });
293
+ });
294
+ });
@@ -1,56 +1,56 @@
1
- import { Mapping } from "./interfaces";
2
-
3
- /**
4
- * Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.
5
- * See {@link http://maquettejs.org/docs/arrays.html Working with arrays}.
6
- *
7
- * @param <Source> The type of source items. A database-record for instance.
8
- * @param <Target> The type of target items. A [[MaquetteComponent]] for instance.
9
- * @param getSourceKey `function(source)` that must return a key to identify each source object. The result must either be a string or a number.
10
- * @param createResult `function(source, index)` that must create a new result object from a given source. This function is identical
11
- * to the `callback` argument in `Array.map(callback)`.
12
- * @param updateResult `function(source, target, index)` that updates a result to an updated source.
13
- */
14
- export let createMapping = <Source, Target>(
15
- getSourceKey: (source: Source) => string | number,
16
- createResult: (source: Source, index: number) => Target,
17
- updateResult: (source: Source, target: Target, index: number) => void
18
- ): Mapping<Source, Target> => {
19
- let keys = [] as unknown[];
20
- let results = [] as Target[];
21
-
22
- return {
23
- results: results,
24
- map: (newSources: Source[]) => {
25
- let newKeys = newSources.map(getSourceKey);
26
- let oldTargets = results.slice();
27
- let oldIndex = 0;
28
- for (let i = 0; i < newSources.length; i++) {
29
- let source = newSources[i];
30
- let sourceKey = newKeys[i];
31
- if (sourceKey === keys[oldIndex]) {
32
- results[i] = oldTargets[oldIndex];
33
- updateResult(source, oldTargets[oldIndex], i);
34
- oldIndex++;
35
- } else {
36
- let found = false;
37
- for (let j = 1; j < keys.length + 1; j++) {
38
- let searchIndex = (oldIndex + j) % keys.length;
39
- if (keys[searchIndex] === sourceKey) {
40
- results[i] = oldTargets[searchIndex];
41
- updateResult(newSources[i], oldTargets[searchIndex], i);
42
- oldIndex = searchIndex + 1;
43
- found = true;
44
- break;
45
- }
46
- }
47
- if (!found) {
48
- results[i] = createResult(source, i);
49
- }
50
- }
51
- }
52
- results.length = newSources.length;
53
- keys = newKeys;
54
- },
55
- };
56
- };
1
+ import { Mapping } from "./interfaces";
2
+
3
+ /**
4
+ * Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.
5
+ * See {@link http://maquettejs.org/docs/arrays.html Working with arrays}.
6
+ *
7
+ * @param <Source> The type of source items. A database-record for instance.
8
+ * @param <Target> The type of target items. A [[MaquetteComponent]] for instance.
9
+ * @param getSourceKey `function(source)` that must return a key to identify each source object. The result must either be a string or a number.
10
+ * @param createResult `function(source, index)` that must create a new result object from a given source. This function is identical
11
+ * to the `callback` argument in `Array.map(callback)`.
12
+ * @param updateResult `function(source, target, index)` that updates a result to an updated source.
13
+ */
14
+ export let createMapping = <Source, Target>(
15
+ getSourceKey: (source: Source) => string | number,
16
+ createResult: (source: Source, index: number) => Target,
17
+ updateResult: (source: Source, target: Target, index: number) => void
18
+ ): Mapping<Source, Target> => {
19
+ let keys = [] as unknown[];
20
+ let results = [] as Target[];
21
+
22
+ return {
23
+ results: results,
24
+ map: (newSources: Source[]) => {
25
+ let newKeys = newSources.map(getSourceKey);
26
+ let oldTargets = results.slice();
27
+ let oldIndex = 0;
28
+ for (let i = 0; i < newSources.length; i++) {
29
+ let source = newSources[i];
30
+ let sourceKey = newKeys[i];
31
+ if (sourceKey === keys[oldIndex]) {
32
+ results[i] = oldTargets[oldIndex];
33
+ updateResult(source, oldTargets[oldIndex], i);
34
+ oldIndex++;
35
+ } else {
36
+ let found = false;
37
+ for (let j = 1; j < keys.length + 1; j++) {
38
+ let searchIndex = (oldIndex + j) % keys.length;
39
+ if (keys[searchIndex] === sourceKey) {
40
+ results[i] = oldTargets[searchIndex];
41
+ updateResult(newSources[i], oldTargets[searchIndex], i);
42
+ oldIndex = searchIndex + 1;
43
+ found = true;
44
+ break;
45
+ }
46
+ }
47
+ if (!found) {
48
+ results[i] = createResult(source, i);
49
+ }
50
+ }
51
+ }
52
+ results.length = newSources.length;
53
+ keys = newKeys;
54
+ },
55
+ };
56
+ };