@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.
- package/AlpineWebComponent.ts +158 -0
- package/README.md +48 -0
- package/example/index.html +15 -0
- package/example/package-lock.json +1023 -0
- package/example/package.json +15 -0
- package/example/public/vite.svg +1 -0
- package/example/src/Counter.ts +44 -0
- package/example/src/Input.ts +30 -0
- package/example/src/main.ts +7 -0
- package/example/tsconfig.json +25 -0
- package/package.json +26 -0
- package/tsconfig.json +113 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -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>
|