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.
- package/.editorconfig +17 -17
- package/.gitlab-ci.yml +18 -18
- package/CHANGELOG.md +201 -0
- package/demo/sample.js +1 -1
- package/demo/screen.css +16 -16
- package/demo/theme.css +1 -0
- package/dist/p-elements-core-modern.js +1 -1
- package/dist/p-elements-core.js +1 -1
- package/docs/package-lock.json +6897 -6897
- package/docs/package.json +27 -27
- package/docs/src/404.md +8 -8
- package/docs/src/_data/demos/hello-world/hello-world.tsx +35 -35
- package/docs/src/_data/demos/hello-world/index.html +10 -10
- package/docs/src/_data/demos/hello-world/project.json +7 -7
- package/docs/src/_data/demos/timer/demo-timer.tsx +120 -120
- package/docs/src/_data/demos/timer/icons.tsx +62 -62
- package/docs/src/_data/demos/timer/index.html +12 -12
- package/docs/src/_data/demos/timer/project.json +8 -8
- package/docs/src/_data/global.js +13 -13
- package/docs/src/_data/helpers.js +19 -19
- package/docs/src/_includes/layouts/base.njk +30 -30
- package/docs/src/_includes/layouts/playground.njk +40 -40
- package/docs/src/_includes/partials/app-header.njk +8 -8
- package/docs/src/_includes/partials/head.njk +14 -14
- package/docs/src/_includes/partials/nav.njk +19 -19
- package/docs/src/_includes/partials/top-nav.njk +51 -51
- package/docs/src/documentation/custom-element.md +221 -221
- package/docs/src/documentation/decorators/bind.md +71 -71
- package/docs/src/documentation/decorators/custom-element-config.md +63 -63
- package/docs/src/documentation/decorators/property.md +83 -83
- package/docs/src/documentation/decorators/query.md +66 -66
- package/docs/src/documentation/decorators/render-property-on-set.md +60 -60
- package/docs/src/documentation/decorators.md +9 -9
- package/docs/src/documentation/reactive-properties.md +53 -53
- package/docs/src/index.d.ts +25 -25
- package/docs/src/index.md +3 -3
- package/docs/src/scripts/components/app-mode-switch/app-mode-switch.css +78 -78
- package/docs/src/scripts/components/app-mode-switch/app-mode-switch.tsx +166 -166
- package/docs/src/scripts/components/app-playground/app-playground.tsx +189 -189
- package/docs/tsconfig.json +22 -22
- package/index.html +15 -2
- package/p-elements-core.d.ts +12 -3
- package/package.json +11 -4
- package/readme.md +206 -206
- package/src/custom-element-controller.test.ts +226 -0
- package/src/custom-element-controller.ts +31 -31
- package/src/custom-element.test.ts +906 -0
- package/src/custom-element.ts +471 -188
- package/src/custom-style-element.ts +4 -1
- package/src/decorators/bind.test.ts +163 -0
- package/src/decorators/bind.ts +46 -46
- package/src/decorators/custom-element-config.ts +17 -17
- package/src/decorators/property.test.ts +279 -0
- package/src/decorators/property.ts +822 -150
- package/src/decorators/query.test.ts +146 -0
- package/src/decorators/query.ts +12 -12
- package/src/decorators/render-property-on-set.ts +3 -3
- package/src/helpers/css.test.ts +150 -0
- package/src/helpers/css.ts +71 -71
- package/src/maquette/cache.test.ts +150 -0
- package/src/maquette/cache.ts +35 -35
- package/src/maquette/dom.test.ts +263 -0
- package/src/maquette/dom.ts +115 -115
- package/src/maquette/h.test.ts +165 -0
- package/src/maquette/h.ts +100 -100
- package/src/maquette/index.ts +12 -12
- package/src/maquette/interfaces.ts +536 -536
- package/src/maquette/jsx.ts +61 -61
- package/src/maquette/mapping.test.ts +294 -0
- package/src/maquette/mapping.ts +56 -56
- package/src/maquette/maquette.test.ts +493 -0
- package/src/maquette/projection.test.ts +366 -0
- package/src/maquette/projection.ts +666 -666
- package/src/maquette/projector.test.ts +351 -0
- package/src/maquette/projector.ts +200 -200
- package/src/sample/mixin/highlight.tsx +33 -32
- package/src/sample/sample.tsx +167 -7
- package/src/test-setup.ts +85 -0
- package/src/test-utils.ts +223 -0
- package/tsconfig.json +1 -0
- package/vitest.config.ts +41 -0
- 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
|
+
});
|