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
@@ -0,0 +1,366 @@
1
+ /**
2
+ * Tests for Maquette projection module
3
+ */
4
+
5
+ import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
6
+ import { h } from './h.js';
7
+ import { createProjection } from './projection.js';
8
+
9
+ describe('Maquette projection', () => {
10
+ let container: HTMLDivElement;
11
+
12
+ beforeEach(() => {
13
+ container = document.createElement('div');
14
+ document.body.appendChild(container);
15
+ });
16
+
17
+ afterEach(() => {
18
+ if (container.parentNode) {
19
+ document.body.removeChild(container);
20
+ }
21
+ });
22
+
23
+ describe('Projection lifecycle callbacks', () => {
24
+ it('should call afterUpdate when VNode is updated', () => {
25
+ const afterUpdate = vi.fn();
26
+ const dom = {
27
+ create: (vnode: any) => document.createElement(vnode.vnodeSelector),
28
+ append: (parentNode: any, newNode: any) => parentNode.appendChild(newNode),
29
+ insertBefore: (parentNode: any, newNode: any, referenceNode: any) =>
30
+ parentNode.insertBefore(newNode, referenceNode),
31
+ detach: (domNode: any) => domNode.parentNode?.removeChild(domNode),
32
+ merge: (element: any, vnode: any) => vnode.domNode = element,
33
+ };
34
+
35
+ let vnode = h('div', { afterUpdate }, ['Initial']);
36
+ dom.merge(container, vnode);
37
+
38
+ const projection = createProjection(vnode, {
39
+ styleApplyer: (domNode, styleName, value) => {
40
+ (domNode as any).style[styleName] = value;
41
+ }
42
+ });
43
+
44
+ // Update should trigger afterUpdate
45
+ projection.update(h('div', { afterUpdate }, ['Updated']));
46
+
47
+ expect(afterUpdate).toHaveBeenCalled();
48
+ });
49
+
50
+ it('should call updateAnimation when VNode properties change', () => {
51
+ const updateAnimation = vi.fn();
52
+
53
+ let vnode = h('div', { class: 'initial', updateAnimation });
54
+ const element = document.createElement('div');
55
+ element.className = 'initial';
56
+ vnode.domNode = element;
57
+ container.appendChild(element);
58
+
59
+ const projection = createProjection(vnode, {
60
+ styleApplyer: (domNode, styleName, value) => {
61
+ (domNode as any).style[styleName] = value;
62
+ }
63
+ });
64
+
65
+ // Update with different properties - updateAnimation is called when properties are updated
66
+ const updatedVnode = h('div', { class: 'updated', id: 'test', updateAnimation });
67
+ projection.update(updatedVnode);
68
+
69
+ // updateAnimation is called when DOM updates happen
70
+ expect(updateAnimation).toHaveBeenCalled();
71
+ });
72
+
73
+ it('should call afterCreate callback when element is created', () => {
74
+ const afterCreate = vi.fn();
75
+
76
+ const vnode = h('div', { afterCreate }, ['Content']);
77
+ const element = document.createElement('div');
78
+ vnode.domNode = element;
79
+ container.appendChild(element);
80
+
81
+ const projection = createProjection(vnode, {
82
+ styleApplyer: (domNode, styleName, value) => {
83
+ (domNode as any).style[styleName] = value;
84
+ }
85
+ });
86
+
87
+ // Update to trigger lifecycle
88
+ projection.update(h('div', { afterCreate }, ['Updated']));
89
+
90
+ // afterCreate should have been called during initial render
91
+ expect(projection.domNode).toBe(element);
92
+ });
93
+ });
94
+
95
+ describe('Projection updates', () => {
96
+ it('should throw error when selector changes', () => {
97
+ const vnode = h('div', ['Content']);
98
+ const element = document.createElement('div');
99
+ vnode.domNode = element;
100
+ container.appendChild(element);
101
+
102
+ const projection = createProjection(vnode, {
103
+ styleApplyer: (domNode, styleName, value) => {
104
+ (domNode as any).style[styleName] = value;
105
+ }
106
+ });
107
+
108
+ expect(() => {
109
+ projection.update(h('span', ['Content']));
110
+ }).toThrow('The selector for the root VNode may not be changed');
111
+ });
112
+
113
+ it('should update text content when text changes', () => {
114
+ const vnode = h('div.text-node', { key: 'test' }, ['Initial text']);
115
+ const element = document.createElement('div');
116
+ element.className = 'text-node';
117
+ element.textContent = 'Initial text';
118
+ vnode.domNode = element;
119
+ container.appendChild(element);
120
+
121
+ const projection = createProjection(vnode, {
122
+ styleApplyer: (domNode, styleName, value) => {
123
+ (domNode as any).style[styleName] = value;
124
+ }
125
+ });
126
+
127
+ projection.update(h('div.text-node', { key: 'test' }, ['Updated text']));
128
+
129
+ expect(element.textContent).toBe('Updated text');
130
+ });
131
+
132
+ it('should handle text node to empty update', () => {
133
+ const vnode = h('div', { key: 'test' }, ['Text']);
134
+ const element = document.createElement('div');
135
+ const textNode = document.createTextNode('Text');
136
+ element.appendChild(textNode);
137
+ vnode.domNode = element;
138
+ container.appendChild(element);
139
+
140
+ const projection = createProjection(vnode, {
141
+ styleApplyer: (domNode, styleName, value) => {
142
+ (domNode as any).style[styleName] = value;
143
+ }
144
+ });
145
+
146
+ // Update to have no text
147
+ projection.update(h('div', { key: 'test' }));
148
+
149
+ expect(element.childNodes.length).toBe(0);
150
+ });
151
+ });
152
+
153
+ describe('SVG namespace handling', () => {
154
+ it('should handle SVG namespace for svg elements', () => {
155
+ const circleChild = h('circle', { r: 10, cx: 20, cy: 20 });
156
+ const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
157
+ circle.setAttribute('r', '10');
158
+ circle.setAttribute('cx', '20');
159
+ circle.setAttribute('cy', '20');
160
+ circleChild.domNode = circle;
161
+
162
+ const svgVnode = h('svg', [circleChild]);
163
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
164
+ svg.appendChild(circle);
165
+ svgVnode.domNode = svg;
166
+ container.appendChild(svg);
167
+
168
+ const projection = createProjection(svgVnode, {
169
+ styleApplyer: (domNode, styleName, value) => {
170
+ (domNode as any).style[styleName] = value;
171
+ }
172
+ });
173
+
174
+ // Update SVG child
175
+ const updatedCircle = h('circle', { r: 15, cx: 20, cy: 20 });
176
+ projection.update(h('svg', [updatedCircle]));
177
+
178
+ expect(svg.children.length).toBe(1);
179
+ expect(svg.children[0].getAttribute('r')).toBe('15');
180
+ });
181
+ });
182
+
183
+ describe('Property updates', () => {
184
+ it('should update boolean attributes correctly', () => {
185
+ const vnode = h('input', { type: 'checkbox', checked: false });
186
+ const input = document.createElement('input') as HTMLInputElement;
187
+ input.type = 'checkbox';
188
+ vnode.domNode = input;
189
+ container.appendChild(input);
190
+
191
+ const projection = createProjection(vnode, {
192
+ styleApplyer: (domNode, styleName, value) => {
193
+ (domNode as any).style[styleName] = value;
194
+ }
195
+ });
196
+
197
+ projection.update(h('input', { type: 'checkbox', checked: true }));
198
+
199
+ expect(input.checked).toBe(true);
200
+ });
201
+
202
+ it('should handle style updates', () => {
203
+ const vnode = h('div', { styles: { color: 'red' } });
204
+ const div = document.createElement('div');
205
+ vnode.domNode = div;
206
+ container.appendChild(div);
207
+
208
+ const projection = createProjection(vnode, {
209
+ styleApplyer: (domNode, styleName, value) => {
210
+ (domNode as any).style[styleName] = value;
211
+ }
212
+ });
213
+
214
+ projection.update(h('div', { styles: { color: 'blue', fontSize: '14px' } }));
215
+
216
+ expect(div.style.color).toBe('blue');
217
+ expect(div.style.fontSize).toBe('14px');
218
+ });
219
+
220
+ it('should handle class string updates', () => {
221
+ const vnode = h('div', { class: 'class1 class2' });
222
+ const div = document.createElement('div');
223
+ div.className = 'class1 class2';
224
+ vnode.domNode = div;
225
+ container.appendChild(div);
226
+
227
+ const projection = createProjection(vnode, {
228
+ styleApplyer: (domNode, styleName, value) => {
229
+ (domNode as any).style[styleName] = value;
230
+ }
231
+ });
232
+
233
+ projection.update(h('div', { class: 'class3 class4' }));
234
+
235
+ expect(div.className).toBe('class3 class4');
236
+ });
237
+ });
238
+
239
+ describe('Children updates', () => {
240
+ it('should add new children', () => {
241
+ const vnode = h('div', [h('span', { key: '1' }, ['1'])]);
242
+ const div = document.createElement('div');
243
+ const span = document.createElement('span');
244
+ span.textContent = '1';
245
+ div.appendChild(span);
246
+ vnode.domNode = div;
247
+ vnode.children![0].domNode = span;
248
+ container.appendChild(div);
249
+
250
+ const projection = createProjection(vnode, {
251
+ styleApplyer: (domNode, styleName, value) => {
252
+ (domNode as any).style[styleName] = value;
253
+ }
254
+ });
255
+
256
+ projection.update(h('div', [
257
+ h('span', { key: '1' }, ['1']),
258
+ h('span', { key: '2' }, ['2'])
259
+ ]));
260
+
261
+ expect(div.children.length).toBe(2);
262
+ });
263
+
264
+ it('should remove children', () => {
265
+ const vnode = h('div', [
266
+ h('span', { key: '1' }, ['1']),
267
+ h('span', { key: '2' }, ['2'])
268
+ ]);
269
+ const div = document.createElement('div');
270
+ const span1 = document.createElement('span');
271
+ const span2 = document.createElement('span');
272
+ span1.textContent = '1';
273
+ span2.textContent = '2';
274
+ div.appendChild(span1);
275
+ div.appendChild(span2);
276
+ vnode.domNode = div;
277
+ vnode.children![0].domNode = span1;
278
+ vnode.children![1].domNode = span2;
279
+ container.appendChild(div);
280
+
281
+ const projection = createProjection(vnode, {
282
+ styleApplyer: (domNode, styleName, value) => {
283
+ (domNode as any).style[styleName] = value;
284
+ }
285
+ });
286
+
287
+ projection.update(h('div', [h('span', { key: '1' }, ['1'])]));
288
+
289
+ expect(div.children.length).toBe(1);
290
+ });
291
+
292
+ it('should reorder children with keys', () => {
293
+ const vnode = h('div', [
294
+ h('span', { key: 'a' }, ['A']),
295
+ h('span', { key: 'b' }, ['B']),
296
+ h('span', { key: 'c' }, ['C'])
297
+ ]);
298
+
299
+ const div = document.createElement('div');
300
+ const spanA = document.createElement('span');
301
+ const spanB = document.createElement('span');
302
+ const spanC = document.createElement('span');
303
+ spanA.textContent = 'A';
304
+ spanB.textContent = 'B';
305
+ spanC.textContent = 'C';
306
+ div.appendChild(spanA);
307
+ div.appendChild(spanB);
308
+ div.appendChild(spanC);
309
+ vnode.domNode = div;
310
+ vnode.children![0].domNode = spanA;
311
+ vnode.children![1].domNode = spanB;
312
+ vnode.children![2].domNode = spanC;
313
+ container.appendChild(div);
314
+
315
+ const projection = createProjection(vnode, {
316
+ styleApplyer: (domNode, styleName, value) => {
317
+ (domNode as any).style[styleName] = value;
318
+ }
319
+ });
320
+
321
+ // Reorder: C, A, B
322
+ projection.update(h('div', [
323
+ h('span', { key: 'c' }, ['C']),
324
+ h('span', { key: 'a' }, ['A']),
325
+ h('span', { key: 'b' }, ['B'])
326
+ ]));
327
+
328
+ expect(div.children[0].textContent).toBe('C');
329
+ expect(div.children[1].textContent).toBe('A');
330
+ expect(div.children[2].textContent).toBe('B');
331
+ });
332
+ });
333
+
334
+ describe('Edge cases', () => {
335
+ it('should handle getLastRender', () => {
336
+ const vnode = h('div', ['Content']);
337
+ const element = document.createElement('div');
338
+ vnode.domNode = element;
339
+ container.appendChild(element);
340
+
341
+ const projection = createProjection(vnode, {
342
+ styleApplyer: (domNode, styleName, value) => {
343
+ (domNode as any).style[styleName] = value;
344
+ }
345
+ });
346
+
347
+ const lastRender = projection.getLastRender();
348
+ expect(lastRender.vnodeSelector).toBe('div');
349
+ });
350
+
351
+ it('should expose domNode in projection', () => {
352
+ const vnode = h('div', ['Content']);
353
+ const element = document.createElement('div');
354
+ vnode.domNode = element;
355
+ container.appendChild(element);
356
+
357
+ const projection = createProjection(vnode, {
358
+ styleApplyer: (domNode, styleName, value) => {
359
+ (domNode as any).style[styleName] = value;
360
+ }
361
+ });
362
+
363
+ expect(projection.domNode).toBe(element);
364
+ });
365
+ });
366
+ });