@justeattakeaway/pie-webc-core 0.8.0 → 0.9.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 +8 -0
- package/package.json +1 -1
- package/src/index.ts +0 -1
- package/src/mixins/rtl/rtlMixin.ts +74 -33
- package/src/mixins/test/rtlMixin.client.spec.ts +92 -0
- package/src/mixins/test/rtlMixin.server.spec.ts +62 -0
- package/src/mixins/test/rtlMixin.spec.ts +0 -72
- package/src/type-helpers/DependentMap.ts +0 -6
- package/src/type-helpers/index.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [Changed] - Rewrite RTL mixin to remove the dir property. The LitElement base class is a subclass of HTMLElement, so a Lit component inherits all of the standard HTMLElement properties and methods. This means that the dir property is already available on the component and we don't need to add it again. [Reference](https://lit.dev/docs/components/defining/#a-lit-component-is-an-html-element). During SSR, if no dir is provided, it will be inferred from the document.documentElement once it's instantiated on the client. ([#818](https://github.com/justeattakeaway/pie/pull/818)) by [@jamieomaguire](https://github.com/jamieomaguire)
|
|
8
|
+
|
|
9
|
+
- [Changed] - Removed the DependantMap type and replaced all references with Lit's own PropertyValues helper. This provides exactly the same strongly typed map for a component's properties which makes our own type a little redundant. [Reference](https://lit.dev/docs/components/lifecycle/#typescript-types-for-changedproperties) ([#818](https://github.com/justeattakeaway/pie/pull/818)) by [@jamieomaguire](https://github.com/jamieomaguire)
|
|
10
|
+
|
|
3
11
|
## 0.8.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,55 +1,96 @@
|
|
|
1
1
|
/* eslint-disable max-classes-per-file */
|
|
2
|
-
import { LitElement } from 'lit';
|
|
3
|
-
import { property } from 'lit/decorators/property.js';
|
|
2
|
+
import { LitElement, isServer } from 'lit';
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* A type representing a constructor of any class.
|
|
6
|
+
* @typedef {new (...args: any[]) => T} Constructor
|
|
7
|
+
* @template T
|
|
8
|
+
*/
|
|
6
9
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
10
|
type Constructor<T> = new (...args: any[]) => T;
|
|
8
11
|
|
|
9
|
-
type htmlDirAttribute = 'ltr' | 'rtl' | 'auto';
|
|
10
|
-
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
+
* An interface representing the structure of RTL related class.
|
|
14
|
+
* @interface
|
|
13
15
|
*/
|
|
14
|
-
export interface RTLComponentProps {
|
|
15
|
-
dir?: htmlDirAttribute
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// This is just used by the dynamically constructed class below and does not need to be imported or referenced anywhere else
|
|
19
16
|
declare class _RTLInterface {
|
|
20
|
-
|
|
17
|
+
/** A boolean indicating whether the text direction is right-to-left. */
|
|
21
18
|
isRTL: boolean;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
22
|
+
* A mixin to extend LitElement with right-to-left (RTL) text direction handling.
|
|
23
|
+
* This mixin adds a read-only `isRTL` property to the class it's applied to,
|
|
24
|
+
* allowing you to easily determine the text direction within your component.
|
|
25
|
+
*
|
|
26
|
+
* @function
|
|
27
|
+
* @param {Constructor<LitElement>} superClass - The LitElement class to extend with RTL functionality.
|
|
28
|
+
* @returns {Constructor<_RTLInterface> & T} - A new class extending both LitElement and _RTLInterface.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { LitElement, html } from 'lit';
|
|
33
|
+
* import { RtlMixin } from './RtlMixin'; // Adjust the import path as needed
|
|
34
|
+
*
|
|
35
|
+
* class MyElement extends RtlMixin(LitElement) {
|
|
36
|
+
* render() {
|
|
37
|
+
* return html`<p>Text direction is ${this.isRTL ? 'right-to-left' : 'left-to-right'}</p>`;
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* customElements.define('my-element', MyElement);
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* import { LitElement, html } from 'lit';
|
|
47
|
+
* import { RtlMixin } from './RtlMixin'; // Adjust the import path as needed
|
|
48
|
+
*
|
|
49
|
+
* class MyStyledElement extends RtlMixin(LitElement) {
|
|
50
|
+
* render() {
|
|
51
|
+
* return html`<div class="foo" ?isRTL=${this.isRTL}>Content</div>`;
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* customElements.define('my-styled-element', MyStyledElement);
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* The corresponding SCSS to leverage the `isRTL` attribute:
|
|
59
|
+
* ```scss
|
|
60
|
+
* .foo[isRTL] {
|
|
61
|
+
* background-color: red;
|
|
62
|
+
* text-align: right;
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
34
65
|
*/
|
|
35
|
-
|
|
36
66
|
export const RtlMixin =
|
|
37
67
|
<T extends Constructor<LitElement>>(superClass: T) => {
|
|
68
|
+
/**
|
|
69
|
+
* Class representing a LitElement with RTL handling.
|
|
70
|
+
* @extends {LitElement}
|
|
71
|
+
* @implements {_RTLInterface}
|
|
72
|
+
*/
|
|
38
73
|
class RTLElement extends superClass implements _RTLInterface {
|
|
39
|
-
@property({ type: String, reflect: true })
|
|
40
|
-
dir : htmlDirAttribute = document?.documentElement?.dir as htmlDirAttribute ?? 'ltr';
|
|
41
|
-
|
|
42
74
|
/**
|
|
43
|
-
*
|
|
44
|
-
* If
|
|
45
|
-
*
|
|
75
|
+
* A getter to determine whether the text direction is right-to-left (RTL).
|
|
76
|
+
* If the `dir` property is present on the component, it will be used to determine the text direction.
|
|
77
|
+
* If running on the client-side (not SSR) and the `dir` property is not present, the text direction will be inferred
|
|
78
|
+
* from the document's root element. This inference is not available during SSR.
|
|
79
|
+
* In all other cases, it will return `false`, indicating a left-to-right (LTR) text direction.
|
|
46
80
|
*
|
|
47
|
-
*
|
|
48
|
-
* If the component falls back to a parent dir attribute then the value
|
|
49
|
-
* will not be reactive and is only computed once
|
|
81
|
+
* @returns {boolean} - Returns `true` if the text direction is RTL, otherwise `false`.
|
|
50
82
|
*/
|
|
51
|
-
get isRTL ()
|
|
52
|
-
|
|
83
|
+
get isRTL (): boolean {
|
|
84
|
+
if (this.dir) {
|
|
85
|
+
return this.dir === 'rtl';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// If running on client-side and `dir` is not present, infer from the document's root element.
|
|
89
|
+
if (!isServer && !this.dir) {
|
|
90
|
+
return document.documentElement.getAttribute('dir') === 'rtl';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return false;
|
|
53
94
|
}
|
|
54
95
|
}
|
|
55
96
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LitElement, html, nothing,
|
|
3
|
+
} from 'lit';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
describe,
|
|
7
|
+
it,
|
|
8
|
+
expect,
|
|
9
|
+
vi,
|
|
10
|
+
} from 'vitest';
|
|
11
|
+
|
|
12
|
+
import { RtlMixin } from '../index';
|
|
13
|
+
|
|
14
|
+
const scenarios = [
|
|
15
|
+
{ dir: 'ltr', isRTL: false },
|
|
16
|
+
{ dir: 'rtl', isRTL: true },
|
|
17
|
+
{ dir: 'auto', isRTL: false }
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
vi.mock('lit', async () => ({
|
|
21
|
+
...((await vi.importActual('lit')) as Array<unknown>),
|
|
22
|
+
isServer: false,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
describe('RtlMixin', () => {
|
|
26
|
+
const componentSelector = 'rtl-mixin-mock';
|
|
27
|
+
|
|
28
|
+
class MockComponent extends RtlMixin(LitElement) {
|
|
29
|
+
render () {
|
|
30
|
+
const { isRTL, dir } = this;
|
|
31
|
+
return html`<div dir="${dir || nothing}">${isRTL ? 'RTL Mode' : 'LTR Mode'}</div>`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
customElements.define(componentSelector, MockComponent);
|
|
36
|
+
|
|
37
|
+
function getMockInstance (): MockComponent {
|
|
38
|
+
return document.body.querySelector(componentSelector) as MockComponent;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('when running in the browser', () => {
|
|
42
|
+
describe('when the dir attribute is set via the component', () => {
|
|
43
|
+
scenarios.forEach(({ dir, isRTL }) => {
|
|
44
|
+
it(`should reflect ${isRTL ? 'RTL' : 'LTR'} if the component dir attribute is set to ${dir}`, () => {
|
|
45
|
+
// Arrange
|
|
46
|
+
document.body.innerHTML = `<rtl-mixin-mock dir="${dir}"></rtl-mixin-mock>`;
|
|
47
|
+
const component = getMockInstance();
|
|
48
|
+
|
|
49
|
+
// Assert
|
|
50
|
+
expect(component.isRTL).toBe(isRTL);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('when the dir attribute is set via the root document and no attribute provided to the component', () => {
|
|
56
|
+
scenarios.forEach(({ dir, isRTL }) => {
|
|
57
|
+
it(`should reflect ${isRTL ? 'RTL' : 'LTR'} if the root document dir attribute is set to ${dir}`, () => {
|
|
58
|
+
// Arrange
|
|
59
|
+
document.documentElement.setAttribute('dir', dir);
|
|
60
|
+
document.body.innerHTML = '<rtl-mixin-mock></rtl-mixin-mock>'; // component doesn't set dir
|
|
61
|
+
const component = getMockInstance();
|
|
62
|
+
|
|
63
|
+
// Assert
|
|
64
|
+
expect(component.isRTL).toBe(isRTL);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should prefer the dir value of the component over the root document when both are set', () => {
|
|
70
|
+
// Arrange
|
|
71
|
+
const rootDir = 'ltr';
|
|
72
|
+
const componentDir = 'rtl';
|
|
73
|
+
document.documentElement.setAttribute('dir', rootDir);
|
|
74
|
+
document.body.innerHTML = `<rtl-mixin-mock dir="${componentDir}"></rtl-mixin-mock>`;
|
|
75
|
+
const component = getMockInstance();
|
|
76
|
+
|
|
77
|
+
// Assert
|
|
78
|
+
expect(component.isRTL).toBeTruthy();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should reflect the root document dir attribute if `auto` is provided to the component', () => {
|
|
82
|
+
// Arrange
|
|
83
|
+
const rootDir = 'ltr';
|
|
84
|
+
document.documentElement.setAttribute('dir', rootDir);
|
|
85
|
+
document.body.innerHTML = '<rtl-mixin-mock dir="auto"></rtl-mixin-mock>';
|
|
86
|
+
const component = getMockInstance();
|
|
87
|
+
|
|
88
|
+
// Assert
|
|
89
|
+
expect(component.isRTL).toBeFalsy();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LitElement, html, nothing,
|
|
3
|
+
} from 'lit';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
describe,
|
|
7
|
+
it,
|
|
8
|
+
expect,
|
|
9
|
+
vi,
|
|
10
|
+
} from 'vitest';
|
|
11
|
+
|
|
12
|
+
import { RtlMixin } from '../index';
|
|
13
|
+
|
|
14
|
+
const scenarios = [
|
|
15
|
+
{ dir: 'ltr', isRTL: false },
|
|
16
|
+
{ dir: 'rtl', isRTL: true },
|
|
17
|
+
{ dir: 'auto', isRTL: false }
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
vi.mock('lit', async () => ({
|
|
21
|
+
...((await vi.importActual('lit')) as Array<unknown>),
|
|
22
|
+
isServer: true,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
describe('RtlMixin', () => {
|
|
26
|
+
const componentSelector = 'rtl-mixin-mock';
|
|
27
|
+
|
|
28
|
+
class MockComponent extends RtlMixin(LitElement) {
|
|
29
|
+
render () {
|
|
30
|
+
const { isRTL, dir } = this;
|
|
31
|
+
return html`<div dir="${dir || nothing}">${isRTL ? 'RTL Mode' : 'LTR Mode'}</div>`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
customElements.define(componentSelector, MockComponent);
|
|
36
|
+
|
|
37
|
+
function getMockInstance (): MockComponent {
|
|
38
|
+
return document.body.querySelector(componentSelector) as MockComponent;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('when running on the server', () => {
|
|
42
|
+
it('should return false for isRTL when dir is not set', () => {
|
|
43
|
+
// Arrange
|
|
44
|
+
document.body.innerHTML = '<rtl-mixin-mock></rtl-mixin-mock>';
|
|
45
|
+
const component = getMockInstance();
|
|
46
|
+
|
|
47
|
+
// Assert
|
|
48
|
+
expect(component.isRTL).toBeFalsy();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
scenarios.forEach(({ dir, isRTL }) => {
|
|
52
|
+
it(`should reflect ${isRTL ? 'RTL' : 'LTR'} if the component dir attribute is set to ${dir} when running on the server`, () => {
|
|
53
|
+
// Arrange
|
|
54
|
+
document.body.innerHTML = `<rtl-mixin-mock dir="${dir}"></rtl-mixin-mock>`;
|
|
55
|
+
const component = getMockInstance();
|
|
56
|
+
|
|
57
|
+
// Assert
|
|
58
|
+
expect(component.isRTL).toBe(isRTL);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { LitElement, html, nothing } from 'lit';
|
|
2
|
-
import {
|
|
3
|
-
describe,
|
|
4
|
-
it,
|
|
5
|
-
expect,
|
|
6
|
-
} from 'vitest';
|
|
7
|
-
import { RTLComponentProps, RtlMixin } from '../index';
|
|
8
|
-
|
|
9
|
-
const scenarios = [
|
|
10
|
-
{ dir: 'ltr', isRTL: false },
|
|
11
|
-
{ dir: 'rtl', isRTL: true },
|
|
12
|
-
{ dir: 'auto', isRTL: false }
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
describe('RtlMixin', () => {
|
|
16
|
-
const componentSelector = 'rtl-mixin-mock';
|
|
17
|
-
class MockComponent extends RtlMixin(LitElement) implements RTLComponentProps {
|
|
18
|
-
render () {
|
|
19
|
-
const { isRTL, dir } = this;
|
|
20
|
-
return html`<div dir="${dir || nothing}">${isRTL ? 'RTL Mode' : 'LTR Mode'}</div>`;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
customElements.define(componentSelector, MockComponent);
|
|
25
|
-
|
|
26
|
-
function getMockInstance (): MockComponent {
|
|
27
|
-
return document.body.querySelector(componentSelector) as MockComponent;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe('when the dir attribute is set via the component', () => {
|
|
31
|
-
scenarios.forEach(({ dir, isRTL }) => {
|
|
32
|
-
it(`should default to ${dir} if the component dir attribute is set to ${dir}`, () => {
|
|
33
|
-
// Arrange
|
|
34
|
-
document.body.innerHTML = `<rtl-mixin-mock dir="${dir}"></rtl-mixin-mock>`;
|
|
35
|
-
const component = getMockInstance();
|
|
36
|
-
|
|
37
|
-
// Assert
|
|
38
|
-
expect(component.dir).toEqual(dir);
|
|
39
|
-
expect(component.isRTL).toBe(isRTL);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('when the dir attribute is set via the root document', () => {
|
|
45
|
-
scenarios.forEach(({ dir, isRTL }) => {
|
|
46
|
-
it(`should default to ${dir} if the root document dir attribute is set to ${dir}`, () => {
|
|
47
|
-
// Arrange
|
|
48
|
-
document.documentElement.setAttribute('dir', dir);
|
|
49
|
-
document.body.innerHTML = '<rtl-mixin-mock></rtl-mixin-mock>'; // component doesn't set dir
|
|
50
|
-
const component = getMockInstance();
|
|
51
|
-
|
|
52
|
-
// Assert
|
|
53
|
-
expect(component.dir).toEqual(dir);
|
|
54
|
-
expect(component.isRTL).toBe(isRTL);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should default to dir value of the component if the root document and component dir attribute are set', () => {
|
|
60
|
-
// Arrange
|
|
61
|
-
const rootDir = 'ltr';
|
|
62
|
-
const componentDir = 'rtl';
|
|
63
|
-
document.documentElement.setAttribute('dir', rootDir);
|
|
64
|
-
document.body.innerHTML = `<rtl-mixin-mock dir="${componentDir}"></rtl-mixin-mock>`;
|
|
65
|
-
const component = getMockInstance();
|
|
66
|
-
|
|
67
|
-
// Assert
|
|
68
|
-
expect(component.dir).toEqual(componentDir);
|
|
69
|
-
expect(component.dir).not.toEqual(rootDir);
|
|
70
|
-
expect(component.isRTL).toBeTruthy();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A type alias that represents a specialized version of the built-in Map type, but with a custom get method that enforces stricter type checking
|
|
3
|
-
*/
|
|
4
|
-
export type DependentMap<T> = Omit<Map<keyof T, T[keyof T]>, 'get'> & {
|
|
5
|
-
get<K extends keyof T>(key: K): T[K] | undefined;
|
|
6
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './DependentMap';
|