p-elements-core 1.2.31 → 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 +11 -1
  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
@@ -28,4 +28,7 @@ customElements.whenDefined("custom-style").then(() => {
28
28
  document.body.parentElement.classList.add("custom-style-defined");
29
29
  });
30
30
 
31
- customElements.define("custom-style", CustomStyleElement, { extends: "link" });
31
+ // Only define if not already defined
32
+ if (!customElements.get("custom-style")) {
33
+ customElements.define("custom-style", CustomStyleElement, { extends: "link" });
34
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Tests for @bind decorator
3
+ * Covers method binding to preserve 'this' context
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest';
7
+ import '../test-setup.js';
8
+ import { bind } from './bind.js';
9
+ import { CustomElement } from '../custom-element.js';
10
+ import { customElementConfig } from './custom-element-config.js';
11
+ import { generateUniqueTagName } from '../test-setup.js';
12
+ import { waitForRender } from '../test-utils.js';
13
+
14
+ describe('@bind decorator', () => {
15
+ it('should bind method to instance', async () => {
16
+ const tagName = generateUniqueTagName('bind-test');
17
+
18
+ @customElementConfig({ tagName })
19
+ class BindTest extends CustomElement {
20
+ static style = ':host { display: block; }';
21
+ value = 'bound';
22
+
23
+ @bind
24
+ getValue() {
25
+ return this.value;
26
+ }
27
+
28
+ render() {
29
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
30
+ }
31
+ }
32
+
33
+ const el = document.createElement(tagName) as BindTest;
34
+ document.body.appendChild(el);
35
+ await waitForRender(el);
36
+
37
+ const method = el.getValue;
38
+ expect(method()).toBe('bound');
39
+
40
+ document.body.removeChild(el);
41
+ });
42
+
43
+ it('should preserve this context when method is extracted', async () => {
44
+ const tagName = generateUniqueTagName('bind-test');
45
+
46
+ @customElementConfig({ tagName })
47
+ class BindTest extends CustomElement {
48
+ static style = ':host { display: block; }';
49
+ name = 'test-element';
50
+
51
+ @bind
52
+ getName() {
53
+ return this.name;
54
+ }
55
+
56
+ render() {
57
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
58
+ }
59
+ }
60
+
61
+ const el = document.createElement(tagName) as BindTest;
62
+ document.body.appendChild(el);
63
+ await waitForRender(el);
64
+
65
+ const { getName } = el;
66
+ expect(getName()).toBe('test-element');
67
+
68
+ document.body.removeChild(el);
69
+ });
70
+
71
+ it('should work with callbacks', async () => {
72
+ const tagName = generateUniqueTagName('bind-test');
73
+ let result: string;
74
+
75
+ @customElementConfig({ tagName })
76
+ class BindTest extends CustomElement {
77
+ static style = ':host { display: block; }';
78
+ message = 'callback test';
79
+
80
+ @bind
81
+ handleCallback() {
82
+ return this.message;
83
+ }
84
+
85
+ render() {
86
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
87
+ }
88
+ }
89
+
90
+ const el = document.createElement(tagName) as BindTest;
91
+ document.body.appendChild(el);
92
+ await waitForRender(el);
93
+
94
+ const callback = el.handleCallback;
95
+ result = callback();
96
+
97
+ expect(result).toBe('callback test');
98
+
99
+ document.body.removeChild(el);
100
+ });
101
+
102
+ it('should handle methods with arguments', async () => {
103
+ const tagName = generateUniqueTagName('bind-test');
104
+
105
+ @customElementConfig({ tagName })
106
+ class BindTest extends CustomElement {
107
+ static style = ':host { display: block; }';
108
+
109
+ prefixValue = 'Hello';
110
+
111
+ @bind
112
+ greet(name: string) {
113
+ return `${this.prefixValue}, ${name}!`;
114
+ }
115
+
116
+ render() {
117
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
118
+ }
119
+ }
120
+
121
+ const el = document.createElement(tagName) as BindTest;
122
+ document.body.appendChild(el);
123
+ await waitForRender(el);
124
+
125
+ const extracted = el.greet;
126
+ expect(extracted('World')).toBe('Hello, World!');
127
+
128
+ document.body.removeChild(el);
129
+ });
130
+
131
+ it('should preserve bound method reference', async () => {
132
+ const tagName = generateUniqueTagName('bind-test');
133
+
134
+ @customElementConfig({ tagName })
135
+ class BindTest extends CustomElement {
136
+ static style = ':host { display: block; }';
137
+
138
+ valueNum = 42;
139
+
140
+ @bind
141
+ getValue() {
142
+ return this.valueNum;
143
+ }
144
+
145
+ render() {
146
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
147
+ }
148
+ }
149
+
150
+ const el = document.createElement(tagName) as BindTest;
151
+ document.body.appendChild(el);
152
+ await waitForRender(el);
153
+
154
+ const bound1 = el.getValue;
155
+ const bound2 = el.getValue;
156
+
157
+ // Should return the same bound function
158
+ expect(bound1).toBe(bound2);
159
+ expect(bound1()).toBe(42);
160
+
161
+ document.body.removeChild(el);
162
+ });
163
+ });
@@ -1,46 +1,46 @@
1
- export const bind = (target, key, descriptor) => {
2
- let fn = descriptor.value;
3
- // console.warn("@Bind decorator is deprecated, use arrow function expression");
4
- if (typeof fn !== "function") {
5
- throw new Error(
6
- `@Bind decorator can only be applied to methods not: ${typeof fn}`
7
- );
8
- }
9
-
10
- // In IE11 calling Object.defineProperty has a side-effect of evaluating the
11
- // getter for the property which is being replaced. This causes infinite
12
- // recursion and an "Out of stack space" error.
13
- let definingProperty = false;
14
-
15
- return {
16
- configurable: true,
17
- get() {
18
- if (
19
- definingProperty ||
20
- this === target.prototype ||
21
- this.hasOwnProperty(key) ||
22
- typeof fn !== "function"
23
- ) {
24
- return fn;
25
- }
26
-
27
- let boundFn = fn.bind(this);
28
- definingProperty = true;
29
- Object.defineProperty(this, key, {
30
- configurable: true,
31
- get() {
32
- return boundFn;
33
- },
34
- set(value) {
35
- fn = value;
36
- delete this[key];
37
- },
38
- });
39
- definingProperty = false;
40
- return boundFn;
41
- },
42
- set(value) {
43
- fn = value;
44
- },
45
- };
46
- };
1
+ export const bind = (target, key, descriptor) => {
2
+ let fn = descriptor.value;
3
+ // console.warn("@Bind decorator is deprecated, use arrow function expression");
4
+ if (typeof fn !== "function") {
5
+ throw new Error(
6
+ `@Bind decorator can only be applied to methods not: ${typeof fn}`
7
+ );
8
+ }
9
+
10
+ // In IE11 calling Object.defineProperty has a side-effect of evaluating the
11
+ // getter for the property which is being replaced. This causes infinite
12
+ // recursion and an "Out of stack space" error.
13
+ let definingProperty = false;
14
+
15
+ return {
16
+ configurable: true,
17
+ get() {
18
+ if (
19
+ definingProperty ||
20
+ this === target.prototype ||
21
+ this.hasOwnProperty(key) ||
22
+ typeof fn !== "function"
23
+ ) {
24
+ return fn;
25
+ }
26
+
27
+ let boundFn = fn.bind(this);
28
+ definingProperty = true;
29
+ Object.defineProperty(this, key, {
30
+ configurable: true,
31
+ get() {
32
+ return boundFn;
33
+ },
34
+ set(value) {
35
+ fn = value;
36
+ delete this[key];
37
+ },
38
+ });
39
+ definingProperty = false;
40
+ return boundFn;
41
+ },
42
+ set(value) {
43
+ fn = value;
44
+ },
45
+ };
46
+ };
@@ -1,17 +1,17 @@
1
- export interface IElementConfig {
2
- tagName: string;
3
- options?: {
4
- extends: string;
5
- };
6
- }
7
- export const customElementConfig = (config: IElementConfig) => {
8
- return (Element) => {
9
- if (customElements.get(config.tagName)) {
10
- console.warn(
11
- `Custom element with tag name ${config.tagName} already exists.`
12
- );
13
- return;
14
- }
15
- customElements.define(config.tagName, Element, config.options);
16
- };
17
- };
1
+ export interface IElementConfig {
2
+ tagName: string;
3
+ options?: {
4
+ extends: string;
5
+ };
6
+ }
7
+ export const customElementConfig = (config: IElementConfig) => {
8
+ return (Element) => {
9
+ if (customElements.get(config.tagName)) {
10
+ console.warn(
11
+ `Custom element with tag name ${config.tagName} already exists.`
12
+ );
13
+ return;
14
+ }
15
+ customElements.define(config.tagName, Element, config.options);
16
+ };
17
+ };
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Tests for @property decorator
3
+ * Covers type conversion, attribute reflection, custom converters, and lifecycle integration
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest';
7
+ import '../test-setup.js';
8
+ import { property } from './property.js';
9
+ import { CustomElement } from '../custom-element.js';
10
+ import { customElementConfig } from './custom-element-config.js';
11
+ import { generateUniqueTagName } from '../test-setup.js';
12
+ import { waitForRender } from '../test-utils.js';
13
+
14
+ describe('@property decorator', () => {
15
+ describe('Type: String', () => {
16
+ it('should convert attribute to string property', async () => {
17
+ const tagName = generateUniqueTagName('prop-string');
18
+
19
+ @customElementConfig({ tagName })
20
+ class StringTest extends CustomElement {
21
+ static style = ':host { display: block; }';
22
+ @property({ type: String })
23
+ text = 'default';
24
+
25
+ render() {
26
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
27
+ }
28
+ }
29
+
30
+ const el = document.createElement(tagName) as StringTest;
31
+ el.setAttribute('text', 'hello');
32
+ document.body.appendChild(el);
33
+ await waitForRender(el);
34
+
35
+ expect(el.text).toBe('hello');
36
+
37
+ document.body.removeChild(el);
38
+ });
39
+
40
+ it('should set property from JavaScript', async () => {
41
+ const tagName = generateUniqueTagName('prop-string');
42
+
43
+ @customElementConfig({ tagName })
44
+ class StringTest extends CustomElement {
45
+ static style = ':host { display: block; }';
46
+ @property({ type: String })
47
+ text = 'default';
48
+
49
+ render() {
50
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
51
+ }
52
+ }
53
+
54
+ const el = document.createElement(tagName) as StringTest;
55
+ document.body.appendChild(el);
56
+
57
+ el.text = 'updated';
58
+ await waitForRender(el);
59
+
60
+ expect(el.text).toBe('updated');
61
+
62
+ document.body.removeChild(el);
63
+ });
64
+ });
65
+
66
+ describe('Type: Number', () => {
67
+ it('should convert attribute to number property', async () => {
68
+ const tagName = generateUniqueTagName('prop-number');
69
+
70
+ @customElementConfig({ tagName })
71
+ class NumberTest extends CustomElement {
72
+ static style = ':host { display: block; }';
73
+ @property({ type: Number })
74
+ count = 0;
75
+
76
+ render() {
77
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
78
+ }
79
+ }
80
+
81
+ const el = document.createElement(tagName) as NumberTest;
82
+ el.setAttribute('count', '42');
83
+ document.body.appendChild(el);
84
+ await waitForRender(el);
85
+
86
+ expect(el.count).toBe(42);
87
+
88
+ document.body.removeChild(el);
89
+ });
90
+ });
91
+
92
+ describe('Type: Boolean', () => {
93
+ it('should convert presence of attribute to true', async () => {
94
+ const tagName = generateUniqueTagName('prop-bool');
95
+
96
+ @customElementConfig({ tagName })
97
+ class BooleanTest extends CustomElement {
98
+ static style = ':host { display: block; }';
99
+ @property({ type: Boolean })
100
+ active = false;
101
+
102
+ render() {
103
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
104
+ }
105
+ }
106
+
107
+ const el = document.createElement(tagName) as BooleanTest;
108
+ el.setAttribute('active', '');
109
+ document.body.appendChild(el);
110
+ await waitForRender(el);
111
+
112
+ expect(el.active).toBe(true);
113
+
114
+ document.body.removeChild(el);
115
+ });
116
+ });
117
+
118
+ describe('Attribute reflection (reflect: true)', () => {
119
+ it('should reflect string property to attribute', async () => {
120
+ const tagName = generateUniqueTagName('prop-reflect');
121
+
122
+ @customElementConfig({ tagName })
123
+ class ReflectTest extends CustomElement {
124
+ static style = ':host { display: block; }';
125
+ @property({ type: String, reflect: true })
126
+ status = 'pending';
127
+
128
+ render() {
129
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
130
+ }
131
+ }
132
+
133
+ const el = document.createElement(tagName) as ReflectTest;
134
+ document.body.appendChild(el);
135
+
136
+ el.status = 'complete';
137
+ await waitForRender(el);
138
+
139
+ expect(el.getAttribute('status')).toBe('complete');
140
+
141
+ document.body.removeChild(el);
142
+ });
143
+
144
+ it('should reflect boolean property to attribute', async () => {
145
+ const tagName = generateUniqueTagName('prop-reflect');
146
+
147
+ @customElementConfig({ tagName })
148
+ class ReflectTest extends CustomElement {
149
+ static style = ':host { display: block; }';
150
+ @property({ type: Boolean, reflect: true })
151
+ active = false;
152
+
153
+ render() {
154
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
155
+ }
156
+ }
157
+
158
+ const el = document.createElement(tagName) as ReflectTest;
159
+ document.body.appendChild(el);
160
+
161
+ el.active = true;
162
+ await waitForRender(el);
163
+ expect(el.hasAttribute('active')).toBe(true);
164
+
165
+ el.active = false;
166
+ await waitForRender(el);
167
+ expect(el.hasAttribute('active')).toBe(false);
168
+
169
+ document.body.removeChild(el);
170
+ });
171
+
172
+ it('should reflect number property to attribute', async () => {
173
+ const tagName = generateUniqueTagName('prop-reflect');
174
+
175
+ @customElementConfig({ tagName })
176
+ class ReflectTest extends CustomElement {
177
+ static style = ':host { display: block; }';
178
+ @property({ type: Number, reflect: true })
179
+ count = 0;
180
+
181
+ render() {
182
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
183
+ }
184
+ }
185
+
186
+ const el = document.createElement(tagName) as ReflectTest;
187
+ document.body.appendChild(el);
188
+
189
+ el.count = 42;
190
+ await waitForRender(el);
191
+
192
+ expect(el.getAttribute('count')).toBe('42');
193
+
194
+ document.body.removeChild(el);
195
+ });
196
+ });
197
+
198
+ describe('Property without type conversion', () => {
199
+ it('should handle object properties', async () => {
200
+ const tagName = generateUniqueTagName('prop-object');
201
+
202
+ @customElementConfig({ tagName })
203
+ class ObjectTest extends CustomElement {
204
+ static style = ':host { display: block; }';
205
+ @property()
206
+ data: any = null;
207
+
208
+ render() {
209
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
210
+ }
211
+ }
212
+
213
+ const el = document.createElement(tagName) as ObjectTest;
214
+ document.body.appendChild(el);
215
+
216
+ const testData = { foo: 'bar', num: 123 };
217
+ el.data = testData;
218
+ await waitForRender(el);
219
+
220
+ expect(el.data).toBe(testData);
221
+ expect(el.data.foo).toBe('bar');
222
+
223
+ document.body.removeChild(el);
224
+ });
225
+
226
+ it('should handle array properties', async () => {
227
+ const tagName = generateUniqueTagName('prop-array');
228
+
229
+ @customElementConfig({ tagName })
230
+ class ArrayTest extends CustomElement {
231
+ static style = ':host { display: block; }';
232
+ @property()
233
+ items: string[] = [];
234
+
235
+ render() {
236
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
237
+ }
238
+ }
239
+
240
+ const el = document.createElement(tagName) as ArrayTest;
241
+ document.body.appendChild(el);
242
+
243
+ el.items = ['a', 'b', 'c'];
244
+ await waitForRender(el);
245
+
246
+ expect(el.items.length).toBe(3);
247
+ expect(el.items[0]).toBe('a');
248
+
249
+ document.body.removeChild(el);
250
+ });
251
+ });
252
+
253
+ describe('Custom attribute names', () => {
254
+ it('should support custom attribute names', async () => {
255
+ const tagName = generateUniqueTagName('prop-custom');
256
+
257
+ @customElementConfig({ tagName })
258
+ class CustomAttrTest extends CustomElement {
259
+ static style = ':host { display: block; }';
260
+ @property({ type: String, attribute: 'data-value' })
261
+ value = '';
262
+
263
+ render() {
264
+ return { vnodeSelector: 'div', properties: {}, children: [], text: undefined, domNode: null };
265
+ }
266
+ }
267
+
268
+ const el = document.createElement(tagName) as CustomAttrTest;
269
+ document.body.appendChild(el);
270
+
271
+ el.setAttribute('data-value', 'test');
272
+ await new Promise(resolve => setTimeout(resolve, 10));
273
+
274
+ expect(el.value).toBe('test');
275
+
276
+ document.body.removeChild(el);
277
+ });
278
+ });
279
+ });