@justeattakeaway/pie-webc-core 0.8.0 → 0.9.1

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [Changed] - Align author field for all packages ([#852](https://github.com/justeattakeaway/pie/pull/852)) by [@xander-marjoram](https://github.com/xander-marjoram)
8
+
9
+ ## 0.9.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [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)
14
+
15
+ - [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)
16
+
3
17
  ## 0.8.0
4
18
 
5
19
  ### Minor Changes
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@justeattakeaway/pie-webc-core",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "PIE design system base classes, mixins and utilities for web components",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
- "author": "JustEatTakeaway - Design System Web Team",
7
+ "author": "Just Eat Takeaway.com - Design System Team",
8
8
  "license": "Apache-2.0",
9
9
  "scripts": {
10
10
  "lint:scripts": "run -T eslint .",
package/src/index.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export * from './mixins';
2
2
  export * from './decorators';
3
- export * from './type-helpers';
@@ -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
- // According to TS, "A mixin class must have a constructor with a single rest parameter of type 'any[]'."
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
- * Any component property interface that implements RTL should extend this interface. See the ModalProps interface for an example of this.
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
- dir: htmlDirAttribute;
17
+ /** A boolean indicating whether the text direction is right-to-left. */
21
18
  isRTL: boolean;
22
19
  }
23
20
 
24
21
  /**
25
- * This RTL mixin is used with Lit Web components to add programmatic Right-to-Left (RTL) support.
26
- * It is only required if your component either:
27
- * - Needs RTL awareness in its TypeScript logic.
28
- * - Its CSS requires a [dir='rtl'] attribute to be present.
29
- *
30
- * By default, components will infer the 'dir' property from the document's root 'dir' attribute.
31
- * If needed, it's possible to override specific components direction by setting the 'dir' property
32
- * value. The 'dir' property value is reflected in the DOM, making it queryable. The 'isRTL'
33
- * internal property returns true or false depending on whether or not the 'dir' property is 'rtl' or not.
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
- * Returns true if the element is in Right to Left mode.
44
- * If a dir attribute is not explicitly set on the web component
45
- * then it falls back to the nearest parent with a dir attribute set.
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
- * A dir attribute being set will result in a reactive property.
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 () : boolean {
52
- return this.dir === 'rtl';
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';