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,493 @@
1
+ /**
2
+ * Tests for Maquette integration with CustomElement
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import '../test-setup.js';
7
+ import { CustomElement } from '../custom-element.js';
8
+ import { customElementConfig } from '../decorators/custom-element-config.js';
9
+ import { property } from '../decorators/property.js';
10
+ import { h } from './h.js';
11
+ import { generateUniqueTagName } from '../test-setup.js';
12
+ import { waitForRender } from '../test-utils.js';
13
+
14
+ describe('Maquette rendering', () => {
15
+ describe('h() function', () => {
16
+ it('should render simple vnodes', async () => {
17
+ const tagName = generateUniqueTagName('maquette-test');
18
+
19
+ @customElementConfig({ tagName })
20
+ class MaquetteTest extends CustomElement {
21
+ static style = ':host { display: block; }';
22
+
23
+ render() {
24
+ return h('div', ['Hello World']);
25
+ }
26
+ }
27
+
28
+ const el = document.createElement(tagName) as MaquetteTest;
29
+ document.body.appendChild(el);
30
+ await waitForRender(el);
31
+
32
+ const div = el.shadowRoot.querySelector('div');
33
+ expect(div).toBeDefined();
34
+ expect(div.textContent).toBe('Hello World');
35
+
36
+ document.body.removeChild(el);
37
+ });
38
+
39
+ it('should render nested vnodes', async () => {
40
+ const tagName = generateUniqueTagName('maquette-test');
41
+
42
+ @customElementConfig({ tagName })
43
+ class MaquetteTest extends CustomElement {
44
+ static style = ':host { display: block; }';
45
+
46
+ render() {
47
+ return h('div', [
48
+ h('span', { class: 'child' }, ['Child 1']),
49
+ h('span', { class: 'child' }, ['Child 2'])
50
+ ]);
51
+ }
52
+ }
53
+
54
+ const el = document.createElement(tagName) as MaquetteTest;
55
+ document.body.appendChild(el);
56
+ await waitForRender(el);
57
+
58
+ const spans = el.shadowRoot.querySelectorAll('span');
59
+ expect(spans.length).toBe(2);
60
+ expect(spans[0].textContent).toBe('Child 1');
61
+ expect(spans[1].textContent).toBe('Child 2');
62
+
63
+ document.body.removeChild(el);
64
+ });
65
+
66
+ it('should render with properties', async () => {
67
+ const tagName = generateUniqueTagName('maquette-test');
68
+
69
+ @customElementConfig({ tagName })
70
+ class MaquetteTest extends CustomElement {
71
+ static style = ':host { display: block; }';
72
+
73
+ render() {
74
+ return h('button', {
75
+ class: 'btn',
76
+ disabled: true,
77
+ 'data-test': 'value'
78
+ }, ['Click me']);
79
+ }
80
+ }
81
+
82
+ const el = document.createElement(tagName) as MaquetteTest;
83
+ document.body.appendChild(el);
84
+ await waitForRender(el);
85
+
86
+ const button = el.shadowRoot.querySelector('button');
87
+ expect(button).toBeDefined();
88
+ expect(button.className).toBe('btn');
89
+ expect(button.disabled).toBe(true);
90
+ expect(button.getAttribute('data-test')).toBe('value');
91
+
92
+ document.body.removeChild(el);
93
+ });
94
+
95
+ it('should handle dynamic content updates', async () => {
96
+ const tagName = generateUniqueTagName('maquette-test');
97
+
98
+ @customElementConfig({ tagName })
99
+ class MaquetteTest extends CustomElement {
100
+ static style = ':host { display: block; }';
101
+
102
+ @property({ type: String })
103
+ message = 'Initial';
104
+
105
+ render() {
106
+ return h('p', [this.message]);
107
+ }
108
+ }
109
+
110
+ const el = document.createElement(tagName) as MaquetteTest;
111
+ document.body.appendChild(el);
112
+ await waitForRender(el);
113
+
114
+ let p = el.shadowRoot.querySelector('p');
115
+ expect(p.textContent).toBe('Initial');
116
+
117
+ el.message = 'Updated';
118
+ await waitForRender(el);
119
+
120
+ p = el.shadowRoot.querySelector('p');
121
+ expect(p.textContent).toBe('Updated');
122
+
123
+ document.body.removeChild(el);
124
+ });
125
+
126
+ it('should render lists of items', async () => {
127
+ const tagName = generateUniqueTagName('maquette-test');
128
+
129
+ @customElementConfig({ tagName })
130
+ class MaquetteTest extends CustomElement {
131
+ static style = ':host { display: block; }';
132
+
133
+ @property()
134
+ items = ['a', 'b', 'c'];
135
+
136
+ render() {
137
+ return h('ul', this.items.map(item =>
138
+ h('li', { key: item }, [item])
139
+ ));
140
+ }
141
+ }
142
+
143
+ const el = document.createElement(tagName) as MaquetteTest;
144
+ document.body.appendChild(el);
145
+ await waitForRender(el);
146
+
147
+ const lis = el.shadowRoot.querySelectorAll('li');
148
+ expect(lis.length).toBe(3);
149
+ expect(lis[0].textContent).toBe('a');
150
+ expect(lis[1].textContent).toBe('b');
151
+ expect(lis[2].textContent).toBe('c');
152
+
153
+ document.body.removeChild(el);
154
+ });
155
+
156
+ it('should handle conditional rendering', async () => {
157
+ const tagName = generateUniqueTagName('maquette-test');
158
+
159
+ @customElementConfig({ tagName })
160
+ class MaquetteTest extends CustomElement {
161
+ static style = ':host { display: block; }';
162
+
163
+ @property({ type: Boolean })
164
+ showContent = false;
165
+
166
+ render() {
167
+ return h('div', [
168
+ this.showContent ? h('p', ['Visible']) : null
169
+ ]);
170
+ }
171
+ }
172
+
173
+ const el = document.createElement(tagName) as MaquetteTest;
174
+ document.body.appendChild(el);
175
+ await waitForRender(el);
176
+
177
+ let p = el.shadowRoot.querySelector('p');
178
+ expect(p).toBeNull();
179
+
180
+ el.showContent = true;
181
+ await waitForRender(el);
182
+
183
+ p = el.shadowRoot.querySelector('p');
184
+ expect(p).toBeDefined();
185
+ expect(p.textContent).toBe('Visible');
186
+
187
+ document.body.removeChild(el);
188
+ });
189
+ });
190
+
191
+ describe('Event handlers', () => {
192
+ it('should attach event handlers', async () => {
193
+ const tagName = generateUniqueTagName('event-test');
194
+ let clicked = false;
195
+
196
+ @customElementConfig({ tagName })
197
+ class EventTest extends CustomElement {
198
+ static style = ':host { display: block; }';
199
+
200
+ handleClick() {
201
+ clicked = true;
202
+ }
203
+
204
+ render() {
205
+ return h('button', { onclick: () => this.handleClick() }, ['Click']);
206
+ }
207
+ }
208
+
209
+ const el = document.createElement(tagName) as EventTest;
210
+ document.body.appendChild(el);
211
+ await waitForRender(el);
212
+
213
+ const button = el.shadowRoot.querySelector('button');
214
+ button.click();
215
+
216
+ expect(clicked).toBe(true);
217
+
218
+ document.body.removeChild(el);
219
+ });
220
+
221
+ it('should update event handlers', async () => {
222
+ const tagName = generateUniqueTagName('event-test');
223
+ let counter = 0;
224
+
225
+ @customElementConfig({ tagName })
226
+ class EventTest extends CustomElement {
227
+ static style = ':host { display: block; }';
228
+
229
+ @property({ type: Number })
230
+ multiplier = 1;
231
+
232
+ handleClick() {
233
+ counter += this.multiplier;
234
+ this.renderNow();
235
+ }
236
+
237
+ render() {
238
+ return h('button', { onclick: () => this.handleClick() }, [`Count: ${counter}`]);
239
+ }
240
+ }
241
+
242
+ const el = document.createElement(tagName) as EventTest;
243
+ document.body.appendChild(el);
244
+ await waitForRender(el);
245
+
246
+ const button = el.shadowRoot.querySelector('button');
247
+
248
+ button.click();
249
+ await waitForRender(el);
250
+ expect(counter).toBe(1);
251
+
252
+ el.multiplier = 5;
253
+ await waitForRender(el);
254
+ button.click();
255
+ await waitForRender(el);
256
+ expect(counter).toBe(6); // 1 + 5
257
+
258
+ document.body.removeChild(el);
259
+ });
260
+ });
261
+
262
+ describe('Advanced rendering patterns', () => {
263
+ it('should handle conditional rendering', async () => {
264
+ const tagName = generateUniqueTagName('maquette-test');
265
+
266
+ @customElementConfig({ tagName })
267
+ class MaquetteTest extends CustomElement {
268
+ static style = ':host { display: block; }';
269
+
270
+ @property({ type: Boolean })
271
+ showMessage = false;
272
+
273
+ render() {
274
+ return h('div', [
275
+ this.showMessage ? h('span.message', ['Visible']) : null
276
+ ].filter(Boolean) as any);
277
+ }
278
+ }
279
+
280
+ const el = document.createElement(tagName) as MaquetteTest;
281
+ document.body.appendChild(el);
282
+ await waitForRender(el);
283
+
284
+ expect(el.shadowRoot.querySelector('.message')).toBeNull();
285
+
286
+ el.showMessage = true;
287
+ await waitForRender(el);
288
+
289
+ expect(el.shadowRoot.querySelector('.message')).toBeDefined();
290
+ expect(el.shadowRoot.querySelector('.message')?.textContent).toBe('Visible');
291
+
292
+ document.body.removeChild(el);
293
+ });
294
+
295
+ it('should handle list rendering with keys', async () => {
296
+ const tagName = generateUniqueTagName('maquette-test');
297
+
298
+ @customElementConfig({ tagName })
299
+ class MaquetteTest extends CustomElement {
300
+ static style = ':host { display: block; }';
301
+
302
+ @property()
303
+ items: string[] = ['a', 'b', 'c'];
304
+
305
+ render() {
306
+ return h('ul', this.items.map(item =>
307
+ h('li', { key: item }, [item])
308
+ ));
309
+ }
310
+ }
311
+
312
+ const el = document.createElement(tagName) as MaquetteTest;
313
+ document.body.appendChild(el);
314
+ await waitForRender(el);
315
+
316
+ expect(el.shadowRoot.querySelectorAll('li').length).toBe(3);
317
+
318
+ el.items = ['b', 'c', 'd'];
319
+ await waitForRender(el);
320
+
321
+ expect(el.shadowRoot.querySelectorAll('li').length).toBe(3);
322
+ expect(el.shadowRoot.querySelectorAll('li')[0].textContent).toBe('b');
323
+
324
+ document.body.removeChild(el);
325
+ });
326
+
327
+ it('should handle nested component structures', async () => {
328
+ const tagName = generateUniqueTagName('maquette-test');
329
+
330
+ @customElementConfig({ tagName })
331
+ class MaquetteTest extends CustomElement {
332
+ static style = ':host { display: block; }';
333
+
334
+ render() {
335
+ return h('div.container', [
336
+ h('header', [
337
+ h('h1', ['Title']),
338
+ h('nav', [
339
+ h('a', { href: '#' }, ['Link 1']),
340
+ h('a', { href: '#' }, ['Link 2']),
341
+ ])
342
+ ]),
343
+ h('main', [
344
+ h('article', [
345
+ h('p', ['Content'])
346
+ ])
347
+ ]),
348
+ h('footer', ['Footer'])
349
+ ]);
350
+ }
351
+ }
352
+
353
+ const el = document.createElement(tagName) as MaquetteTest;
354
+ document.body.appendChild(el);
355
+ await waitForRender(el);
356
+
357
+ expect(el.shadowRoot.querySelector('h1')?.textContent).toBe('Title');
358
+ expect(el.shadowRoot.querySelectorAll('nav a').length).toBe(2);
359
+ expect(el.shadowRoot.querySelector('article p')?.textContent).toBe('Content');
360
+
361
+ document.body.removeChild(el);
362
+ });
363
+
364
+ it('should handle dynamic styles', async () => {
365
+ const tagName = generateUniqueTagName('maquette-test');
366
+
367
+ @customElementConfig({ tagName })
368
+ class MaquetteTest extends CustomElement {
369
+ static style = ':host { display: block; }';
370
+
371
+ @property({ type: String })
372
+ color = 'red';
373
+
374
+ render() {
375
+ return h('div', {
376
+ styles: {
377
+ color: this.color,
378
+ fontSize: '16px'
379
+ }
380
+ }, ['Styled text']);
381
+ }
382
+ }
383
+
384
+ const el = document.createElement(tagName) as MaquetteTest;
385
+ document.body.appendChild(el);
386
+ await waitForRender(el);
387
+
388
+ const div = el.shadowRoot.querySelector('div') as HTMLElement;
389
+ expect(div.style.color).toBe('red');
390
+
391
+ el.color = 'blue';
392
+ await waitForRender(el);
393
+
394
+ expect(div.style.color).toBe('blue');
395
+
396
+ document.body.removeChild(el);
397
+ });
398
+
399
+ it('should handle classes object', async () => {
400
+ const tagName = generateUniqueTagName('maquette-test');
401
+
402
+ @customElementConfig({ tagName })
403
+ class MaquetteTest extends CustomElement {
404
+ static style = ':host { display: block; }';
405
+
406
+ @property({ type: Boolean })
407
+ active = false;
408
+
409
+ render() {
410
+ return h('div', {
411
+ classes: {
412
+ 'item': true,
413
+ 'active': this.active,
414
+ 'disabled': false
415
+ }
416
+ }, ['Item']);
417
+ }
418
+ }
419
+
420
+ const el = document.createElement(tagName) as MaquetteTest;
421
+ document.body.appendChild(el);
422
+ await waitForRender(el);
423
+
424
+ const div = el.shadowRoot.querySelector('div') as HTMLElement;
425
+ expect(div.classList.contains('item')).toBe(true);
426
+ expect(div.classList.contains('active')).toBe(false);
427
+ expect(div.classList.contains('disabled')).toBe(false);
428
+
429
+ el.active = true;
430
+ await waitForRender(el);
431
+
432
+ expect(div.classList.contains('active')).toBe(true);
433
+
434
+ document.body.removeChild(el);
435
+ });
436
+
437
+ it('should handle afterCreate callback', async () => {
438
+ const tagName = generateUniqueTagName('maquette-test');
439
+ let afterCreateCalled = false;
440
+
441
+ @customElementConfig({ tagName })
442
+ class MaquetteTest extends CustomElement {
443
+ static style = ':host { display: block; }';
444
+
445
+ render() {
446
+ return h('div', {
447
+ afterCreate: (element: HTMLElement) => {
448
+ afterCreateCalled = true;
449
+ element.setAttribute('data-created', 'true');
450
+ }
451
+ }, ['Content']);
452
+ }
453
+ }
454
+
455
+ const el = document.createElement(tagName) as MaquetteTest;
456
+ document.body.appendChild(el);
457
+ await waitForRender(el);
458
+
459
+ expect(afterCreateCalled).toBe(true);
460
+ expect(el.shadowRoot.querySelector('div')?.getAttribute('data-created')).toBe('true');
461
+
462
+ document.body.removeChild(el);
463
+ });
464
+
465
+ it('should handle data attributes', async () => {
466
+ const tagName = generateUniqueTagName('maquette-test');
467
+
468
+ @customElementConfig({ tagName })
469
+ class MaquetteTest extends CustomElement {
470
+ static style = ':host { display: block; }';
471
+
472
+ render() {
473
+ return h('div', {
474
+ 'data-test': 'value',
475
+ 'data-id': '123',
476
+ 'aria-label': 'Test element'
477
+ }, ['Content']);
478
+ }
479
+ }
480
+
481
+ const el = document.createElement(tagName) as MaquetteTest;
482
+ document.body.appendChild(el);
483
+ await waitForRender(el);
484
+
485
+ const div = el.shadowRoot.querySelector('div') as HTMLElement;
486
+ expect(div.getAttribute('data-test')).toBe('value');
487
+ expect(div.getAttribute('data-id')).toBe('123');
488
+ expect(div.getAttribute('aria-label')).toBe('Test element');
489
+
490
+ document.body.removeChild(el);
491
+ });
492
+ });
493
+ });