@mschop/alpine-web-components 1.0.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.
@@ -0,0 +1,158 @@
1
+ import Alpine from 'alpinejs';
2
+
3
+
4
+ export interface AttributeCasts {
5
+ [keyof: string]: (attribute: string) => any
6
+ }
7
+
8
+ export interface Attributes {
9
+ [keyof: string]: any
10
+ }
11
+
12
+ export interface State {
13
+ attributes: Attributes,
14
+ }
15
+
16
+ abstract class AlpineWebComponent extends HTMLElement {
17
+
18
+ public static isAlpineInitStarted: boolean = false
19
+ protected state: State = {attributes: {}}
20
+ protected abstract template: string
21
+ protected styles: () => string = () => ``
22
+ protected attributeCasts: AttributeCasts = {};
23
+ protected updatingAttribute = false;
24
+ private alpineInitHandler = () => this.init()
25
+
26
+ constructor() {
27
+ super()
28
+ if (this.useShadow()) {
29
+ this.attachShadow({mode: 'open'});
30
+ }
31
+ }
32
+
33
+ public connectedCallback() {
34
+ if (AlpineWebComponent.isAlpineInitStarted) {
35
+ this.init()
36
+ } else {
37
+ document.addEventListener('alpine:init', this.alpineInitHandler);
38
+ }
39
+ }
40
+
41
+ public init() {
42
+ AlpineWebComponent.isAlpineInitStarted = true
43
+
44
+ this.loadTemplate();
45
+ this.bootState();
46
+ this.bootAlpine();
47
+ this.bootAttributeObservation();
48
+ }
49
+
50
+ disconnectedCallback(): void {
51
+ document.removeEventListener('alpine:init', this.alpineInitHandler);
52
+ // @ts-ignore
53
+ Alpine.destroyTree(this.getRoot())
54
+ }
55
+
56
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
57
+ if (this.state.attributes === undefined) {
58
+ this.state.attributes = {};
59
+ }
60
+
61
+ if (oldValue === newValue) {
62
+ return;
63
+ }
64
+
65
+ if (this.state.attributes[name] === newValue) {
66
+ return;
67
+ }
68
+
69
+ console.log("attributeChangedCallback", name, oldValue, newValue);
70
+
71
+ this.state.attributes[name] = this.castAttribute(name, newValue);
72
+ }
73
+
74
+ private loadTemplate() {
75
+ this.getRoot().innerHTML = this.template;
76
+ }
77
+
78
+ private bootState() {
79
+ // const initStateJson = this.attributes['initial-state']
80
+ // if (typeof initStateJson === 'string') {
81
+ // const initState = JSON.parse(initStateJson);
82
+ // Object.keys(initState).forEach((key) => {
83
+ // this.state[key] = initState[key]
84
+ // })
85
+ // }
86
+
87
+ let state = this.state;
88
+
89
+ // @ts-ignore
90
+ state.component = this;
91
+
92
+ if (typeof state.attributes === 'undefined') {
93
+ state.attributes = {};
94
+ }
95
+
96
+ this.bootAttributes();
97
+
98
+ this.state = Alpine.reactive(state);
99
+ }
100
+
101
+ private bootAlpine() {
102
+ // @ts-ignore
103
+ Alpine.addScopeToNode(this.getRoot(), this.state)
104
+ // @ts-ignore
105
+ Alpine.initTree(this.getRoot())
106
+ }
107
+
108
+ private bootAttributes(): void {
109
+ // @ts-ignore
110
+ this.constructor.observedAttributes?.forEach((key: string) => {
111
+ const value = this.getAttribute(key);
112
+
113
+ if (value === null) {
114
+ return;
115
+ }
116
+
117
+ this.state.attributes[key] = this.castAttribute(key, value)
118
+ })
119
+ }
120
+
121
+ private castAttribute(name: string, value: string): any {
122
+ return this.attributeCasts[name] === undefined ? value : this.attributeCasts[name](value);
123
+ }
124
+
125
+ protected getRoot(): this|ShadowRoot {
126
+ return this.shadowRoot ?? this;
127
+ }
128
+
129
+ protected bootAttributeObservation() {
130
+ Object.keys(this.state.attributes).forEach(key => {
131
+ Alpine.effect(() => this.updateAttribute(key))
132
+ })
133
+ }
134
+
135
+ protected updateAttribute(key: string): void
136
+ {
137
+ const newValue = this.state.attributes[key];
138
+ const currentValue = this.getAttribute(key);
139
+ if (newValue === currentValue) {
140
+ return;
141
+ }
142
+ this.setAttribute(key, this.state.attributes[key])
143
+ this.dispatchEvent(
144
+ new CustomEvent(
145
+ 'updated-' + key,
146
+ {detail: {key: key, value: this.state.attributes[key]}},
147
+ )
148
+ )
149
+ }
150
+
151
+ protected abstract useShadow(): boolean;
152
+ }
153
+
154
+ document.addEventListener('alpine:initialized', () => {
155
+ AlpineWebComponent.isAlpineInitStarted = true
156
+ })
157
+
158
+ export default AlpineWebComponent
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # alpine-web-components
2
+
3
+ This library helps you with using AlpineJS with web components. This includes web components using shadow root.
4
+
5
+ ## Install
6
+
7
+ 1. Make sure you have AlpineJS installed and ready.
8
+ 2. Install this library by running `npm i alpine-web-components`
9
+ 3. Use classes AlpineWebComponent or AlpineComponent as base classes.
10
+
11
+ ## How to use it
12
+
13
+ The following example shows you a basic "Counter" example.
14
+
15
+ ```ts
16
+ import AlpineWebComponent from "alpine-shadow-component";
17
+
18
+ interface State {
19
+ counter: number,
20
+ }
21
+
22
+ class Counter extends AlpineWebComponent {
23
+
24
+ state: State = {
25
+ counter: 1,
26
+ }
27
+
28
+ template = `
29
+ <span x-text=""></span>
30
+ <button @click="counter++">+1</button>
31
+ <button @click="component.resetCounter()">Reset</button>
32
+ `
33
+
34
+ resetCounter() {
35
+ this.state.counter = 1
36
+ }
37
+
38
+ useShadow(): boolean {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ window.define('my-counter', Counter);
44
+ ```
45
+
46
+ You should now be able to reference the counter with `<my-counter></my-counter>`
47
+
48
+ Please see directory `example` for more details.
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Vite + TS</title>
8
+ </head>
9
+ <body>
10
+ <div id="app">
11
+ <my-counter></my-counter>
12
+ </div>
13
+ <script type="module" src="/src/main.ts"></script>
14
+ </body>
15
+ </html>