@ramstack/alpinegear-bound 1.0.0-preview.1
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/README.md +259 -0
- package/alpinegear-bound.esm.js +414 -0
- package/alpinegear-bound.esm.min.js +1 -0
- package/alpinegear-bound.js +419 -0
- package/alpinegear-bound.min.js +1 -0
- package/package.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# @ramstack/alpinegear-bound
|
|
2
|
+
|
|
3
|
+
`@ramstack/alpinegear-bound` is a plugin for [Alpine.js](https://alpinejs.dev/) that provides the `x-bound` directive.
|
|
4
|
+
|
|
5
|
+
This directive allows for two-way binding between input elements and their associated data properties. It works similarly to the binding provided by [Svelte](https://svelte.dev/docs/element-directives#bind-property) and also supports synchronizing values between two `Alpine.js` data properties.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### Using CDN
|
|
10
|
+
To include the CDN version of this plugin, add the following `<script>` tag before the core `alpine.js` file:
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<!-- alpine.js plugin -->
|
|
14
|
+
<script src="https://cdn.jsdelivr.net/npm/@ramstack/alpinegear-bound@1/alpinegear-bound.min.js" defer></script>
|
|
15
|
+
|
|
16
|
+
<!-- alpine.js -->
|
|
17
|
+
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Using NPM
|
|
21
|
+
Alternatively, you can install the plugin via `npm`:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install --save @ramstack/alpinegear-bound
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then initialize it in your bundle:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import Alpine from "alpinejs";
|
|
31
|
+
import bound from "@ramstack/alpinegear-bound";
|
|
32
|
+
|
|
33
|
+
Alpine.plugin(bound);
|
|
34
|
+
Alpine.start();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
The syntax for binding properties is `x-bound:property="dataprop"`.
|
|
40
|
+
|
|
41
|
+
Let's take the following example:
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<div x-data="{ name: '' }">
|
|
45
|
+
<input x-bound:value="name" />
|
|
46
|
+
Hello <span x-text="name"></span>!
|
|
47
|
+
|
|
48
|
+
<button @click="name = 'John'">Change Name</button>
|
|
49
|
+
</div>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
In this example, we bind the `name` property to the `value` property of the `<input>` element. Since `x-bound` provides two-way binding, any changes to `name` will be reflected in the `<input>` element, as will occur when the `button` is clicked.
|
|
53
|
+
|
|
54
|
+
### Shorthand Syntax
|
|
55
|
+
The `x-bound` directive also supports shorthand syntax: `&`.
|
|
56
|
+
|
|
57
|
+
So the previous example could be written as follows:
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<input &value="name" />
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If the element's property name matches the bound data property, you can simplify this further:
|
|
64
|
+
```html
|
|
65
|
+
<input x-bound:value />
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
In this example, the repetition of the `value` in `x-bound:value="value"` is redundant, so we can simply shorten it to `<input x-bound:value>`. Since we can use `&` instead of `x-bound`, the example can be written as follows:
|
|
69
|
+
|
|
70
|
+
```html
|
|
71
|
+
<input &value />
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
More examples:
|
|
75
|
+
|
|
76
|
+
```html
|
|
77
|
+
<div x-data="{ name: '', text: '', yes: true }">
|
|
78
|
+
<input &value="name" />
|
|
79
|
+
<textarea &value="text"></textarea>
|
|
80
|
+
<input &checked="yes" type="checkbox" />
|
|
81
|
+
</div>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Binding Numeric Inputs
|
|
85
|
+
|
|
86
|
+
For `<input>` elements with `type="number"` and `type="range"`, values are automatically coerced into numbers. If the `<input>` value is empty or invalid, the bound property will be set to `null`.
|
|
87
|
+
|
|
88
|
+
```html
|
|
89
|
+
<input &value="number" type="number" />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Binding `<input type="file">`
|
|
93
|
+
|
|
94
|
+
For `<input>` elements with `type="file"`, the binding is applied to the `files` property, resulting in a [FileList](https://developer.mozilla.org/en-US/docs/Web/API/FileList) object being assigned, containing the list of selected files.
|
|
95
|
+
|
|
96
|
+
```html
|
|
97
|
+
<input &files type="file" accept="image/jpeg" />
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> [!NOTE]
|
|
101
|
+
> The `files` binding is one-way.
|
|
102
|
+
|
|
103
|
+
### Binding `<select>`
|
|
104
|
+
|
|
105
|
+
To bind the value of a `<select>` element, use the `value` property:
|
|
106
|
+
```html
|
|
107
|
+
<select &value="pet">
|
|
108
|
+
<option value="cat">Cat</option>
|
|
109
|
+
<option value="goldfish">Goldfish</option>
|
|
110
|
+
<option value="parrot">Parrot</option>
|
|
111
|
+
</select>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
For a `<select multiple>` element, the data property is an array containing the values of the selected options.
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<div x-data="{ pets: ['goldfish', 'parrot'] }">
|
|
118
|
+
<select &value="pets" multiple>
|
|
119
|
+
<option value="cat">Cat</option>
|
|
120
|
+
<option value="goldfish">Goldfish</option>
|
|
121
|
+
<option value="parrot">Parrot</option>
|
|
122
|
+
<option value="spider">Spider</option>
|
|
123
|
+
</select>
|
|
124
|
+
|
|
125
|
+
Pets: <span x-text="pets"></span>
|
|
126
|
+
</div>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Binding `<details>`
|
|
130
|
+
|
|
131
|
+
The directive also allows binding to the `open` property of `<details>` elements:
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<details &open="isOpen">
|
|
135
|
+
<summary>Details</summary>
|
|
136
|
+
<p>Something small enough to escape casual notice.</p>
|
|
137
|
+
</details>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Binding `<img>` sizes
|
|
141
|
+
|
|
142
|
+
You can bind the `naturalWidth` and `naturalHeight` properties of an image after it loads:
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<img src="..." &naturalWidth="width" &naturalHeight="height" />
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> [!TIP]
|
|
149
|
+
> If you prefer using `kebab-case` for multi-word properties like `naturalWidth`, you can write it as `natural-width`. It will be automatically normalized internally:
|
|
150
|
+
> ```html
|
|
151
|
+
> <img src="..." &natural-width="width" &natural-height="height" />
|
|
152
|
+
> ```
|
|
153
|
+
|
|
154
|
+
> [!NOTE]
|
|
155
|
+
> As HTML attributes are case-insensitive, corresponding properties can be written as follows:
|
|
156
|
+
> ```html
|
|
157
|
+
> <img src="..." &naturalwidth="width" &naturalheight="height" />
|
|
158
|
+
> ```
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
> [!NOTE]
|
|
162
|
+
> The `naturalWidth` and `naturalHeight` properties are read-only and reflect the original image dimensions, available after the image has loaded.
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
### Binding `contenteditable` elements
|
|
166
|
+
|
|
167
|
+
For `contenteditable` elements, you can bind the following properties:
|
|
168
|
+
- [innerHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML)
|
|
169
|
+
- [innerText](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText)
|
|
170
|
+
- [textContent](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent)
|
|
171
|
+
|
|
172
|
+
```html
|
|
173
|
+
<div &inner-html="html" contenteditable="true"></div>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Binding block-level element sizes
|
|
177
|
+
|
|
178
|
+
You can bind to the following properties to get the **width** and **height** of block-level elements. The values will update whenever the element's size changes:
|
|
179
|
+
|
|
180
|
+
- [clientHeight](https://developer.mozilla.org/en-US/docs/Web/API/Element/clientHeight)
|
|
181
|
+
- [clientWidth](https://developer.mozilla.org/en-US/docs/Web/API/Element/clientWidth)
|
|
182
|
+
- [offsetHeight](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetHeight)
|
|
183
|
+
- [offsetWidth](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetWidth)
|
|
184
|
+
|
|
185
|
+
```html
|
|
186
|
+
<div &client-width="width" &client-height="height"></div>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
> [!NOTE]
|
|
190
|
+
> These properties are read-only.
|
|
191
|
+
|
|
192
|
+
### Binding group of `<input type="radio">` and `<input type="checkbox">`
|
|
193
|
+
The group of `<input>` elements that should function together can utilize the `group` bound property.
|
|
194
|
+
|
|
195
|
+
```html
|
|
196
|
+
<div x-data="{ pets: ['goldfish', 'parrot'], contact: 'Email' }">
|
|
197
|
+
|
|
198
|
+
<!-- grouped checkboxes are similar to "select multiple"
|
|
199
|
+
and use an array for selected options -->
|
|
200
|
+
<input &group="pets" type="checkbox" value="cat" />
|
|
201
|
+
<input &group="pets" type="checkbox" value="goldfish" />
|
|
202
|
+
<input &group="pets" type="checkbox" value="parrot" />
|
|
203
|
+
<input &group="pets" type="checkbox" value="spider" />
|
|
204
|
+
|
|
205
|
+
<!-- grouped radio inputs are mutually exclusive -->
|
|
206
|
+
<input &group="contact" type="radio" value="Email" />
|
|
207
|
+
<input &group="contact" type="radio" value="Phone" />
|
|
208
|
+
<input &group="contact" type="radio" value="Mail" />
|
|
209
|
+
|
|
210
|
+
</div>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Binding `Alpine` data properties
|
|
214
|
+
|
|
215
|
+
The directive also supports synchronizing values between two data properties.
|
|
216
|
+
|
|
217
|
+
```html
|
|
218
|
+
<div x-data="{ number: 5 }">
|
|
219
|
+
<div x-data="{ count: 0 }" &count="number">
|
|
220
|
+
<button @click="count++">Increment</button>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
Number: <span x-text="number"></span>
|
|
224
|
+
</div>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
In this example, we bind the outer `number` property to the inner `count` property. Since `number` is initially set to `5`, the `count` property is also set to `5` when the binding occurs.
|
|
228
|
+
|
|
229
|
+
By default, the binding is two-way, so changes in `count` are reflected in `number` and vice versa.
|
|
230
|
+
|
|
231
|
+
But what if we want changes to propagate in one direction only? For this case, the `x-bound` directive provides three modifiers to control data flow:
|
|
232
|
+
|
|
233
|
+
> [!TIP]
|
|
234
|
+
> - **`inout`**: Binding works in both directions. This means that changes in one property are automatically reflected in the other and vice versa. This modifier is used by default.
|
|
235
|
+
>
|
|
236
|
+
> Example: If we have the property `&count="number"`, then changing the value of `count` will also change the value of `number`, and vice versa.
|
|
237
|
+
>
|
|
238
|
+
> - **`in`**: Binding works in one direction only — from the source property to the target property. This means that changes in the source property are passed to the target property, but changes in the target property do not affect the source property.
|
|
239
|
+
>
|
|
240
|
+
> Example: If we have `&count.in="number"`, then changes in `number` will be passed to `count`, but changes in `count` will not be reflected in `number`.
|
|
241
|
+
>
|
|
242
|
+
> - **`out`**: Binding works in one direction only — from the target property to the source property. This means that changes in the target property are passed to the source property, but changes in the source property do not affect the target property.
|
|
243
|
+
>
|
|
244
|
+
> Example: If we have `&count.out="number"`, then changes in `count` will be passed to `number`, but changes in `number` will not be reflected in `count`.
|
|
245
|
+
|
|
246
|
+
> [!NOTE]
|
|
247
|
+
> The default behavior (`inout`) can also be achieved using the `x-model` and `x-modelable` directives.
|
|
248
|
+
|
|
249
|
+
## Source code
|
|
250
|
+
You can find the source code for this plugin on GitHub:
|
|
251
|
+
|
|
252
|
+
https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/bound
|
|
253
|
+
|
|
254
|
+
## Contributions
|
|
255
|
+
Bug reports and contributions are welcome.
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
This package is released as open source under the **MIT License**.
|
|
259
|
+
See the [LICENSE](https://github.com/rameel/ramstack.alpinegear.js/blob/main/LICENSE) file for more details.
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
function create_getter(evaluate_later, ...args) {
|
|
2
|
+
const evaluate = evaluate_later(...args);
|
|
3
|
+
return () => {
|
|
4
|
+
let result;
|
|
5
|
+
evaluate(v => result = v);
|
|
6
|
+
return has_getter(result) ? result.get() : result;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function create_setter(evaluate_later, ...args) {
|
|
11
|
+
const evaluate = evaluate_later(...args);
|
|
12
|
+
args[args.length - 1] = `${ args.at(-1) } = __val`;
|
|
13
|
+
const set = evaluate_later(...args);
|
|
14
|
+
|
|
15
|
+
return value => {
|
|
16
|
+
let result;
|
|
17
|
+
evaluate(v => result = v);
|
|
18
|
+
|
|
19
|
+
if (has_setter(result)) {
|
|
20
|
+
result.set(value);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
set(() => { }, {
|
|
24
|
+
scope: {
|
|
25
|
+
__val: value
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function has_getter(value) {
|
|
33
|
+
return typeof value?.get === "function";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function has_setter(value) {
|
|
37
|
+
return typeof value?.set === "function";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const key = Symbol();
|
|
41
|
+
let observable;
|
|
42
|
+
|
|
43
|
+
function observe_resize(el, listener) {
|
|
44
|
+
observable ??= new ResizeObserver(entries => {
|
|
45
|
+
for (const e of entries) {
|
|
46
|
+
for (const callback of e.target[key]?.values() ?? []) {
|
|
47
|
+
callback(e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
el[key] ??= new Set();
|
|
53
|
+
el[key].add(listener);
|
|
54
|
+
|
|
55
|
+
observable.observe(el);
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
el[key].delete(listener);
|
|
59
|
+
|
|
60
|
+
if (!el[key].size) {
|
|
61
|
+
observable.unobserve(el);
|
|
62
|
+
el[key] = null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const warn = (...args) => console.warn("alpine-gear.js:", ...args);
|
|
68
|
+
const is_array = Array.isArray;
|
|
69
|
+
const is_nullish = value => value === null || value === undefined;
|
|
70
|
+
const is_checkable_input = el => el.type === "checkbox" || el.type === "radio";
|
|
71
|
+
const is_numeric_input = el => el.type === "number" || el.type === "range";
|
|
72
|
+
const as_array = value => is_array(value) ? value : [value];
|
|
73
|
+
const loose_equal = (a, b) => a == b;
|
|
74
|
+
const loose_index_of = (array, value) => array.findIndex(v => v == value);
|
|
75
|
+
const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
|
|
76
|
+
|
|
77
|
+
function assert(value, message) {
|
|
78
|
+
if (!value) {
|
|
79
|
+
throw new Error(message);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const listen = (target, type, listener, options) => {
|
|
84
|
+
target.addEventListener(type, listener, options);
|
|
85
|
+
return () => target.removeEventListener(type, listener, options);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const clone = value =>
|
|
89
|
+
typeof value === "object"
|
|
90
|
+
? JSON.parse(JSON.stringify(value))
|
|
91
|
+
: value;
|
|
92
|
+
|
|
93
|
+
const closest = (el, callback) => {
|
|
94
|
+
while (el && !callback(el)) {
|
|
95
|
+
el = (el._x_teleportBack ?? el).parentElement;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return el;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const create_map = keys => new Map(
|
|
102
|
+
keys.split(",").map(v => [v.trim().toLowerCase(), v.trim()]));
|
|
103
|
+
|
|
104
|
+
function watch(get_value, callback, options = null) {
|
|
105
|
+
assert(Alpine, "Alpine is not defined.");
|
|
106
|
+
|
|
107
|
+
const {
|
|
108
|
+
effect,
|
|
109
|
+
release
|
|
110
|
+
} = Alpine;
|
|
111
|
+
|
|
112
|
+
let new_value;
|
|
113
|
+
let old_value;
|
|
114
|
+
let initialized = false;
|
|
115
|
+
|
|
116
|
+
const handle = effect(() => {
|
|
117
|
+
new_value = get_value();
|
|
118
|
+
|
|
119
|
+
if (!initialized) {
|
|
120
|
+
options?.deep && JSON.stringify(new_value);
|
|
121
|
+
old_value = new_value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (initialized || (options?.immediate ?? true)) {
|
|
125
|
+
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
callback(new_value, old_value);
|
|
128
|
+
old_value = new_value;
|
|
129
|
+
}, 0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
initialized = true;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return () => release(handle);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const canonical_names = create_map(
|
|
139
|
+
"value,checked,files," +
|
|
140
|
+
"innerHTML,innerText,textContent," +
|
|
141
|
+
"videoHeight,videoWidth," +
|
|
142
|
+
"naturalHeight,naturalWidth," +
|
|
143
|
+
"clientHeight,clientWidth,offsetHeight,offsetWidth," +
|
|
144
|
+
"open," +
|
|
145
|
+
"group");
|
|
146
|
+
|
|
147
|
+
function plugin({ directive, entangle, evaluateLater, mapAttributes, mutateDom, prefixed }) {
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
mapAttributes(attr => ({
|
|
153
|
+
name: attr.name.replace(/^&/, prefixed("bound:")),
|
|
154
|
+
value: attr.value
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
directive("bound", (el, { expression, value, modifiers }, { effect, cleanup }) => {
|
|
158
|
+
if (!value) {
|
|
159
|
+
warn("x-bound directive expects the presence of a bound property name.");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const tag_name = el.tagName.toUpperCase();
|
|
164
|
+
|
|
165
|
+
expression = expression?.trim();
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
const property_name = canonical_names.get(value.trim().replace("-", "").toLowerCase());
|
|
170
|
+
|
|
171
|
+
// if the expression is omitted, then we assume it corresponds
|
|
172
|
+
// to the bound property name, allowing us to write expressions more concisely,
|
|
173
|
+
// and write &value instead of &value="value"
|
|
174
|
+
expression ||= property_name;
|
|
175
|
+
|
|
176
|
+
const get_value = create_getter(evaluateLater, el, expression);
|
|
177
|
+
const set_value = create_setter(evaluateLater, el, expression);
|
|
178
|
+
|
|
179
|
+
const update_property = () => loose_equal(el[property_name], get_value()) || mutateDom(() => el[property_name] = get_value());
|
|
180
|
+
const update_variable = () => set_value(is_numeric_input(el) ? to_number(el[property_name]) : el[property_name]);
|
|
181
|
+
|
|
182
|
+
let processed;
|
|
183
|
+
|
|
184
|
+
switch (property_name) {
|
|
185
|
+
case "value":
|
|
186
|
+
process_value();
|
|
187
|
+
break;
|
|
188
|
+
|
|
189
|
+
case "checked":
|
|
190
|
+
process_checked();
|
|
191
|
+
break;
|
|
192
|
+
|
|
193
|
+
case "files":
|
|
194
|
+
process_files();
|
|
195
|
+
break;
|
|
196
|
+
|
|
197
|
+
case "innerHTML":
|
|
198
|
+
case "innerText":
|
|
199
|
+
case "textContent":
|
|
200
|
+
process_contenteditable();
|
|
201
|
+
break;
|
|
202
|
+
|
|
203
|
+
case "videoHeight":
|
|
204
|
+
case "videoWidth":
|
|
205
|
+
process_media_resize("VIDEO", "resize");
|
|
206
|
+
break;
|
|
207
|
+
|
|
208
|
+
case "naturalHeight":
|
|
209
|
+
case "naturalWidth":
|
|
210
|
+
process_media_resize("IMG", "load");
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
case "clientHeight":
|
|
214
|
+
case "clientWidth":
|
|
215
|
+
case "offsetHeight":
|
|
216
|
+
case "offsetWidth":
|
|
217
|
+
process_dimensions();
|
|
218
|
+
break;
|
|
219
|
+
|
|
220
|
+
case "open":
|
|
221
|
+
process_details();
|
|
222
|
+
break;
|
|
223
|
+
|
|
224
|
+
case "group":
|
|
225
|
+
process_group();
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!processed) {
|
|
230
|
+
const modifier =
|
|
231
|
+
has_modifier(modifiers, "in") ? "in" :
|
|
232
|
+
has_modifier(modifiers, "out") ? "out" : "inout";
|
|
233
|
+
|
|
234
|
+
const source_el = expression === value
|
|
235
|
+
? closest(el.parentNode, node => node._x_dataStack)
|
|
236
|
+
: el;
|
|
237
|
+
|
|
238
|
+
if (!el._x_dataStack) {
|
|
239
|
+
warn("x-bound directive requires the presence of the x-data directive to bind component properties.");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!source_el) {
|
|
244
|
+
warn(`x-bound directive cannot find the parent scope where the '${ value }' property is defined.`);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const source = {
|
|
249
|
+
get: create_getter(evaluateLater, source_el, expression),
|
|
250
|
+
set: create_setter(evaluateLater, source_el, expression),
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const target = {
|
|
254
|
+
get: create_getter(evaluateLater, el, value),
|
|
255
|
+
set: create_setter(evaluateLater, el, value),
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
switch (modifier) {
|
|
259
|
+
case "in":
|
|
260
|
+
cleanup(watch(() => source.get(), v => target.set(clone(v))));
|
|
261
|
+
break;
|
|
262
|
+
case "out":
|
|
263
|
+
cleanup(watch(() => target.get(), v => source.set(clone(v))));
|
|
264
|
+
break;
|
|
265
|
+
default:
|
|
266
|
+
cleanup(entangle(source, target));
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function process_value() {
|
|
272
|
+
switch (tag_name) {
|
|
273
|
+
case "INPUT":
|
|
274
|
+
case "TEXTAREA":
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
is_nullish(get_value()) && update_variable();
|
|
278
|
+
|
|
279
|
+
effect(update_property);
|
|
280
|
+
cleanup(listen(el, "input", update_variable));
|
|
281
|
+
|
|
282
|
+
processed = true;
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
case "SELECT":
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
queueMicrotask(() => {
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
is_nullish(get_value()) && update_variable();
|
|
297
|
+
|
|
298
|
+
effect(() => apply_select_values(el, as_array(get_value() ?? [])));
|
|
299
|
+
cleanup(listen(el, "change", () => set_value(collect_selected_values(el))));
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
processed = true;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function process_checked() {
|
|
308
|
+
if (is_checkable_input(el)) {
|
|
309
|
+
effect(update_property);
|
|
310
|
+
cleanup(listen(el, "change", update_variable));
|
|
311
|
+
processed = true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function process_files() {
|
|
316
|
+
if (el.type === "file") {
|
|
317
|
+
cleanup(listen(el, "input", update_variable));
|
|
318
|
+
processed = true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function process_contenteditable() {
|
|
323
|
+
if (el.contentEditable === "true") {
|
|
324
|
+
is_nullish(get_value()) && update_variable();
|
|
325
|
+
|
|
326
|
+
effect(update_property);
|
|
327
|
+
cleanup(listen(el, "input", update_variable));
|
|
328
|
+
processed = true;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function process_media_resize(name, event_name) {
|
|
333
|
+
if (tag_name === name) {
|
|
334
|
+
update_variable();
|
|
335
|
+
cleanup(listen(el, event_name, update_variable));
|
|
336
|
+
processed = true;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function process_dimensions() {
|
|
341
|
+
cleanup(observe_resize(el, update_variable));
|
|
342
|
+
processed = true;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function process_details() {
|
|
346
|
+
if (tag_name === "DETAILS") {
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
is_nullish(get_value()) && update_variable();
|
|
350
|
+
|
|
351
|
+
effect(update_property);
|
|
352
|
+
cleanup(listen(el, "toggle", update_variable));
|
|
353
|
+
processed = true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function process_group() {
|
|
358
|
+
if (is_checkable_input(el)) {
|
|
359
|
+
el.name || mutateDom(() => el.name = expression);
|
|
360
|
+
|
|
361
|
+
effect(() =>
|
|
362
|
+
mutateDom(() =>
|
|
363
|
+
apply_group_values(el, get_value() ?? [])));
|
|
364
|
+
|
|
365
|
+
cleanup(listen(el, "input", () => set_value(collect_group_values(el, get_value()))));
|
|
366
|
+
processed = true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function to_number(value) {
|
|
373
|
+
return value === "" ? null : +value;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function apply_select_values(el, values) {
|
|
377
|
+
for (const option of el.options) {
|
|
378
|
+
option.selected = loose_index_of(values, option.value) >= 0;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function collect_selected_values(el) {
|
|
383
|
+
if (el.multiple) {
|
|
384
|
+
return [...el.selectedOptions].map(o => o.value);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return el.value;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function apply_group_values(el, values) {
|
|
391
|
+
el.checked = is_array(values)
|
|
392
|
+
? loose_index_of(values, el.value) >= 0
|
|
393
|
+
: loose_equal(el.value, values);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function collect_group_values(el, values) {
|
|
397
|
+
if (el.type === "radio") {
|
|
398
|
+
return el.value;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
values = as_array(values);
|
|
402
|
+
const index = loose_index_of(values, el.value);
|
|
403
|
+
|
|
404
|
+
if (el.checked) {
|
|
405
|
+
index >= 0 || values.push(el.value);
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
index >= 0 && values.splice(index, 1);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return values;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export { plugin as bound };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,...t){const n=e(...t);return()=>{let e;return n((t=>e=t)),t=e,"function"==typeof t?.get?e.get():e;var t}}function t(e,...t){const n=e(...t);t[t.length-1]=`${t.at(-1)} = __val`;const i=e(...t);return e=>{let t;n((e=>t=e)),function(e){return"function"==typeof e?.set}(t)?t.set(e):i((()=>{}),{scope:{__val:e}})}}const n=Symbol();let i;const a=(...e)=>console.warn("alpine-gear.js:",...e),r=Array.isArray,o=e=>null==e,c=e=>"checkbox"===e.type||"radio"===e.type,s=e=>r(e)?e:[e],u=(e,t)=>e==t,l=(e,t)=>e.findIndex((e=>e==t)),d=(e,t)=>e.includes(t),f=(e,t,n,i)=>(e.addEventListener(t,n,i),()=>e.removeEventListener(t,n,i)),p=e=>"object"==typeof e?JSON.parse(JSON.stringify(e)):e;function v(e,t,n=null){const{effect:i,release:a}=Alpine;let r,o,c=!1;const s=i((()=>{r=e(),c||(n?.deep&&JSON.stringify(r),o=r),(c||(n?.immediate??1))&&setTimeout((()=>{t(r,o),o=r}),0),c=!0}));return()=>a(s)}const h=new Map("value,checked,files,innerHTML,innerText,textContent,videoHeight,videoWidth,naturalHeight,naturalWidth,clientHeight,clientWidth,offsetHeight,offsetWidth,open,group".split(",").map((e=>[e.trim().toLowerCase(),e.trim()])));function g({directive:g,entangle:b,evaluateLater:m,mapAttributes:k,mutateDom:x,prefixed:y}){k((e=>({name:e.name.replace(/^&/,y("bound:")),value:e.value}))),g("bound",((g,{expression:k,value:y,modifiers:w},{effect:E,cleanup:H})=>{if(!y)return void a("x-bound directive expects the presence of a bound property name.");const T=g.tagName.toUpperCase();k=k?.trim();const _=h.get(y.trim().replace("-","").toLowerCase());k||=_;const L=e(m,g,k),S=t(m,g,k),W=()=>u(g[_],L())||x((()=>g[_]=L())),A=()=>S((e=>"number"===e.type||"range"===e.type)(g)?function(e){return""===e?null:+e}(g[_]):g[_]);let C;switch(_){case"value":!function(){switch(T){case"INPUT":case"TEXTAREA":o(L())&&A(),E(W),H(f(g,"input",A)),C=!0;break;case"SELECT":queueMicrotask((()=>{o(L())&&A(),E((()=>function(e,t){for(const n of e.options)n.selected=l(t,n.value)>=0}(g,s(L()??[])))),H(f(g,"change",(()=>S(function(e){return e.multiple?[...e.selectedOptions].map((e=>e.value)):e.value}(g)))))})),C=!0}}();break;case"checked":c(g)&&(E(W),H(f(g,"change",A)),C=!0);break;case"files":"file"===g.type&&(H(f(g,"input",A)),C=!0);break;case"innerHTML":case"innerText":case"textContent":"true"===g.contentEditable&&(o(L())&&A(),E(W),H(f(g,"input",A)),C=!0);break;case"videoHeight":case"videoWidth":N("VIDEO","resize");break;case"naturalHeight":case"naturalWidth":N("IMG","load");break;case"clientHeight":case"clientWidth":case"offsetHeight":case"offsetWidth":H(function(e,t){return i??=new ResizeObserver((e=>{for(const t of e)for(const e of t.target[n]?.values()??[])e(t)})),e[n]??=new Set,e[n].add(t),i.observe(e),()=>{e[n].delete(t),e[n].size||(i.unobserve(e),e[n]=null)}}(g,A)),C=!0;break;case"open":"DETAILS"===T&&(o(L())&&A(),E(W),H(f(g,"toggle",A)),C=!0);break;case"group":c(g)&&(g.name||x((()=>g.name=k)),E((()=>x((()=>function(e,t){e.checked=r(t)?l(t,e.value)>=0:u(e.value,t)}(g,L()??[]))))),H(f(g,"input",(()=>S(function(e,t){if("radio"===e.type)return e.value;t=s(t);const n=l(t,e.value);return e.checked?n>=0||t.push(e.value):n>=0&&t.splice(n,1),t}(g,L()))))),C=!0)}if(!C){const n=d(w,"in")?"in":d(w,"out")?"out":"inout",i=k===y?(e=>{for(;e&&!e._x_dataStack;)e=(e._x_teleportBack??e).parentElement;return e})(g.parentNode):g;if(!g._x_dataStack)return void a("x-bound directive requires the presence of the x-data directive to bind component properties.");if(!i)return void a(`x-bound directive cannot find the parent scope where the '${y}' property is defined.`);const r={get:e(m,i,k),set:t(m,i,k)},o={get:e(m,g,y),set:t(m,g,y)};switch(n){case"in":H(v((()=>r.get()),(e=>o.set(p(e)))));break;case"out":H(v((()=>o.get()),(e=>r.set(p(e)))));break;default:H(b(r,o))}}function N(e,t){T===e&&(A(),H(f(g,t,A)),C=!0)}}))}export{g as bound};
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
function create_getter(evaluate_later, ...args) {
|
|
5
|
+
const evaluate = evaluate_later(...args);
|
|
6
|
+
return () => {
|
|
7
|
+
let result;
|
|
8
|
+
evaluate(v => result = v);
|
|
9
|
+
return has_getter(result) ? result.get() : result;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function create_setter(evaluate_later, ...args) {
|
|
14
|
+
const evaluate = evaluate_later(...args);
|
|
15
|
+
args[args.length - 1] = `${ args.at(-1) } = __val`;
|
|
16
|
+
const set = evaluate_later(...args);
|
|
17
|
+
|
|
18
|
+
return value => {
|
|
19
|
+
let result;
|
|
20
|
+
evaluate(v => result = v);
|
|
21
|
+
|
|
22
|
+
if (has_setter(result)) {
|
|
23
|
+
result.set(value);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
set(() => { }, {
|
|
27
|
+
scope: {
|
|
28
|
+
__val: value
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function has_getter(value) {
|
|
36
|
+
return typeof value?.get === "function";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function has_setter(value) {
|
|
40
|
+
return typeof value?.set === "function";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const key = Symbol();
|
|
44
|
+
let observable;
|
|
45
|
+
|
|
46
|
+
function observe_resize(el, listener) {
|
|
47
|
+
observable ??= new ResizeObserver(entries => {
|
|
48
|
+
for (const e of entries) {
|
|
49
|
+
for (const callback of e.target[key]?.values() ?? []) {
|
|
50
|
+
callback(e);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
el[key] ??= new Set();
|
|
56
|
+
el[key].add(listener);
|
|
57
|
+
|
|
58
|
+
observable.observe(el);
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
el[key].delete(listener);
|
|
62
|
+
|
|
63
|
+
if (!el[key].size) {
|
|
64
|
+
observable.unobserve(el);
|
|
65
|
+
el[key] = null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const warn = (...args) => console.warn("alpine-gear.js:", ...args);
|
|
71
|
+
const is_array = Array.isArray;
|
|
72
|
+
const is_nullish = value => value === null || value === undefined;
|
|
73
|
+
const is_checkable_input = el => el.type === "checkbox" || el.type === "radio";
|
|
74
|
+
const is_numeric_input = el => el.type === "number" || el.type === "range";
|
|
75
|
+
const as_array = value => is_array(value) ? value : [value];
|
|
76
|
+
const loose_equal = (a, b) => a == b;
|
|
77
|
+
const loose_index_of = (array, value) => array.findIndex(v => v == value);
|
|
78
|
+
const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
|
|
79
|
+
|
|
80
|
+
function assert(value, message) {
|
|
81
|
+
if (!value) {
|
|
82
|
+
throw new Error(message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const listen = (target, type, listener, options) => {
|
|
87
|
+
target.addEventListener(type, listener, options);
|
|
88
|
+
return () => target.removeEventListener(type, listener, options);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const clone = value =>
|
|
92
|
+
typeof value === "object"
|
|
93
|
+
? JSON.parse(JSON.stringify(value))
|
|
94
|
+
: value;
|
|
95
|
+
|
|
96
|
+
const closest = (el, callback) => {
|
|
97
|
+
while (el && !callback(el)) {
|
|
98
|
+
el = (el._x_teleportBack ?? el).parentElement;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return el;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const create_map = keys => new Map(
|
|
105
|
+
keys.split(",").map(v => [v.trim().toLowerCase(), v.trim()]));
|
|
106
|
+
|
|
107
|
+
function watch(get_value, callback, options = null) {
|
|
108
|
+
assert(Alpine, "Alpine is not defined.");
|
|
109
|
+
|
|
110
|
+
const {
|
|
111
|
+
effect,
|
|
112
|
+
release
|
|
113
|
+
} = Alpine;
|
|
114
|
+
|
|
115
|
+
let new_value;
|
|
116
|
+
let old_value;
|
|
117
|
+
let initialized = false;
|
|
118
|
+
|
|
119
|
+
const handle = effect(() => {
|
|
120
|
+
new_value = get_value();
|
|
121
|
+
|
|
122
|
+
if (!initialized) {
|
|
123
|
+
options?.deep && JSON.stringify(new_value);
|
|
124
|
+
old_value = new_value;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (initialized || (options?.immediate ?? true)) {
|
|
128
|
+
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
callback(new_value, old_value);
|
|
131
|
+
old_value = new_value;
|
|
132
|
+
}, 0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
initialized = true;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return () => release(handle);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const canonical_names = create_map(
|
|
142
|
+
"value,checked,files," +
|
|
143
|
+
"innerHTML,innerText,textContent," +
|
|
144
|
+
"videoHeight,videoWidth," +
|
|
145
|
+
"naturalHeight,naturalWidth," +
|
|
146
|
+
"clientHeight,clientWidth,offsetHeight,offsetWidth," +
|
|
147
|
+
"open," +
|
|
148
|
+
"group");
|
|
149
|
+
|
|
150
|
+
function plugin({ directive, entangle, evaluateLater, mapAttributes, mutateDom, prefixed }) {
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
mapAttributes(attr => ({
|
|
156
|
+
name: attr.name.replace(/^&/, prefixed("bound:")),
|
|
157
|
+
value: attr.value
|
|
158
|
+
}));
|
|
159
|
+
|
|
160
|
+
directive("bound", (el, { expression, value, modifiers }, { effect, cleanup }) => {
|
|
161
|
+
if (!value) {
|
|
162
|
+
warn("x-bound directive expects the presence of a bound property name.");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const tag_name = el.tagName.toUpperCase();
|
|
167
|
+
|
|
168
|
+
expression = expression?.trim();
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
const property_name = canonical_names.get(value.trim().replace("-", "").toLowerCase());
|
|
173
|
+
|
|
174
|
+
// if the expression is omitted, then we assume it corresponds
|
|
175
|
+
// to the bound property name, allowing us to write expressions more concisely,
|
|
176
|
+
// and write &value instead of &value="value"
|
|
177
|
+
expression ||= property_name;
|
|
178
|
+
|
|
179
|
+
const get_value = create_getter(evaluateLater, el, expression);
|
|
180
|
+
const set_value = create_setter(evaluateLater, el, expression);
|
|
181
|
+
|
|
182
|
+
const update_property = () => loose_equal(el[property_name], get_value()) || mutateDom(() => el[property_name] = get_value());
|
|
183
|
+
const update_variable = () => set_value(is_numeric_input(el) ? to_number(el[property_name]) : el[property_name]);
|
|
184
|
+
|
|
185
|
+
let processed;
|
|
186
|
+
|
|
187
|
+
switch (property_name) {
|
|
188
|
+
case "value":
|
|
189
|
+
process_value();
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
case "checked":
|
|
193
|
+
process_checked();
|
|
194
|
+
break;
|
|
195
|
+
|
|
196
|
+
case "files":
|
|
197
|
+
process_files();
|
|
198
|
+
break;
|
|
199
|
+
|
|
200
|
+
case "innerHTML":
|
|
201
|
+
case "innerText":
|
|
202
|
+
case "textContent":
|
|
203
|
+
process_contenteditable();
|
|
204
|
+
break;
|
|
205
|
+
|
|
206
|
+
case "videoHeight":
|
|
207
|
+
case "videoWidth":
|
|
208
|
+
process_media_resize("VIDEO", "resize");
|
|
209
|
+
break;
|
|
210
|
+
|
|
211
|
+
case "naturalHeight":
|
|
212
|
+
case "naturalWidth":
|
|
213
|
+
process_media_resize("IMG", "load");
|
|
214
|
+
break;
|
|
215
|
+
|
|
216
|
+
case "clientHeight":
|
|
217
|
+
case "clientWidth":
|
|
218
|
+
case "offsetHeight":
|
|
219
|
+
case "offsetWidth":
|
|
220
|
+
process_dimensions();
|
|
221
|
+
break;
|
|
222
|
+
|
|
223
|
+
case "open":
|
|
224
|
+
process_details();
|
|
225
|
+
break;
|
|
226
|
+
|
|
227
|
+
case "group":
|
|
228
|
+
process_group();
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!processed) {
|
|
233
|
+
const modifier =
|
|
234
|
+
has_modifier(modifiers, "in") ? "in" :
|
|
235
|
+
has_modifier(modifiers, "out") ? "out" : "inout";
|
|
236
|
+
|
|
237
|
+
const source_el = expression === value
|
|
238
|
+
? closest(el.parentNode, node => node._x_dataStack)
|
|
239
|
+
: el;
|
|
240
|
+
|
|
241
|
+
if (!el._x_dataStack) {
|
|
242
|
+
warn("x-bound directive requires the presence of the x-data directive to bind component properties.");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!source_el) {
|
|
247
|
+
warn(`x-bound directive cannot find the parent scope where the '${ value }' property is defined.`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const source = {
|
|
252
|
+
get: create_getter(evaluateLater, source_el, expression),
|
|
253
|
+
set: create_setter(evaluateLater, source_el, expression),
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const target = {
|
|
257
|
+
get: create_getter(evaluateLater, el, value),
|
|
258
|
+
set: create_setter(evaluateLater, el, value),
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
switch (modifier) {
|
|
262
|
+
case "in":
|
|
263
|
+
cleanup(watch(() => source.get(), v => target.set(clone(v))));
|
|
264
|
+
break;
|
|
265
|
+
case "out":
|
|
266
|
+
cleanup(watch(() => target.get(), v => source.set(clone(v))));
|
|
267
|
+
break;
|
|
268
|
+
default:
|
|
269
|
+
cleanup(entangle(source, target));
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function process_value() {
|
|
275
|
+
switch (tag_name) {
|
|
276
|
+
case "INPUT":
|
|
277
|
+
case "TEXTAREA":
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
is_nullish(get_value()) && update_variable();
|
|
281
|
+
|
|
282
|
+
effect(update_property);
|
|
283
|
+
cleanup(listen(el, "input", update_variable));
|
|
284
|
+
|
|
285
|
+
processed = true;
|
|
286
|
+
break;
|
|
287
|
+
|
|
288
|
+
case "SELECT":
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
queueMicrotask(() => {
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
is_nullish(get_value()) && update_variable();
|
|
300
|
+
|
|
301
|
+
effect(() => apply_select_values(el, as_array(get_value() ?? [])));
|
|
302
|
+
cleanup(listen(el, "change", () => set_value(collect_selected_values(el))));
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
processed = true;
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function process_checked() {
|
|
311
|
+
if (is_checkable_input(el)) {
|
|
312
|
+
effect(update_property);
|
|
313
|
+
cleanup(listen(el, "change", update_variable));
|
|
314
|
+
processed = true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function process_files() {
|
|
319
|
+
if (el.type === "file") {
|
|
320
|
+
cleanup(listen(el, "input", update_variable));
|
|
321
|
+
processed = true;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function process_contenteditable() {
|
|
326
|
+
if (el.contentEditable === "true") {
|
|
327
|
+
is_nullish(get_value()) && update_variable();
|
|
328
|
+
|
|
329
|
+
effect(update_property);
|
|
330
|
+
cleanup(listen(el, "input", update_variable));
|
|
331
|
+
processed = true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function process_media_resize(name, event_name) {
|
|
336
|
+
if (tag_name === name) {
|
|
337
|
+
update_variable();
|
|
338
|
+
cleanup(listen(el, event_name, update_variable));
|
|
339
|
+
processed = true;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function process_dimensions() {
|
|
344
|
+
cleanup(observe_resize(el, update_variable));
|
|
345
|
+
processed = true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function process_details() {
|
|
349
|
+
if (tag_name === "DETAILS") {
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
is_nullish(get_value()) && update_variable();
|
|
353
|
+
|
|
354
|
+
effect(update_property);
|
|
355
|
+
cleanup(listen(el, "toggle", update_variable));
|
|
356
|
+
processed = true;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function process_group() {
|
|
361
|
+
if (is_checkable_input(el)) {
|
|
362
|
+
el.name || mutateDom(() => el.name = expression);
|
|
363
|
+
|
|
364
|
+
effect(() =>
|
|
365
|
+
mutateDom(() =>
|
|
366
|
+
apply_group_values(el, get_value() ?? [])));
|
|
367
|
+
|
|
368
|
+
cleanup(listen(el, "input", () => set_value(collect_group_values(el, get_value()))));
|
|
369
|
+
processed = true;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function to_number(value) {
|
|
376
|
+
return value === "" ? null : +value;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function apply_select_values(el, values) {
|
|
380
|
+
for (const option of el.options) {
|
|
381
|
+
option.selected = loose_index_of(values, option.value) >= 0;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function collect_selected_values(el) {
|
|
386
|
+
if (el.multiple) {
|
|
387
|
+
return [...el.selectedOptions].map(o => o.value);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return el.value;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function apply_group_values(el, values) {
|
|
394
|
+
el.checked = is_array(values)
|
|
395
|
+
? loose_index_of(values, el.value) >= 0
|
|
396
|
+
: loose_equal(el.value, values);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function collect_group_values(el, values) {
|
|
400
|
+
if (el.type === "radio") {
|
|
401
|
+
return el.value;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
values = as_array(values);
|
|
405
|
+
const index = loose_index_of(values, el.value);
|
|
406
|
+
|
|
407
|
+
if (el.checked) {
|
|
408
|
+
index >= 0 || values.push(el.value);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
index >= 0 && values.splice(index, 1);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return values;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
document.addEventListener("alpine:init", () => { Alpine.plugin(plugin); });
|
|
418
|
+
|
|
419
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(){"use strict";function e(e,...t){const n=e(...t);return()=>{let e;return n((t=>e=t)),t=e,"function"==typeof t?.get?e.get():e;var t}}function t(e,...t){const n=e(...t);t[t.length-1]=`${t.at(-1)} = __val`;const i=e(...t);return e=>{let t;n((e=>t=e)),function(e){return"function"==typeof e?.set}(t)?t.set(e):i((()=>{}),{scope:{__val:e}})}}const n=Symbol();let i;const a=(...e)=>console.warn("alpine-gear.js:",...e),r=Array.isArray,o=e=>null==e,c=e=>"checkbox"===e.type||"radio"===e.type,s=e=>r(e)?e:[e],u=(e,t)=>e==t,l=(e,t)=>e.findIndex((e=>e==t)),d=(e,t)=>e.includes(t),p=(e,t,n,i)=>(e.addEventListener(t,n,i),()=>e.removeEventListener(t,n,i)),f=e=>"object"==typeof e?JSON.parse(JSON.stringify(e)):e;function v(e,t,n=null){const{effect:i,release:a}=Alpine;let r,o,c=!1;const s=i((()=>{r=e(),c||(n?.deep&&JSON.stringify(r),o=r),(c||(n?.immediate??1))&&setTimeout((()=>{t(r,o),o=r}),0),c=!0}));return()=>a(s)}const h=new Map("value,checked,files,innerHTML,innerText,textContent,videoHeight,videoWidth,naturalHeight,naturalWidth,clientHeight,clientWidth,offsetHeight,offsetWidth,open,group".split(",").map((e=>[e.trim().toLowerCase(),e.trim()])));function g({directive:g,entangle:b,evaluateLater:m,mapAttributes:k,mutateDom:x,prefixed:y}){k((e=>({name:e.name.replace(/^&/,y("bound:")),value:e.value}))),g("bound",((g,{expression:k,value:y,modifiers:E},{effect:w,cleanup:H})=>{if(!y)return void a("x-bound directive expects the presence of a bound property name.");const L=g.tagName.toUpperCase();k=k?.trim();const T=h.get(y.trim().replace("-","").toLowerCase());k||=T;const _=e(m,g,k),S=t(m,g,k),A=()=>u(g[T],_())||x((()=>g[T]=_())),W=()=>S((e=>"number"===e.type||"range"===e.type)(g)?function(e){return""===e?null:+e}(g[T]):g[T]);let C;switch(T){case"value":!function(){switch(L){case"INPUT":case"TEXTAREA":o(_())&&W(),w(A),H(p(g,"input",W)),C=!0;break;case"SELECT":queueMicrotask((()=>{o(_())&&W(),w((()=>function(e,t){for(const n of e.options)n.selected=l(t,n.value)>=0}(g,s(_()??[])))),H(p(g,"change",(()=>S(function(e){return e.multiple?[...e.selectedOptions].map((e=>e.value)):e.value}(g)))))})),C=!0}}();break;case"checked":c(g)&&(w(A),H(p(g,"change",W)),C=!0);break;case"files":"file"===g.type&&(H(p(g,"input",W)),C=!0);break;case"innerHTML":case"innerText":case"textContent":"true"===g.contentEditable&&(o(_())&&W(),w(A),H(p(g,"input",W)),C=!0);break;case"videoHeight":case"videoWidth":N("VIDEO","resize");break;case"naturalHeight":case"naturalWidth":N("IMG","load");break;case"clientHeight":case"clientWidth":case"offsetHeight":case"offsetWidth":H(function(e,t){return i??=new ResizeObserver((e=>{for(const t of e)for(const e of t.target[n]?.values()??[])e(t)})),e[n]??=new Set,e[n].add(t),i.observe(e),()=>{e[n].delete(t),e[n].size||(i.unobserve(e),e[n]=null)}}(g,W)),C=!0;break;case"open":"DETAILS"===L&&(o(_())&&W(),w(A),H(p(g,"toggle",W)),C=!0);break;case"group":c(g)&&(g.name||x((()=>g.name=k)),w((()=>x((()=>function(e,t){e.checked=r(t)?l(t,e.value)>=0:u(e.value,t)}(g,_()??[]))))),H(p(g,"input",(()=>S(function(e,t){if("radio"===e.type)return e.value;t=s(t);const n=l(t,e.value);return e.checked?n>=0||t.push(e.value):n>=0&&t.splice(n,1),t}(g,_()))))),C=!0)}if(!C){const n=d(E,"in")?"in":d(E,"out")?"out":"inout",i=k===y?(e=>{for(;e&&!e._x_dataStack;)e=(e._x_teleportBack??e).parentElement;return e})(g.parentNode):g;if(!g._x_dataStack)return void a("x-bound directive requires the presence of the x-data directive to bind component properties.");if(!i)return void a(`x-bound directive cannot find the parent scope where the '${y}' property is defined.`);const r={get:e(m,i,k),set:t(m,i,k)},o={get:e(m,g,y),set:t(m,g,y)};switch(n){case"in":H(v((()=>r.get()),(e=>o.set(f(e)))));break;case"out":H(v((()=>o.get()),(e=>r.set(f(e)))));break;default:H(b(r,o))}}function N(e,t){L===e&&(W(),H(p(g,t,W)),C=!0)}}))}document.addEventListener("alpine:init",(()=>{Alpine.plugin(g)}))}();
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ramstack/alpinegear-bound",
|
|
3
|
+
"version": "1.0.0-preview.1",
|
|
4
|
+
"description": "@ramstack/alpinegear-bound provides the 'x-bound' Alpine.js directive, which allows for two-way binding of input elements and their associated data properties.",
|
|
5
|
+
"author": "Rameel Burhan",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/rameel/ramstack.alpinegear.js.git",
|
|
10
|
+
"directory": "src/plugins/bound"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"alpine.js",
|
|
14
|
+
"alpinejs"
|
|
15
|
+
],
|
|
16
|
+
"main": "alpinegear-bound.js",
|
|
17
|
+
"module": "alpinegear-bound.esm.js"
|
|
18
|
+
}
|