@justeattakeaway/pie-webc-core 0.3.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 +25 -0
- package/LICENSE +17 -0
- package/README.md +7 -0
- package/index.ts +1 -0
- package/package.json +12 -0
- package/src/decorators/index.ts +3 -0
- package/src/decorators/required-property.ts +24 -0
- package/src/decorators/valid-property-values.ts +32 -0
- package/src/index.ts +3 -0
- package/src/mixins/index.ts +1 -0
- package/src/mixins/rtl/rtlMixin.ts +41 -0
- package/src/test-helpers/components/index.ts +1 -0
- package/src/test-helpers/components/web-component-test-wrapper/WebComponentTestWrapper.ts +58 -0
- package/src/test-helpers/components/web-component-test-wrapper/webComponentTestWrapper.scss +20 -0
- package/src/test-helpers/defs.ts +15 -0
- package/src/test-helpers/get-all-prop-combos.ts +83 -0
- package/src/test-helpers/index.ts +4 -0
- package/src/test-helpers/rendering.ts +10 -0
- package/tsconfig.json +30 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [Added] - new WebComponentTestWrapper test component for adding labels to visual tests ([#519](https://github.com/justeattakeaway/pie/pull/519)) by [@jamieomaguire](https://github.com/jamieomaguire)
|
|
8
|
+
|
|
9
|
+
- [Added] - a helper module to generate all prop variants of a given component ([#499](https://github.com/justeattakeaway/pie/pull/499)) by [@jamieomaguire](https://github.com/jamieomaguire)
|
|
10
|
+
|
|
11
|
+
- [Added] - `required` property decorator ([#530](https://github.com/justeattakeaway/pie/pull/530)) by [@raoufswe](https://github.com/raoufswe)
|
|
12
|
+
|
|
13
|
+
- [Changed] - Make pie-webc-core public ([#523](https://github.com/justeattakeaway/pie/pull/523)) by [@jamieomaguire](https://github.com/jamieomaguire)
|
|
14
|
+
|
|
15
|
+
## 0.2.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- [Added] - Property validation decorator ([#491](https://github.com/justeattakeaway/pie/pull/491)) by [@jamieomaguire](https://github.com/jamieomaguire)
|
|
20
|
+
|
|
21
|
+
## 0.1.0
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- [Added] - RTL Mixin and base project ([#478](https://github.com/justeattakeaway/pie/pull/478)) by [@jamieomaguire](https://github.com/jamieomaguire)
|
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright (c) Just Eat Takeaway.com
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
package/README.md
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './src';
|
package/package.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@justeattakeaway/pie-webc-core",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "PIE design system base classes, mixins and utilities for web components",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"author": "JustEatTakeaway - Design System Web Team",
|
|
8
|
+
"license": "Apache-2.0",
|
|
9
|
+
"volta": {
|
|
10
|
+
"extends": "../../../package.json"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A decorator for marking a property as required.
|
|
3
|
+
* If the property's value is `undefined`, `null` or empty string, an error is logged.
|
|
4
|
+
* @returns {Function} - The decorator function.
|
|
5
|
+
*/
|
|
6
|
+
export const requiredProperty = (componentName: string) => function (target: any, propertyKey: string) : void {
|
|
7
|
+
const privatePropertyKey = `#${propertyKey}`;
|
|
8
|
+
|
|
9
|
+
Object.defineProperty(target, propertyKey, {
|
|
10
|
+
get () : any {
|
|
11
|
+
return this[privatePropertyKey];
|
|
12
|
+
},
|
|
13
|
+
set (value: any) : void {
|
|
14
|
+
const oldValue = this[privatePropertyKey];
|
|
15
|
+
|
|
16
|
+
if (value === undefined || value === null || value === '') {
|
|
17
|
+
console.error(`<${componentName}> Missing required attribute "${propertyKey}"`);
|
|
18
|
+
}
|
|
19
|
+
this[privatePropertyKey] = value;
|
|
20
|
+
|
|
21
|
+
this.requestUpdate(propertyKey, oldValue);
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A decorator for specifying a list of valid values for a property.
|
|
3
|
+
* If this property's setter is called with an invalid value, an error is logged and the default value will be assigned instead.
|
|
4
|
+
* @param validValues - The array of valid values
|
|
5
|
+
* @param defaultValue - The value to fall back on
|
|
6
|
+
* @returns - The decorator function
|
|
7
|
+
*/
|
|
8
|
+
export const validPropertyValues = (componentName: string, validValues: any[], defaultValue: any) => function (target: any, propertyKey: string) : void {
|
|
9
|
+
const privatePropertyKey = `#${propertyKey}`;
|
|
10
|
+
|
|
11
|
+
Object.defineProperty(target, propertyKey, {
|
|
12
|
+
get () : any {
|
|
13
|
+
return this[privatePropertyKey];
|
|
14
|
+
},
|
|
15
|
+
set (value: any) : void {
|
|
16
|
+
const oldValue = this[privatePropertyKey];
|
|
17
|
+
|
|
18
|
+
if (!validValues.includes(value)) {
|
|
19
|
+
console.error(
|
|
20
|
+
`<${componentName}> Invalid value "${value}" provided for property "${propertyKey}".`,
|
|
21
|
+
`Must be one of: ${validValues.join(' | ')}.`,
|
|
22
|
+
`Falling back to default value: "${defaultValue}"`,
|
|
23
|
+
);
|
|
24
|
+
this[privatePropertyKey] = defaultValue;
|
|
25
|
+
} else {
|
|
26
|
+
this[privatePropertyKey] = value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.requestUpdate(propertyKey, oldValue);
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './rtl/rtlMixin';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* eslint-disable max-classes-per-file */
|
|
2
|
+
import { LitElement } from 'lit';
|
|
3
|
+
import { property } from 'lit/decorators/property.js';
|
|
4
|
+
|
|
5
|
+
type Constructor<T> = new (...args: any[]) => T;
|
|
6
|
+
|
|
7
|
+
declare class RTLInterface {
|
|
8
|
+
dir: string;
|
|
9
|
+
isRTL: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const RtlMixin =
|
|
13
|
+
<T extends Constructor<LitElement>>(superClass: T) => {
|
|
14
|
+
class RTLElement extends superClass {
|
|
15
|
+
@property({ type: String })
|
|
16
|
+
dir = '';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns true if the element is in Right to Left mode.
|
|
20
|
+
* If a dir attribute is not explicitly set on the web component
|
|
21
|
+
* then it falls back to the nearest parent with a dir attribute set.
|
|
22
|
+
*
|
|
23
|
+
* A dir attribute being set will result in a reactive property.
|
|
24
|
+
* If the component falls back to a parent dir attribute then the value
|
|
25
|
+
* will not be reactive and is only computed once
|
|
26
|
+
*/
|
|
27
|
+
get isRTL () : boolean {
|
|
28
|
+
if (this.dir === 'ltr') {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (this.dir === 'rtl' || window?.getComputedStyle(this)?.direction === 'rtl') {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return RTLElement as Constructor<RTLInterface> & T;
|
|
41
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './web-component-test-wrapper/WebComponentTestWrapper';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { LitElement, html, unsafeCSS } from 'lit'; // eslint-disable-line import/no-extraneous-dependencies
|
|
2
|
+
import { property } from 'lit/decorators.js';
|
|
3
|
+
import styles from './webComponentTestWrapper.scss?inline';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This is a Web Component used for visual testing purposes.
|
|
7
|
+
* It allows us to wrap a component we'd like to test in a container
|
|
8
|
+
* that displays the component's props as a label.
|
|
9
|
+
*
|
|
10
|
+
* Components can be tested without this, but it's useful if your tests
|
|
11
|
+
* require additional markup when testing a component.
|
|
12
|
+
*/
|
|
13
|
+
export class WebComponentTestWrapper extends LitElement {
|
|
14
|
+
static styles = unsafeCSS(styles);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The prop key and values to display above the component.
|
|
18
|
+
* This should be a single string representing a comma separated list of prop key/value pairs.
|
|
19
|
+
* Such as: 'size: small, isFullWidth: true'
|
|
20
|
+
*/
|
|
21
|
+
@property({ type: String })
|
|
22
|
+
propKeyValues = '';
|
|
23
|
+
|
|
24
|
+
// Renders a string such as 'size: small, isFullWidth: true'
|
|
25
|
+
// as HTML such as:
|
|
26
|
+
// <p class="c-webComponentTestWrapper-label"><b>size</b>: <code>small</code></p>
|
|
27
|
+
// <p class="c-webComponentTestWrapper-label"><b>isFullWidth</b>: <code>true</code></p>
|
|
28
|
+
_renderPropKeyValues () {
|
|
29
|
+
return this.propKeyValues.split(',').map((propKeyValueString) => {
|
|
30
|
+
const [key, value] = propKeyValueString.split(':');
|
|
31
|
+
|
|
32
|
+
return html`<p class="c-webComponentTestWrapper-label"><b>${key}</b>: <code>${value}</code></p>`;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// eslint-disable-next-line class-methods-use-this
|
|
37
|
+
render () {
|
|
38
|
+
return html`
|
|
39
|
+
<div class="c-webComponentTestWrapper">
|
|
40
|
+
${this._renderPropKeyValues()}
|
|
41
|
+
<div class="c-webComponentTestWrapper-slot">
|
|
42
|
+
<slot></slot>
|
|
43
|
+
</div>
|
|
44
|
+
</div>`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const componentSelector = 'web-component-test-wrapper';
|
|
49
|
+
|
|
50
|
+
if (!customElements.get(componentSelector)) {
|
|
51
|
+
customElements.define(componentSelector, WebComponentTestWrapper);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare global {
|
|
55
|
+
interface HTMLElementTagNameMap {
|
|
56
|
+
[componentSelector]: WebComponentTestWrapper;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.c-webComponentTestWrapper {
|
|
2
|
+
padding-block: var(--dt-spacing-c);
|
|
3
|
+
padding-inline: var(--dt-spacing-e);
|
|
4
|
+
font-family: var(--dt-font-interactive-m-family);
|
|
5
|
+
font-size: calc(var(--dt-font-size-20) * 1px);
|
|
6
|
+
border: 1px solid var(--dt-color-background-dark);
|
|
7
|
+
display: grid;
|
|
8
|
+
grid-template-columns: 1fr 1fr;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.c-webComponentTestWrapper-label {
|
|
12
|
+
margin-block: var(--dt-spacing-c);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.c-webComponentTestWrapper-slot {
|
|
16
|
+
padding: var(--dt-spacing-c);
|
|
17
|
+
border: 1px dashed var(--dt-color-background-dark);
|
|
18
|
+
grid-column: 1 / 3;
|
|
19
|
+
margin-block-start: var(--dt-spacing-c);
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
export type PropObject = {
|
|
3
|
+
[key: string]: any;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type WebComponentPropValues = {
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type WebComponentTestInput = {
|
|
11
|
+
propValues: WebComponentPropValues;
|
|
12
|
+
renderedString: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type WebComponentRenderFn = (propVals: WebComponentPropValues) => string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { PropObject, WebComponentPropValues } from './defs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate all possible combinations of properties for a given object.
|
|
5
|
+
*
|
|
6
|
+
* @param {PropObject} obj - The object containing properties for which combinations are to be generated.
|
|
7
|
+
* Each property value can any data type.
|
|
8
|
+
*
|
|
9
|
+
* @returns {WebComponentPropValues[]} An array of objects, where each object is a unique combination of web component property values.
|
|
10
|
+
*/
|
|
11
|
+
export const getAllPropCombinations = (obj: PropObject): WebComponentPropValues[] => {
|
|
12
|
+
const propertyKeys = Object.keys(obj);
|
|
13
|
+
const combinationsOfPropValues: WebComponentPropValues[] = [];
|
|
14
|
+
|
|
15
|
+
// This function generates combinations of properties from a given object.
|
|
16
|
+
// It does this by recursively concatenating each property value to an 'accumulatedPropertyValues' array,
|
|
17
|
+
// and adding the resulting combination to a 'WebComponentPropValues' array when it reaches the end of the keys.
|
|
18
|
+
function generatePropCombinations (accumulatedPropertyValues: any[], i: number): void {
|
|
19
|
+
// When 'i' equals the length of 'keys', we've reached the end of the keys.
|
|
20
|
+
// This means we've formed a complete combination.
|
|
21
|
+
if (i === propertyKeys.length) {
|
|
22
|
+
// Create an empty object to hold the current WebComponentPropValues.
|
|
23
|
+
const combo: WebComponentPropValues = {};
|
|
24
|
+
// Loop over the 'accumulatedPropertyValues' array, which contains the property values for this combination.
|
|
25
|
+
for (let j = 0; j < accumulatedPropertyValues.length; j++) {
|
|
26
|
+
// Assign each value to the corresponding key in the 'combo' object.
|
|
27
|
+
combo[propertyKeys[j]] = accumulatedPropertyValues[j];
|
|
28
|
+
}
|
|
29
|
+
// Add this combination to the 'combinationsOfPropValues' array.
|
|
30
|
+
combinationsOfPropValues.push(combo);
|
|
31
|
+
// End the recursion for this branch.
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Get the current key and its values from the input object.
|
|
36
|
+
const propertyKey = propertyKeys[i];
|
|
37
|
+
const propertyValues = obj[propertyKey];
|
|
38
|
+
|
|
39
|
+
if (typeof propertyValues === 'boolean') {
|
|
40
|
+
// If the values for this key are a boolean, we generate two combinations:
|
|
41
|
+
// one with the value 'true', and one with the value 'false'.
|
|
42
|
+
generatePropCombinations([...accumulatedPropertyValues, true], i + 1);
|
|
43
|
+
generatePropCombinations([...accumulatedPropertyValues, false], i + 1);
|
|
44
|
+
} else if (Array.isArray(propertyValues)) {
|
|
45
|
+
// If the values for this key are an array, we generate a combination for each value in the array.
|
|
46
|
+
for (let j = 0; j < propertyValues.length; j++) {
|
|
47
|
+
generatePropCombinations([...accumulatedPropertyValues, propertyValues[j]], i + 1);
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
// If the values for this key are neither a boolean nor an array,
|
|
51
|
+
// we simply generate a single combination with the value as is.
|
|
52
|
+
generatePropCombinations([...accumulatedPropertyValues, propertyValues], i + 1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
generatePropCombinations([], 0);
|
|
57
|
+
|
|
58
|
+
return combinationsOfPropValues;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Splits an array of component prop combinations by a particular property value.
|
|
63
|
+
*
|
|
64
|
+
* @param {WebComponentPropValues[]} propValueCombinations - The array of combinations to split.
|
|
65
|
+
* @param {string} property - The property to split by.
|
|
66
|
+
*
|
|
67
|
+
* @returns {Record<string, WebComponentPropValues[]>} An object mapping each unique property value to the combinations that have it.
|
|
68
|
+
*/
|
|
69
|
+
export const splitCombinationsByPropertyValue = (propValueCombinations: WebComponentPropValues[], property: string): Record<string, WebComponentPropValues[]> => propValueCombinations
|
|
70
|
+
.reduce((splitCombinations: Record<string, WebComponentPropValues[]>, combination: WebComponentPropValues) => {
|
|
71
|
+
const propertyValue = combination[property];
|
|
72
|
+
const propertyValueKey = String(propertyValue);
|
|
73
|
+
|
|
74
|
+
if (!(propertyValueKey in splitCombinations)) {
|
|
75
|
+
splitCombinations[propertyValueKey] = [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Add the current combination to the array for its property value
|
|
79
|
+
splitCombinations[propertyValueKey].push(combination);
|
|
80
|
+
|
|
81
|
+
return splitCombinations;
|
|
82
|
+
}, {});
|
|
83
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { WebComponentPropValues, WebComponentTestInput, WebComponentRenderFn } from './defs';
|
|
2
|
+
|
|
3
|
+
export function createTestWebComponent (propVals: WebComponentPropValues, componentRenderFn: WebComponentRenderFn): WebComponentTestInput {
|
|
4
|
+
const testComponent: WebComponentTestInput = {
|
|
5
|
+
propValues: propVals,
|
|
6
|
+
renderedString: componentRenderFn(propVals),
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
return testComponent;
|
|
10
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES6",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"lib": ["es2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"inlineSources": true,
|
|
10
|
+
"outDir": "./compiled",
|
|
11
|
+
"baseUrl": ".",
|
|
12
|
+
"paths": {
|
|
13
|
+
"@/*": ["./src/*"]
|
|
14
|
+
},
|
|
15
|
+
"rootDir": ".",
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noImplicitReturns": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noImplicitAny": true,
|
|
22
|
+
"noImplicitThis": true,
|
|
23
|
+
"moduleResolution": "node",
|
|
24
|
+
"allowSyntheticDefaultImports": true,
|
|
25
|
+
"experimentalDecorators": true,
|
|
26
|
+
"forceConsistentCasingInFileNames": true
|
|
27
|
+
},
|
|
28
|
+
"include": ["src/**/*.ts","./declaration.d.ts", "test/**/*.ts"],
|
|
29
|
+
"exclude": []
|
|
30
|
+
}
|