@mschop/alpine-web-components 1.0.9 → 2.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 +33 -10
- package/README.md +33 -2
- package/package.json +1 -1
package/AlpineWebComponent.ts
CHANGED
|
@@ -20,6 +20,7 @@ export interface State {
|
|
|
20
20
|
abstract class AlpineWebComponent extends HTMLElement {
|
|
21
21
|
|
|
22
22
|
public static isAlpineInitStarted: boolean = false
|
|
23
|
+
private isInitialized: boolean = false
|
|
23
24
|
protected state: State = {attributes: {}}
|
|
24
25
|
protected abstract template: string
|
|
25
26
|
protected styles: () => string = () => ``
|
|
@@ -33,15 +34,11 @@ abstract class AlpineWebComponent extends HTMLElement {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
public connectedCallback() {
|
|
37
|
-
if (AlpineWebComponent.isAlpineInitStarted) {
|
|
38
|
-
this.init()
|
|
39
|
-
} else {
|
|
40
|
-
document.addEventListener('alpine:init', this.alpineInitHandler);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
37
|
public init() {
|
|
38
|
+
if (this.isInitialized) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.isInitialized = true;
|
|
45
42
|
AlpineWebComponent.isAlpineInitStarted = true
|
|
46
43
|
|
|
47
44
|
this.loadTemplate();
|
|
@@ -81,7 +78,7 @@ abstract class AlpineWebComponent extends HTMLElement {
|
|
|
81
78
|
let state = this.state;
|
|
82
79
|
|
|
83
80
|
// @ts-ignore
|
|
84
|
-
state.component = this;
|
|
81
|
+
state.component = this.markRaw(this);
|
|
85
82
|
|
|
86
83
|
if (typeof state.attributes === 'undefined') {
|
|
87
84
|
state.attributes = {};
|
|
@@ -92,6 +89,17 @@ abstract class AlpineWebComponent extends HTMLElement {
|
|
|
92
89
|
this.state = Alpine.reactive(state);
|
|
93
90
|
}
|
|
94
91
|
|
|
92
|
+
protected markRaw<T>(value: T): T {
|
|
93
|
+
if (value !== null && typeof value === 'object') {
|
|
94
|
+
Object.defineProperty(value, '__v_skip', {
|
|
95
|
+
value: true,
|
|
96
|
+
enumerable: false,
|
|
97
|
+
configurable: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
|
|
95
103
|
protected bootAlpine() {
|
|
96
104
|
// @ts-ignore
|
|
97
105
|
Alpine.addScopeToNode(this.getRoot(), this.state)
|
|
@@ -136,6 +144,21 @@ abstract class AlpineWebComponent extends HTMLElement {
|
|
|
136
144
|
})
|
|
137
145
|
}
|
|
138
146
|
|
|
147
|
+
protected initChildren(): void {
|
|
148
|
+
const root = this.getRoot();
|
|
149
|
+
if (root === null) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
root.querySelectorAll('*').forEach((node) => {
|
|
153
|
+
if (node === this) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (node instanceof AlpineWebComponent) {
|
|
157
|
+
node.init();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
139
162
|
protected updateAttribute(key: string): void
|
|
140
163
|
{
|
|
141
164
|
const newValue = this.castToAttribute(key, this.state.attributes[key]);
|
|
@@ -163,4 +186,4 @@ document.addEventListener('alpine:initialized', () => {
|
|
|
163
186
|
AlpineWebComponent.isAlpineInitStarted = true
|
|
164
187
|
})
|
|
165
188
|
|
|
166
|
-
export default AlpineWebComponent
|
|
189
|
+
export default AlpineWebComponent
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ This library helps you with using AlpineJS with web components. This includes we
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
1. Make sure you have AlpineJS installed and ready.
|
|
8
|
-
2. Install this library by running `npm i alpine-web-components`
|
|
8
|
+
2. Install this library by running `npm i @mschop/alpine-web-components`
|
|
9
9
|
3. Use classes AlpineWebComponent or AlpineComponent as base classes.
|
|
10
10
|
|
|
11
11
|
## How to use it
|
|
@@ -45,6 +45,37 @@ class Counter extends AlpineWebComponent {
|
|
|
45
45
|
window.customElements.define('my-counter', Counter);
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
You should now be able to reference the counter with `<my-counter></my-counter
|
|
48
|
+
You should now be able to reference the counter with `<my-counter></my-counter>`.
|
|
49
|
+
|
|
50
|
+
Note: components do not auto-initialize on connect. Call `init()` on your top-level component when you want Alpine to boot. Sub-components will be automatically initialized.
|
|
51
|
+
|
|
52
|
+
The component instance is attached as `state.component` but marked raw, so Alpine won't proxy the full custom element. You can call component methods from the Alpine scope:
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
<button @click="component.resetCounter()">Reset</button>
|
|
56
|
+
```
|
|
49
57
|
|
|
50
58
|
Please see directory `example` for example on how to bind / update attributes etc..
|
|
59
|
+
|
|
60
|
+
## Attribute-based state (string-only, isolated)
|
|
61
|
+
|
|
62
|
+
The default behavior mirrors component state to HTML attributes (and reads attributes back into state). This keeps the component interface purely declarative and works well for primitives (string/number/boolean).
|
|
63
|
+
|
|
64
|
+
Key points:
|
|
65
|
+
- Attributes are always strings, so complex data must be serialized (e.g. JSON) via `attributeCasts`.
|
|
66
|
+
- When state changes, the attribute is updated and an `updated-<key>` event is emitted.
|
|
67
|
+
- When an attribute changes externally, the component state is updated.
|
|
68
|
+
|
|
69
|
+
This approach is useful when you want a clean HTML-only API and strict isolation between components. For complex data, consider property binding with `x-effect` (see section above).
|
|
70
|
+
|
|
71
|
+
## Passing complex data (avoid JSON in attributes)
|
|
72
|
+
|
|
73
|
+
Attributes are strings, so passing objects requires JSON encoding/decoding. If you want to share complex data (objects, arrays, reactive proxies) between components, bind a **property** instead of an attribute.
|
|
74
|
+
|
|
75
|
+
Alpine's `x-effect` runs once on init and re-runs when dependencies change, so you can declaratively sync a property without serialization:
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<my-child x-effect="$el.state.shared = parentState"></my-child>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This keeps the template declarative while passing the actual object reference. Use attributes for primitives and properties for complex data.
|