p-elements-core 1.2.31 → 1.2.32-rc-10

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.
Files changed (82) hide show
  1. package/.editorconfig +17 -17
  2. package/.gitlab-ci.yml +18 -18
  3. package/CHANGELOG.md +201 -0
  4. package/demo/sample.js +1 -1
  5. package/demo/screen.css +16 -16
  6. package/demo/theme.css +1 -0
  7. package/dist/p-elements-core-modern.js +1 -1
  8. package/dist/p-elements-core.js +1 -1
  9. package/docs/package-lock.json +6897 -6897
  10. package/docs/package.json +27 -27
  11. package/docs/src/404.md +8 -8
  12. package/docs/src/_data/demos/hello-world/hello-world.tsx +35 -35
  13. package/docs/src/_data/demos/hello-world/index.html +10 -10
  14. package/docs/src/_data/demos/hello-world/project.json +7 -7
  15. package/docs/src/_data/demos/timer/demo-timer.tsx +120 -120
  16. package/docs/src/_data/demos/timer/icons.tsx +62 -62
  17. package/docs/src/_data/demos/timer/index.html +12 -12
  18. package/docs/src/_data/demos/timer/project.json +8 -8
  19. package/docs/src/_data/global.js +13 -13
  20. package/docs/src/_data/helpers.js +19 -19
  21. package/docs/src/_includes/layouts/base.njk +30 -30
  22. package/docs/src/_includes/layouts/playground.njk +40 -40
  23. package/docs/src/_includes/partials/app-header.njk +8 -8
  24. package/docs/src/_includes/partials/head.njk +14 -14
  25. package/docs/src/_includes/partials/nav.njk +19 -19
  26. package/docs/src/_includes/partials/top-nav.njk +51 -51
  27. package/docs/src/documentation/custom-element.md +221 -221
  28. package/docs/src/documentation/decorators/bind.md +71 -71
  29. package/docs/src/documentation/decorators/custom-element-config.md +63 -63
  30. package/docs/src/documentation/decorators/property.md +83 -83
  31. package/docs/src/documentation/decorators/query.md +66 -66
  32. package/docs/src/documentation/decorators/render-property-on-set.md +60 -60
  33. package/docs/src/documentation/decorators.md +9 -9
  34. package/docs/src/documentation/reactive-properties.md +53 -53
  35. package/docs/src/index.d.ts +25 -25
  36. package/docs/src/index.md +3 -3
  37. package/docs/src/scripts/components/app-mode-switch/app-mode-switch.css +78 -78
  38. package/docs/src/scripts/components/app-mode-switch/app-mode-switch.tsx +166 -166
  39. package/docs/src/scripts/components/app-playground/app-playground.tsx +189 -189
  40. package/docs/tsconfig.json +22 -22
  41. package/index.html +15 -2
  42. package/p-elements-core.d.ts +11 -1
  43. package/package.json +11 -4
  44. package/readme.md +206 -206
  45. package/src/custom-element-controller.test.ts +226 -0
  46. package/src/custom-element-controller.ts +31 -31
  47. package/src/custom-element.test.ts +906 -0
  48. package/src/custom-element.ts +471 -188
  49. package/src/custom-style-element.ts +4 -1
  50. package/src/decorators/bind.test.ts +163 -0
  51. package/src/decorators/bind.ts +46 -46
  52. package/src/decorators/custom-element-config.ts +17 -17
  53. package/src/decorators/property.test.ts +279 -0
  54. package/src/decorators/property.ts +822 -150
  55. package/src/decorators/query.test.ts +146 -0
  56. package/src/decorators/query.ts +12 -12
  57. package/src/decorators/render-property-on-set.ts +3 -3
  58. package/src/helpers/css.test.ts +150 -0
  59. package/src/helpers/css.ts +71 -71
  60. package/src/maquette/cache.test.ts +150 -0
  61. package/src/maquette/cache.ts +35 -35
  62. package/src/maquette/dom.test.ts +263 -0
  63. package/src/maquette/dom.ts +115 -115
  64. package/src/maquette/h.test.ts +165 -0
  65. package/src/maquette/h.ts +100 -100
  66. package/src/maquette/index.ts +12 -12
  67. package/src/maquette/interfaces.ts +536 -536
  68. package/src/maquette/jsx.ts +61 -61
  69. package/src/maquette/mapping.test.ts +294 -0
  70. package/src/maquette/mapping.ts +56 -56
  71. package/src/maquette/maquette.test.ts +493 -0
  72. package/src/maquette/projection.test.ts +366 -0
  73. package/src/maquette/projection.ts +666 -666
  74. package/src/maquette/projector.test.ts +351 -0
  75. package/src/maquette/projector.ts +200 -200
  76. package/src/sample/mixin/highlight.tsx +33 -32
  77. package/src/sample/sample.tsx +167 -7
  78. package/src/test-setup.ts +85 -0
  79. package/src/test-utils.ts +223 -0
  80. package/tsconfig.json +1 -0
  81. package/vitest.config.ts +41 -0
  82. package/webpack.config.js +1 -1
@@ -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)
@@ -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
+