p-elements-core 1.2.32-rc8 → 1.2.32
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/.editorconfig +17 -17
- package/.gitlab-ci.yml +18 -18
- package/CHANGELOG.md +201 -201
- package/demo/sample.js +1 -1
- package/demo/screen.css +16 -16
- package/dist/p-elements-core-modern.js +1 -1
- package/dist/p-elements-core.js +1 -1
- package/docs/package-lock.json +6897 -6897
- package/docs/package.json +27 -27
- package/docs/src/404.md +8 -8
- package/docs/src/_data/demos/hello-world/hello-world.tsx +35 -35
- package/docs/src/_data/demos/hello-world/index.html +10 -10
- package/docs/src/_data/demos/hello-world/project.json +7 -7
- package/docs/src/_data/demos/timer/demo-timer.tsx +120 -120
- package/docs/src/_data/demos/timer/icons.tsx +62 -62
- package/docs/src/_data/demos/timer/index.html +12 -12
- package/docs/src/_data/demos/timer/project.json +8 -8
- package/docs/src/_data/global.js +13 -13
- package/docs/src/_data/helpers.js +19 -19
- package/docs/src/_includes/layouts/base.njk +30 -30
- package/docs/src/_includes/layouts/playground.njk +40 -40
- package/docs/src/_includes/partials/app-header.njk +8 -8
- package/docs/src/_includes/partials/head.njk +14 -14
- package/docs/src/_includes/partials/nav.njk +19 -19
- package/docs/src/_includes/partials/top-nav.njk +51 -51
- package/docs/src/documentation/custom-element.md +221 -221
- package/docs/src/documentation/decorators/bind.md +71 -71
- package/docs/src/documentation/decorators/custom-element-config.md +63 -63
- package/docs/src/documentation/decorators/property.md +83 -83
- package/docs/src/documentation/decorators/query.md +66 -66
- package/docs/src/documentation/decorators/render-property-on-set.md +60 -60
- package/docs/src/documentation/decorators.md +9 -9
- package/docs/src/documentation/reactive-properties.md +53 -53
- package/docs/src/index.d.ts +25 -25
- package/docs/src/index.md +3 -3
- package/docs/src/scripts/components/app-mode-switch/app-mode-switch.css +78 -78
- package/docs/src/scripts/components/app-mode-switch/app-mode-switch.tsx +166 -166
- package/docs/src/scripts/components/app-playground/app-playground.tsx +189 -189
- package/docs/tsconfig.json +22 -22
- package/index.html +10 -2
- package/package.json +1 -1
- package/readme.md +206 -206
- package/src/custom-element-controller.ts +31 -31
- package/src/custom-element.test.ts +906 -906
- package/src/custom-element.ts +3 -8
- package/src/decorators/bind.test.ts +163 -163
- package/src/decorators/bind.ts +46 -46
- package/src/decorators/custom-element-config.ts +17 -17
- package/src/decorators/property.test.ts +279 -279
- package/src/decorators/query.test.ts +146 -146
- package/src/decorators/query.ts +12 -12
- package/src/decorators/render-property-on-set.ts +3 -3
- package/src/helpers/css.ts +71 -71
- package/src/maquette/cache.ts +35 -35
- package/src/maquette/dom.ts +115 -115
- package/src/maquette/h.ts +100 -100
- package/src/maquette/index.ts +12 -12
- package/src/maquette/interfaces.ts +536 -536
- package/src/maquette/jsx.ts +61 -61
- package/src/maquette/mapping.ts +56 -56
- package/src/maquette/projection.ts +666 -666
- package/src/maquette/projector.ts +205 -205
- package/src/sample/mixin/highlight.tsx +33 -33
- package/src/sample/sample.tsx +98 -0
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "ReactiveProperties"
|
|
3
|
-
layout: "base.njk"
|
|
4
|
-
description: "Reactive properties"
|
|
5
|
-
permalink: "/documentation/reactive-properties.html"
|
|
6
|
-
eleventyNavigation:
|
|
7
|
-
key: ReactiveProperties
|
|
8
|
-
title: Reactive properties
|
|
9
|
-
order: 4
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# Reactive properties
|
|
13
|
-
|
|
14
|
-
P-Elements receive input and store their state as JavaScript class fields or properties. Reactive properties are properties that can trigger the reactive update cycle when changed, re-rendering the component, and optionally be read or written to attributes.
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
@CustomElementConfig({ tagName: 'my-greeting' })
|
|
18
|
-
class MyGreeting extends CustomElement {
|
|
19
|
-
|
|
20
|
-
static readonly style = `...`;
|
|
21
|
-
|
|
22
|
-
@Property({ type: 'string', reflect: true, attribute: 'name' })
|
|
23
|
-
name = 'World';
|
|
24
|
-
|
|
25
|
-
render() : VNode {
|
|
26
|
-
return <h1>Hello, ${this.name}!</h1>;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Typescript config
|
|
32
|
-
|
|
33
|
-
To use experimental decorators you must enable the `experimentalDecorators` compiler option.
|
|
34
|
-
|
|
35
|
-
You should also ensure that the `useDefineForClassFields` setting is false. This is needed to avoid issues with class fields when declaring properties.
|
|
36
|
-
|
|
37
|
-
<!-- todo in next version p-elements support standard decorator syntax -->
|
|
38
|
-
|
|
39
|
-
```json
|
|
40
|
-
// tsconfig.json
|
|
41
|
-
{
|
|
42
|
-
"compilerOptions": {
|
|
43
|
-
"experimentalDecorators": true,
|
|
44
|
-
"useDefineForClassFields": false,
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Decorators
|
|
50
|
-
|
|
51
|
-
For more info:
|
|
52
|
-
|
|
53
|
-
- [@Property](./decorators/property.html)
|
|
1
|
+
---
|
|
2
|
+
title: "ReactiveProperties"
|
|
3
|
+
layout: "base.njk"
|
|
4
|
+
description: "Reactive properties"
|
|
5
|
+
permalink: "/documentation/reactive-properties.html"
|
|
6
|
+
eleventyNavigation:
|
|
7
|
+
key: ReactiveProperties
|
|
8
|
+
title: Reactive properties
|
|
9
|
+
order: 4
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Reactive properties
|
|
13
|
+
|
|
14
|
+
P-Elements receive input and store their state as JavaScript class fields or properties. Reactive properties are properties that can trigger the reactive update cycle when changed, re-rendering the component, and optionally be read or written to attributes.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
@CustomElementConfig({ tagName: 'my-greeting' })
|
|
18
|
+
class MyGreeting extends CustomElement {
|
|
19
|
+
|
|
20
|
+
static readonly style = `...`;
|
|
21
|
+
|
|
22
|
+
@Property({ type: 'string', reflect: true, attribute: 'name' })
|
|
23
|
+
name = 'World';
|
|
24
|
+
|
|
25
|
+
render() : VNode {
|
|
26
|
+
return <h1>Hello, ${this.name}!</h1>;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Typescript config
|
|
32
|
+
|
|
33
|
+
To use experimental decorators you must enable the `experimentalDecorators` compiler option.
|
|
34
|
+
|
|
35
|
+
You should also ensure that the `useDefineForClassFields` setting is false. This is needed to avoid issues with class fields when declaring properties.
|
|
36
|
+
|
|
37
|
+
<!-- todo in next version p-elements support standard decorator syntax -->
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
// tsconfig.json
|
|
41
|
+
{
|
|
42
|
+
"compilerOptions": {
|
|
43
|
+
"experimentalDecorators": true,
|
|
44
|
+
"useDefineForClassFields": false,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Decorators
|
|
50
|
+
|
|
51
|
+
For more info:
|
|
52
|
+
|
|
53
|
+
- [@Property](./decorators/property.html)
|
|
54
54
|
- [@PropertyRenderOnSet](./decorators/property-render-on-set.html)
|
package/docs/src/index.d.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
/// <reference types="p-elements-core" />
|
|
2
|
-
/// <reference types="@types/animejs" />
|
|
3
|
-
/// <reference types="@types/underscore" />
|
|
4
|
-
|
|
5
|
-
declare namespace JSX {
|
|
6
|
-
interface IntrinsicElements {
|
|
7
|
-
[tagName: string]: VNodeProperties;
|
|
8
|
-
}
|
|
9
|
-
type Element = VNode;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
declare const _: _.UnderscoreStatic;
|
|
13
|
-
|
|
14
|
-
declare const anime: (params: anime.AnimeParams) => anime.AnimeInstance;
|
|
15
|
-
|
|
16
|
-
declare const Maquette: {
|
|
17
|
-
h: H;
|
|
18
|
-
createProjector: (projectorOptions?: ProjectorOptions) => Projector;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
declare let notifyContainer: NotifyContainerElement;
|
|
22
|
-
|
|
23
|
-
declare module "*.css";
|
|
24
|
-
declare module "*.html";
|
|
25
|
-
declare module "*.svg";
|
|
1
|
+
/// <reference types="p-elements-core" />
|
|
2
|
+
/// <reference types="@types/animejs" />
|
|
3
|
+
/// <reference types="@types/underscore" />
|
|
4
|
+
|
|
5
|
+
declare namespace JSX {
|
|
6
|
+
interface IntrinsicElements {
|
|
7
|
+
[tagName: string]: VNodeProperties;
|
|
8
|
+
}
|
|
9
|
+
type Element = VNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare const _: _.UnderscoreStatic;
|
|
13
|
+
|
|
14
|
+
declare const anime: (params: anime.AnimeParams) => anime.AnimeInstance;
|
|
15
|
+
|
|
16
|
+
declare const Maquette: {
|
|
17
|
+
h: H;
|
|
18
|
+
createProjector: (projectorOptions?: ProjectorOptions) => Projector;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
declare let notifyContainer: NotifyContainerElement;
|
|
22
|
+
|
|
23
|
+
declare module "*.css";
|
|
24
|
+
declare module "*.html";
|
|
25
|
+
declare module "*.svg";
|
package/docs/src/index.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
document.location.href="./documentation/index.html";
|
|
3
|
-
</script>
|
|
1
|
+
<script>
|
|
2
|
+
document.location.href="./documentation/index.html";
|
|
3
|
+
</script>
|
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
:host {
|
|
2
|
-
background: none;
|
|
3
|
-
border: none;
|
|
4
|
-
padding: 0;
|
|
5
|
-
cursor: pointer;
|
|
6
|
-
touch-action: manipulation;
|
|
7
|
-
-webkit-tap-highlight-color: transparent;
|
|
8
|
-
display: inline-block;
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
:host > div{
|
|
13
|
-
display: flex;
|
|
14
|
-
align-items: center;
|
|
15
|
-
justify-content: center;
|
|
16
|
-
gap: .5rem;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
.sun-and-moon > :is(.moon, .sun, .sun-beams) {
|
|
21
|
-
transform-origin: center center;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
svg {
|
|
26
|
-
margin: auto;
|
|
27
|
-
stroke-linecap: round;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
svg + div {
|
|
31
|
-
display: flex;
|
|
32
|
-
align-items: center;
|
|
33
|
-
justify-content: center;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
:host([mode="light"]) .sun-and-moon > .sun {
|
|
37
|
-
transform: scale(1.75);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
:host([mode="light"]) .sun-and-moon > .sun-beams {
|
|
41
|
-
opacity: 0;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
:host([mode="light"]) .sun-and-moon > .moon > circle {
|
|
45
|
-
transform: translate(-7px);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/* Animations */
|
|
49
|
-
|
|
50
|
-
:host([mode="dark"]) .sun-and-moon > .sun {
|
|
51
|
-
transition: transform 0.5s cubic-bezier(0.5, 1.25, 0.75, 1.25);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
:host([mode="dark"]) .sun-and-moon > .sun-beams {
|
|
55
|
-
transition:
|
|
56
|
-
transform 0.5s cubic-bezier(0.5, 1.5, 0.75, 1.25),
|
|
57
|
-
opacity 0.5s cubic-bezier(0.25, 0, 0.3, 1);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
:host([mode="darkt"]) .sun-and-moon .moon > circle {
|
|
61
|
-
transition: transform 0.25s cubic-bezier(0, 0, 0, 1);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
:host([mode="light"]) .sun-and-moon > .sun {
|
|
65
|
-
transform: scale(1.75);
|
|
66
|
-
transition-timing-function: cubic-bezier(0.25, 0, 0.3, 1);
|
|
67
|
-
transition-duration: 0.25s;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
:host([mode="light"]) .sun-and-moon > .sun-beams {
|
|
71
|
-
transform: rotate(-25deg);
|
|
72
|
-
transition-duration: 0.15s;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
:host([mode="light"]) .sun-and-moon > .moon > circle {
|
|
76
|
-
transition-delay: 0.25s;
|
|
77
|
-
transition-duration: 0.5s;
|
|
78
|
-
}
|
|
1
|
+
:host {
|
|
2
|
+
background: none;
|
|
3
|
+
border: none;
|
|
4
|
+
padding: 0;
|
|
5
|
+
cursor: pointer;
|
|
6
|
+
touch-action: manipulation;
|
|
7
|
+
-webkit-tap-highlight-color: transparent;
|
|
8
|
+
display: inline-block;
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:host > div{
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
gap: .5rem;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
.sun-and-moon > :is(.moon, .sun, .sun-beams) {
|
|
21
|
+
transform-origin: center center;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
svg {
|
|
26
|
+
margin: auto;
|
|
27
|
+
stroke-linecap: round;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
svg + div {
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
:host([mode="light"]) .sun-and-moon > .sun {
|
|
37
|
+
transform: scale(1.75);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:host([mode="light"]) .sun-and-moon > .sun-beams {
|
|
41
|
+
opacity: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
:host([mode="light"]) .sun-and-moon > .moon > circle {
|
|
45
|
+
transform: translate(-7px);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Animations */
|
|
49
|
+
|
|
50
|
+
:host([mode="dark"]) .sun-and-moon > .sun {
|
|
51
|
+
transition: transform 0.5s cubic-bezier(0.5, 1.25, 0.75, 1.25);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
:host([mode="dark"]) .sun-and-moon > .sun-beams {
|
|
55
|
+
transition:
|
|
56
|
+
transform 0.5s cubic-bezier(0.5, 1.5, 0.75, 1.25),
|
|
57
|
+
opacity 0.5s cubic-bezier(0.25, 0, 0.3, 1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
:host([mode="darkt"]) .sun-and-moon .moon > circle {
|
|
61
|
+
transition: transform 0.25s cubic-bezier(0, 0, 0, 1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
:host([mode="light"]) .sun-and-moon > .sun {
|
|
65
|
+
transform: scale(1.75);
|
|
66
|
+
transition-timing-function: cubic-bezier(0.25, 0, 0.3, 1);
|
|
67
|
+
transition-duration: 0.25s;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
:host([mode="light"]) .sun-and-moon > .sun-beams {
|
|
71
|
+
transform: rotate(-25deg);
|
|
72
|
+
transition-duration: 0.15s;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
:host([mode="light"]) .sun-and-moon > .moon > circle {
|
|
76
|
+
transition-delay: 0.25s;
|
|
77
|
+
transition-duration: 0.5s;
|
|
78
|
+
}
|
|
@@ -1,166 +1,166 @@
|
|
|
1
|
-
import css from "./app-mode-switch.css";
|
|
2
|
-
|
|
3
|
-
export enum ThemeMode {
|
|
4
|
-
Light = "light",
|
|
5
|
-
Dark = "dark",
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
interface AppModeSwitchEventMap {
|
|
9
|
-
modeChange: CustomEvent<{ mode: ThemeMode }>;
|
|
10
|
-
click: MouseEvent;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
@CustomElementConfig({
|
|
14
|
-
tagName: "app-mode-switch",
|
|
15
|
-
})
|
|
16
|
-
export class AppModeSwitchElement extends CustomElement {
|
|
17
|
-
static readonly style = css;
|
|
18
|
-
|
|
19
|
-
@Property({ type: "string", attribute: "mode", reflect: true })
|
|
20
|
-
mode: ThemeMode = ThemeMode.Light;
|
|
21
|
-
|
|
22
|
-
render(): VNode {
|
|
23
|
-
return (
|
|
24
|
-
<div>
|
|
25
|
-
<svg
|
|
26
|
-
class="sun-and-moon"
|
|
27
|
-
aria-hidden="true"
|
|
28
|
-
width="24"
|
|
29
|
-
height="24"
|
|
30
|
-
viewBox="0 0 24 24"
|
|
31
|
-
>
|
|
32
|
-
<mask class="moon" id="moon-mask">
|
|
33
|
-
<rect x="0" y="0" width="100%" height="100%" fill="white"></rect>
|
|
34
|
-
<circle cx="24" cy="10" r="6" fill="black"></circle>
|
|
35
|
-
</mask>
|
|
36
|
-
<circle
|
|
37
|
-
class="sun"
|
|
38
|
-
cx="12"
|
|
39
|
-
cy="12"
|
|
40
|
-
r="6"
|
|
41
|
-
mask="url(#moon-mask)"
|
|
42
|
-
fill="currentColor"
|
|
43
|
-
></circle>
|
|
44
|
-
<g class="sun-beams" stroke="currentColor">
|
|
45
|
-
<line x1="12" y1="1" x2="12" y2="3"></line>
|
|
46
|
-
<line x1="12" y1="21" x2="12" y2="23"></line>
|
|
47
|
-
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
|
48
|
-
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
|
49
|
-
<line x1="1" y1="12" x2="3" y2="12"></line>
|
|
50
|
-
<line x1="21" y1="12" x2="23" y2="12"></line>
|
|
51
|
-
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
|
52
|
-
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
|
53
|
-
</g>
|
|
54
|
-
</svg>
|
|
55
|
-
<div>
|
|
56
|
-
<slot name={this.mode === ThemeMode.Dark ? ThemeMode.Light : ThemeMode.Dark }></slot>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
addEventListener<T extends keyof AppModeSwitchEventMap>(
|
|
63
|
-
type: T,
|
|
64
|
-
listener: (this: AppModeSwitchElement, ev: AppModeSwitchEventMap[T]) => any,
|
|
65
|
-
options?: boolean | AddEventListenerOptions
|
|
66
|
-
): void;
|
|
67
|
-
|
|
68
|
-
addEventListener(
|
|
69
|
-
type: string,
|
|
70
|
-
listener: (this: AppModeSwitchElement, ev: Event) => any,
|
|
71
|
-
options?: boolean | AddEventListenerOptions
|
|
72
|
-
): void {
|
|
73
|
-
super.addEventListener(type, listener, options);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private onClick = () => {
|
|
77
|
-
this.mode =
|
|
78
|
-
this.mode === ThemeMode.Light ? ThemeMode.Dark : ThemeMode.Light;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
updated(propertyName: string, oldValue: any, newValue: any): void {
|
|
82
|
-
if (propertyName === "mode") {
|
|
83
|
-
this.dispatchEvent(
|
|
84
|
-
new CustomEvent("modeChange", { detail: { mode: newValue } })
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
connectedCallback() {
|
|
90
|
-
super.connectedCallback();
|
|
91
|
-
if (!this.hasAttribute("tabindex")) {
|
|
92
|
-
this.setAttribute("tabindex", "0");
|
|
93
|
-
}
|
|
94
|
-
this.setAttribute("role", "button");
|
|
95
|
-
this.addEventListener("click", this.onClick);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
disconnectedCallback() {
|
|
99
|
-
super.disconnectedCallback();
|
|
100
|
-
this.removeEventListener("click", this.onClick);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// init theme
|
|
105
|
-
let currentTheme: ThemeMode;
|
|
106
|
-
|
|
107
|
-
const darkSchemeMediaQueryList = window.matchMedia(
|
|
108
|
-
"(prefers-color-scheme: dark)"
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
function setTheme(theme: ThemeMode) {
|
|
112
|
-
if (currentTheme === theme) return;
|
|
113
|
-
currentTheme = theme;
|
|
114
|
-
document.documentElement.setAttribute("theme", theme);
|
|
115
|
-
if (systemTheme.startsWith(theme)) {
|
|
116
|
-
localStorage.removeItem("theme");
|
|
117
|
-
} else {
|
|
118
|
-
localStorage.setItem("theme", theme);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
let systemTheme = darkSchemeMediaQueryList.matches
|
|
123
|
-
? `${ThemeMode.Dark}-mode`
|
|
124
|
-
: `${ThemeMode.Light}-mode`;
|
|
125
|
-
|
|
126
|
-
darkSchemeMediaQueryList.addEventListener("change", ({ matches }) => {
|
|
127
|
-
const modeSwitch =
|
|
128
|
-
document.querySelector<AppModeSwitchElement>("app-mode-switch");
|
|
129
|
-
if (matches) {
|
|
130
|
-
systemTheme = ThemeMode.Dark;
|
|
131
|
-
setTheme(ThemeMode.Dark);
|
|
132
|
-
if (modeSwitch){
|
|
133
|
-
modeSwitch.mode = ThemeMode.Dark;
|
|
134
|
-
}
|
|
135
|
-
} else {
|
|
136
|
-
systemTheme = ThemeMode.Light;
|
|
137
|
-
setTheme(ThemeMode.Light);
|
|
138
|
-
if (modeSwitch){
|
|
139
|
-
modeSwitch.mode = ThemeMode.Light;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
const savedTheme = localStorage.getItem("theme") as ThemeMode;
|
|
145
|
-
if (savedTheme) {
|
|
146
|
-
setTheme(savedTheme);
|
|
147
|
-
} else {
|
|
148
|
-
setTheme(systemTheme.split("-")[0] as ThemeMode);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
152
|
-
const modeSwitch =
|
|
153
|
-
document.querySelector<AppModeSwitchElement>("app-mode-switch");
|
|
154
|
-
if (!modeSwitch) return;
|
|
155
|
-
modeSwitch.mode = savedTheme
|
|
156
|
-
? savedTheme
|
|
157
|
-
: (systemTheme.split("-")[0] as ThemeMode);
|
|
158
|
-
modeSwitch.addEventListener("modeChange", (e: CustomEventInit) => {
|
|
159
|
-
if (e.detail.mode === ThemeMode.Dark) {
|
|
160
|
-
setTheme(ThemeMode.Dark);
|
|
161
|
-
} else if (e.detail.mode === ThemeMode.Light) {
|
|
162
|
-
setTheme(ThemeMode.Light);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
1
|
+
import css from "./app-mode-switch.css";
|
|
2
|
+
|
|
3
|
+
export enum ThemeMode {
|
|
4
|
+
Light = "light",
|
|
5
|
+
Dark = "dark",
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface AppModeSwitchEventMap {
|
|
9
|
+
modeChange: CustomEvent<{ mode: ThemeMode }>;
|
|
10
|
+
click: MouseEvent;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@CustomElementConfig({
|
|
14
|
+
tagName: "app-mode-switch",
|
|
15
|
+
})
|
|
16
|
+
export class AppModeSwitchElement extends CustomElement {
|
|
17
|
+
static readonly style = css;
|
|
18
|
+
|
|
19
|
+
@Property({ type: "string", attribute: "mode", reflect: true })
|
|
20
|
+
mode: ThemeMode = ThemeMode.Light;
|
|
21
|
+
|
|
22
|
+
render(): VNode {
|
|
23
|
+
return (
|
|
24
|
+
<div>
|
|
25
|
+
<svg
|
|
26
|
+
class="sun-and-moon"
|
|
27
|
+
aria-hidden="true"
|
|
28
|
+
width="24"
|
|
29
|
+
height="24"
|
|
30
|
+
viewBox="0 0 24 24"
|
|
31
|
+
>
|
|
32
|
+
<mask class="moon" id="moon-mask">
|
|
33
|
+
<rect x="0" y="0" width="100%" height="100%" fill="white"></rect>
|
|
34
|
+
<circle cx="24" cy="10" r="6" fill="black"></circle>
|
|
35
|
+
</mask>
|
|
36
|
+
<circle
|
|
37
|
+
class="sun"
|
|
38
|
+
cx="12"
|
|
39
|
+
cy="12"
|
|
40
|
+
r="6"
|
|
41
|
+
mask="url(#moon-mask)"
|
|
42
|
+
fill="currentColor"
|
|
43
|
+
></circle>
|
|
44
|
+
<g class="sun-beams" stroke="currentColor">
|
|
45
|
+
<line x1="12" y1="1" x2="12" y2="3"></line>
|
|
46
|
+
<line x1="12" y1="21" x2="12" y2="23"></line>
|
|
47
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
|
48
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
|
49
|
+
<line x1="1" y1="12" x2="3" y2="12"></line>
|
|
50
|
+
<line x1="21" y1="12" x2="23" y2="12"></line>
|
|
51
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
|
52
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
|
53
|
+
</g>
|
|
54
|
+
</svg>
|
|
55
|
+
<div>
|
|
56
|
+
<slot name={this.mode === ThemeMode.Dark ? ThemeMode.Light : ThemeMode.Dark }></slot>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
addEventListener<T extends keyof AppModeSwitchEventMap>(
|
|
63
|
+
type: T,
|
|
64
|
+
listener: (this: AppModeSwitchElement, ev: AppModeSwitchEventMap[T]) => any,
|
|
65
|
+
options?: boolean | AddEventListenerOptions
|
|
66
|
+
): void;
|
|
67
|
+
|
|
68
|
+
addEventListener(
|
|
69
|
+
type: string,
|
|
70
|
+
listener: (this: AppModeSwitchElement, ev: Event) => any,
|
|
71
|
+
options?: boolean | AddEventListenerOptions
|
|
72
|
+
): void {
|
|
73
|
+
super.addEventListener(type, listener, options);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private onClick = () => {
|
|
77
|
+
this.mode =
|
|
78
|
+
this.mode === ThemeMode.Light ? ThemeMode.Dark : ThemeMode.Light;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
updated(propertyName: string, oldValue: any, newValue: any): void {
|
|
82
|
+
if (propertyName === "mode") {
|
|
83
|
+
this.dispatchEvent(
|
|
84
|
+
new CustomEvent("modeChange", { detail: { mode: newValue } })
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
connectedCallback() {
|
|
90
|
+
super.connectedCallback();
|
|
91
|
+
if (!this.hasAttribute("tabindex")) {
|
|
92
|
+
this.setAttribute("tabindex", "0");
|
|
93
|
+
}
|
|
94
|
+
this.setAttribute("role", "button");
|
|
95
|
+
this.addEventListener("click", this.onClick);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
disconnectedCallback() {
|
|
99
|
+
super.disconnectedCallback();
|
|
100
|
+
this.removeEventListener("click", this.onClick);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// init theme
|
|
105
|
+
let currentTheme: ThemeMode;
|
|
106
|
+
|
|
107
|
+
const darkSchemeMediaQueryList = window.matchMedia(
|
|
108
|
+
"(prefers-color-scheme: dark)"
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
function setTheme(theme: ThemeMode) {
|
|
112
|
+
if (currentTheme === theme) return;
|
|
113
|
+
currentTheme = theme;
|
|
114
|
+
document.documentElement.setAttribute("theme", theme);
|
|
115
|
+
if (systemTheme.startsWith(theme)) {
|
|
116
|
+
localStorage.removeItem("theme");
|
|
117
|
+
} else {
|
|
118
|
+
localStorage.setItem("theme", theme);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let systemTheme = darkSchemeMediaQueryList.matches
|
|
123
|
+
? `${ThemeMode.Dark}-mode`
|
|
124
|
+
: `${ThemeMode.Light}-mode`;
|
|
125
|
+
|
|
126
|
+
darkSchemeMediaQueryList.addEventListener("change", ({ matches }) => {
|
|
127
|
+
const modeSwitch =
|
|
128
|
+
document.querySelector<AppModeSwitchElement>("app-mode-switch");
|
|
129
|
+
if (matches) {
|
|
130
|
+
systemTheme = ThemeMode.Dark;
|
|
131
|
+
setTheme(ThemeMode.Dark);
|
|
132
|
+
if (modeSwitch){
|
|
133
|
+
modeSwitch.mode = ThemeMode.Dark;
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
systemTheme = ThemeMode.Light;
|
|
137
|
+
setTheme(ThemeMode.Light);
|
|
138
|
+
if (modeSwitch){
|
|
139
|
+
modeSwitch.mode = ThemeMode.Light;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const savedTheme = localStorage.getItem("theme") as ThemeMode;
|
|
145
|
+
if (savedTheme) {
|
|
146
|
+
setTheme(savedTheme);
|
|
147
|
+
} else {
|
|
148
|
+
setTheme(systemTheme.split("-")[0] as ThemeMode);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
152
|
+
const modeSwitch =
|
|
153
|
+
document.querySelector<AppModeSwitchElement>("app-mode-switch");
|
|
154
|
+
if (!modeSwitch) return;
|
|
155
|
+
modeSwitch.mode = savedTheme
|
|
156
|
+
? savedTheme
|
|
157
|
+
: (systemTheme.split("-")[0] as ThemeMode);
|
|
158
|
+
modeSwitch.addEventListener("modeChange", (e: CustomEventInit) => {
|
|
159
|
+
if (e.detail.mode === ThemeMode.Dark) {
|
|
160
|
+
setTheme(ThemeMode.Dark);
|
|
161
|
+
} else if (e.detail.mode === ThemeMode.Light) {
|
|
162
|
+
setTheme(ThemeMode.Light);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|