@justeattakeaway/pie-webc-core 0.10.0 → 0.12.0-next.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [Changed] - `@validPropertyValues` and `@requiredProperty` decorators to support Lit 3. ([#999](https://github.com/justeattakeaway/pie/pull/999)) by [@siggerzz](https://github.com/siggerzz)
8
+
9
+ ## 0.11.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [Added] - defineCustomElement helper function to protect against duplicate component registration errors ([#905](https://github.com/justeattakeaway/pie/pull/905)) by [@jamieomaguire](https://github.com/jamieomaguire)
14
+
3
15
  ## 0.10.0
4
16
 
5
17
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justeattakeaway/pie-webc-core",
3
- "version": "0.10.0",
3
+ "version": "0.12.0-next.0",
4
4
  "description": "PIE design system base classes, mixins and utilities for web components",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -11,15 +11,11 @@ export const requiredProperty = <T>(componentName: string) => function validateR
11
11
  return this[privatePropertyKey];
12
12
  },
13
13
  set (value: T): void {
14
- const oldValue = this[privatePropertyKey];
15
-
16
14
  if (value === undefined || value === null || (typeof value === 'string' && value.trim() === '')) {
17
15
  console.error(`<${componentName}> Missing required attribute "${propertyKey}"`);
18
16
  }
19
17
  this[privatePropertyKey] = value;
20
-
21
- this.requestUpdate(propertyKey, oldValue);
22
18
  },
19
+ configurable: true,
23
20
  });
24
21
  };
25
-
@@ -25,16 +25,6 @@ describe('requiredProperty', () => {
25
25
  class MockComponent {
26
26
  @requiredProperty('mock-component')
27
27
  color?: string | null;
28
-
29
- private _requestUpdateArgs = {};
30
-
31
- requestUpdate (propertyKey: string, oldValue: unknown) {
32
- this._requestUpdateArgs = { propertyKey, oldValue };
33
- }
34
-
35
- requestUpdateCalledWith () {
36
- return this._requestUpdateArgs;
37
- }
38
28
  }
39
29
 
40
30
  it('should log an error if the property is undefined', () => {
@@ -80,15 +70,4 @@ describe('requiredProperty', () => {
80
70
  // Assert
81
71
  expect(consoleErrorSpy).not.toHaveBeenCalled();
82
72
  });
83
-
84
- it('should call requestUpdate when the property is set', () => {
85
- // Arrange
86
- const mockComponent = new MockComponent();
87
-
88
- // Act
89
- mockComponent.color = 'blue';
90
-
91
- // Assert
92
- expect(mockComponent.requestUpdateCalledWith()).toStrictEqual({ propertyKey: 'color', oldValue: undefined });
93
- });
94
73
  });
@@ -25,16 +25,6 @@ describe('validPropertyValues', () => {
25
25
  class MockComponent {
26
26
  @validPropertyValues('mock-component', ['red', 'green', 'blue'], 'red')
27
27
  color = 'red';
28
-
29
- private _requestUpdateArgs = {};
30
-
31
- requestUpdate (propertyKey: string, oldValue: unknown) {
32
- this._requestUpdateArgs = { propertyKey, oldValue };
33
- }
34
-
35
- requestUpdateCalledWith () {
36
- return this._requestUpdateArgs;
37
- }
38
28
  }
39
29
 
40
30
  it('should allow value to be updated with a valid value', () => {
@@ -75,16 +65,4 @@ describe('validPropertyValues', () => {
75
65
  'Falling back to default value: "red"',
76
66
  );
77
67
  });
78
-
79
- it('should call requestUpdate when the property is set', () => {
80
- // Arrange
81
- const mockComponent = new MockComponent();
82
-
83
- // Act
84
- mockComponent.color = 'yellow';
85
-
86
- // Assert
87
- expect(mockComponent.color).toBe('red');
88
- expect(mockComponent.requestUpdateCalledWith()).toStrictEqual({ propertyKey: 'color', oldValue: 'red' });
89
- });
90
68
  });
@@ -5,7 +5,7 @@
5
5
  * @param defaultValue - The value to fall back on
6
6
  * @returns - The decorator function
7
7
  */
8
- export const validPropertyValues = <T>(componentName: string, validValues: readonly T[], defaultValue: T) => function validatePropertyValues (target: object, propertyKey: string): void {
8
+ export const validPropertyValues = <T>(componentName: string, validValues: readonly T[], defaultValue: T) => function validatePropertyValues (target: object, propertyKey: string) : void {
9
9
  const privatePropertyKey = `#${propertyKey}`;
10
10
 
11
11
  Object.defineProperty(target, propertyKey, {
@@ -13,8 +13,6 @@ export const validPropertyValues = <T>(componentName: string, validValues: reado
13
13
  return this[privatePropertyKey];
14
14
  },
15
15
  set (value: T): void {
16
- const oldValue = this[privatePropertyKey];
17
-
18
16
  if (!validValues.includes(value)) {
19
17
  console.error(
20
18
  `<${componentName}> Invalid value "${value}" provided for property "${propertyKey}".`,
@@ -25,8 +23,7 @@ export const validPropertyValues = <T>(componentName: string, validValues: reado
25
23
  } else {
26
24
  this[privatePropertyKey] = value;
27
25
  }
28
-
29
- this.requestUpdate(propertyKey, oldValue);
30
26
  },
27
+ configurable: true,
31
28
  });
32
29
  };
@@ -0,0 +1,47 @@
1
+ import { LitElement } from 'lit';
2
+
3
+ /**
4
+ * Defines a web component, ensuring that the component is only defined once in the Custom Element Registry.
5
+ *
6
+ * If the component has already been defined with the same name, a warning is logged to the console.
7
+ *
8
+ * @param {string} elementSelector - The selector of the custom element. Must be a valid custom element selector, containing a dash. I.e. 'my-component'
9
+ * @param {typeof LitElement} elementClass - The class that defines the custom element, extending LitElement.
10
+ *
11
+ * @example
12
+ *
13
+ * ```javascript
14
+ * import { css, html, LitElement } from 'lit';
15
+ *
16
+ * // IMPORTANT: Add the following JSDoc comment above your class setting the `@tagname` to the selector you want to use. - This is mandatory.
17
+ * // "@tagname my-component"
18
+ * class MyComponent extends LitElement {
19
+ * static styles = css`
20
+ * :host {
21
+ * display: block;
22
+ * padding: 16px;
23
+ * color: var(--my-component-text-color, black);
24
+ * }
25
+ * `;
26
+ *
27
+ * render() {
28
+ * return html`
29
+ * <p>My custom element content goes here!</p>
30
+ * `;
31
+ * }
32
+ * }
33
+ *
34
+ * defineCustomElement('my-component', MyComponent);
35
+ * ```
36
+ *
37
+ * NOTE: The inclusion of the `@tagname` JSDoc comment above your class is essential for correct functionality. Ensure it matches the tag you're registering.
38
+ *
39
+ * @returns {void}
40
+ */
41
+ export function defineCustomElement (elementSelector: string, elementClass: typeof LitElement): void {
42
+ if (!customElements.get(elementSelector)) {
43
+ customElements.define(elementSelector, elementClass);
44
+ } else {
45
+ console.warn(`PIE Web Component: "${elementSelector}" has already been defined. Please ensure the component is only being defined once in your application.`);
46
+ }
47
+ }
@@ -0,0 +1 @@
1
+ export * from './defineCustomElement';
@@ -0,0 +1,81 @@
1
+ /* eslint-disable max-classes-per-file */
2
+ import { LitElement } from 'lit';
3
+ import {
4
+ vi, expect, it,
5
+ } from 'vitest';
6
+ import { defineCustomElement } from '../defineCustomElement';
7
+
8
+ it('should call console.warn when a component is defined twice', () => {
9
+ // Arrange
10
+ class MockComponentA extends LitElement {}
11
+
12
+ const warnSpy = vi.spyOn(console, 'warn');
13
+ const errorSpy = vi.spyOn(console, 'error');
14
+
15
+ // Act
16
+ defineCustomElement('mock-component-a', MockComponentA);
17
+ defineCustomElement('mock-component-a', MockComponentA);
18
+
19
+ // Assert
20
+ const [[warning]] = warnSpy.mock.calls;
21
+
22
+ expect(warnSpy).toHaveBeenCalledOnce();
23
+ expect(warning).toMatch('PIE Web Component: "mock-component-a" has already been defined. Please ensure the component is only being defined once in your application.');
24
+ expect(errorSpy).not.toHaveBeenCalled();
25
+ expect(customElements.get('mock-component-a')).toBe(MockComponentA);
26
+ });
27
+
28
+ it('should define the component without warnings when called once', () => {
29
+ // Arrange
30
+ class MockComponentB extends LitElement {}
31
+
32
+ const warnSpy = vi.spyOn(console, 'warn');
33
+ const errorSpy = vi.spyOn(console, 'error');
34
+
35
+ // Act
36
+ defineCustomElement('mock-component-b', MockComponentB);
37
+
38
+ // Assert
39
+ expect(warnSpy).not.toHaveBeenCalled();
40
+ expect(errorSpy).not.toHaveBeenCalled();
41
+ expect(customElements.get('mock-component-b')).toBe(MockComponentB);
42
+ });
43
+
44
+ it('should warn when defining the same component name with a different class', () => {
45
+ // Arrange
46
+ class MockComponentC extends LitElement {}
47
+
48
+ class MockComponentD extends LitElement {}
49
+
50
+ const warnSpy = vi.spyOn(console, 'warn');
51
+ const errorSpy = vi.spyOn(console, 'error');
52
+
53
+ // Act
54
+ defineCustomElement('mock-component-c', MockComponentC);
55
+ defineCustomElement('mock-component-c', MockComponentD);
56
+
57
+ // Assert
58
+ const [[warning]] = warnSpy.mock.calls;
59
+
60
+ expect(warnSpy).toHaveBeenCalledOnce();
61
+ expect(warning).toMatch('PIE Web Component: "mock-component-c" has already been defined. Please ensure the component is only being defined once in your application.');
62
+ expect(errorSpy).not.toHaveBeenCalled();
63
+ expect(customElements.get('mock-component-c')).toBe(MockComponentC);
64
+ });
65
+
66
+ it('Should correctly accept component classes that use multiple levels of inheritance', () => {
67
+ // Arrange
68
+ class MockComponentE extends LitElement {}
69
+ class MockComponentF extends MockComponentE {}
70
+
71
+ const warnSpy = vi.spyOn(console, 'warn');
72
+ const errorSpy = vi.spyOn(console, 'error');
73
+
74
+ // Act
75
+ defineCustomElement('mock-component-f', MockComponentF);
76
+
77
+ // Assert
78
+ expect(warnSpy).not.toHaveBeenCalled();
79
+ expect(errorSpy).not.toHaveBeenCalled();
80
+ expect(customElements.get('mock-component-f')).toBe(MockComponentF);
81
+ });
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './mixins';
2
2
  export * from './decorators';
3
+ export * from './functions';