@ramstack/alpinegear-bound 1.2.2 → 1.2.4

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 CHANGED
@@ -6,6 +6,10 @@
6
6
 
7
7
  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.
8
8
 
9
+ > [!Note]
10
+ > This package is part of the **[`@ramstack/alpinegear-main`](https://www.npmjs.com/package/@ramstack/alpinegear-main)** bundle.
11
+ > If you are using the main bundle, you don't need to install this package separately.
12
+
9
13
  ## Installation
10
14
 
11
15
  ### Using CDN
@@ -16,7 +20,7 @@ To include the CDN version of this plugin, add the following `<script>` tag befo
16
20
  <script src="https://cdn.jsdelivr.net/npm/@ramstack/alpinegear-bound@1/alpinegear-bound.min.js" defer></script>
17
21
 
18
22
  <!-- alpine.js -->
19
- <script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
23
+ <script src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js" defer></script>
20
24
  ```
21
25
 
22
26
  ### Using NPM
@@ -44,12 +48,13 @@ Let's take the following example:
44
48
 
45
49
  ```html
46
50
  <div x-data="{ name: '' }">
47
- <input x-bound:value="name" />
48
- Hello <span x-text="name"></span>!
51
+ <input x-bound:value="name" />
52
+ Hello <span x-text="name"></span>!
49
53
 
50
- <button @click="name = 'John'">Change Name</button>
54
+ <button @click="name = 'John'">Change Name</button>
51
55
  </div>
52
56
  ```
57
+ 🚀 [Live demo | Alpine.js x-bound: Basic usage](https://jsfiddle.net/rameel/8cw23y7o/)
53
58
 
54
59
  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.
55
60
 
@@ -76,12 +81,16 @@ In this example, the repetition of the `value` in `x-bound:value="value"` is red
76
81
  More examples:
77
82
 
78
83
  ```html
79
- <div x-data="{ name: '', text: '', yes: true }">
80
- <input &value="name" />
81
- <textarea &value="text"></textarea>
82
- <input &checked="yes" type="checkbox" />
84
+ <div x-data="{ name: '', text: '', yes: true, methods: [] }">
85
+ <input &value="name" />
86
+ <textarea &value="text"></textarea>
87
+ <input &checked="yes" type="checkbox" />
88
+ <select &value="methods">
89
+ ...
90
+ </select>
83
91
  </div>
84
92
  ```
93
+ 🚀 [Live demo | Alpine.js x-bound: Shorthand Syntax](https://jsfiddle.net/rameel/9ys23n4z/)
85
94
 
86
95
  ### Binding Numeric Inputs
87
96
 
@@ -90,6 +99,7 @@ For `<input>` elements with `type="number"` and `type="range"`, values are autom
90
99
  ```html
91
100
  <input &value="number" type="number" />
92
101
  ```
102
+ 🚀 [Live demo | Alpine.js x-bound: Bind Numeric Inputs](https://jsfiddle.net/rameel/e160vsta/)
93
103
 
94
104
  ### Binding `<input type="file">`
95
105
 
@@ -98,46 +108,68 @@ For `<input>` elements with `type="file"`, the binding is applied to the `files`
98
108
  ```html
99
109
  <input &files type="file" accept="image/jpeg" />
100
110
  ```
111
+ 🚀 [Live demo | Alpine.js x-bound: Bind Files](https://jsfiddle.net/rameel/phy2zn0a/)
101
112
 
102
- > [!NOTE]
103
- > The `files` binding is one-way.
104
113
 
105
114
  ### Binding `<select>`
106
115
 
107
116
  To bind the value of a `<select>` element, use the `value` property:
108
117
  ```html
109
- <select &value="pet">
110
- <option value="cat">Cat</option>
111
- <option value="goldfish">Goldfish</option>
112
- <option value="parrot">Parrot</option>
113
- </select>
118
+ <div x-data="{ fruit: '' }">
119
+ <select &value="fruit">
120
+ <option value="" disabled>Select...</option>
121
+ <option>Apple</option>
122
+ <option>Banana</option>
123
+ <option>Orange</option>
124
+ <option>Grape</option>
125
+ <option>Mango</option>
126
+ </select>
127
+
128
+ <p>
129
+ Fruit: <span x-text="fruit"></span>
130
+ </p>
131
+ </div>
114
132
  ```
133
+ 🚀 [Live demo | Alpine.js x-bound: Binding select](https://jsfiddle.net/rameel/fs12bo5m/)
134
+
115
135
 
116
136
  For a `<select multiple>` element, the data property is an array containing the values of the selected options.
117
137
 
118
138
  ```html
119
139
  <div x-data="{ pets: ['goldfish', 'parrot'] }">
120
- <select &value="pets" multiple>
121
- <option value="cat">Cat</option>
122
- <option value="goldfish">Goldfish</option>
123
- <option value="parrot">Parrot</option>
124
- <option value="spider">Spider</option>
125
- </select>
126
-
127
- Pets: <span x-text="pets"></span>
140
+ <select &value="pets" multiple>
141
+ <option value="cat">Cat</option>
142
+ <option value="goldfish">Goldfish</option>
143
+ <option value="parrot">Parrot</option>
144
+ <option value="spider">Spider</option>
145
+ </select>
146
+
147
+ Pets: <span x-text="pets"></span>
128
148
  </div>
129
149
  ```
150
+ 🚀 [Live demo | Alpine.js x-bound: Multiple select](https://jsfiddle.net/rameel/kq0xseo1/)
151
+
130
152
 
131
153
  ### Binding `<details>`
132
154
 
133
155
  The directive also allows binding to the `open` property of `<details>` elements:
134
156
 
135
157
  ```html
136
- <details &open="isOpen">
158
+ <div x-data="{ open: true }">
159
+ <details &open>
137
160
  <summary>Details</summary>
138
161
  <p>Something small enough to escape casual notice.</p>
139
- </details>
162
+ </details>
163
+
164
+ <p>
165
+ <label>
166
+ <input &checked="open" type="checkbox" />
167
+ Open / Close
168
+ </label>
169
+ </p>
170
+ </div>
140
171
  ```
172
+ 🚀 [Live demo | Alpine.js x-bound: Binding details](https://jsfiddle.net/rameel/fw2bkLqv/)
141
173
 
142
174
  ### Binding `<img>` sizes
143
175
 
@@ -146,6 +178,7 @@ You can bind the `naturalWidth` and `naturalHeight` properties of an image after
146
178
  ```html
147
179
  <img src="..." &naturalWidth="width" &naturalHeight="height" />
148
180
  ```
181
+ 🚀 [Live demo | Alpine.js x-bound: Binding image sizes](https://jsfiddle.net/rameel/q4vb1d0w/)
149
182
 
150
183
  > [!TIP]
151
184
  > 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:
@@ -164,6 +197,18 @@ You can bind the `naturalWidth` and `naturalHeight` properties of an image after
164
197
  > The `naturalWidth` and `naturalHeight` properties are read-only and reflect the original image dimensions, available after the image has loaded.
165
198
 
166
199
 
200
+ ### Binding `<video>` sizes
201
+
202
+ You can bind the `videoWidth` and `videoHeight` properties of a video after it loads:
203
+
204
+ ```html
205
+ <video &videoWidth="width" &videoHeight="height">
206
+ <source src="..." type="video/mp4">
207
+ </video>
208
+ ```
209
+ 🚀 [Live demo | Alpine.js x-bound: Binding video sizes](https://jsfiddle.net/rameel/nah2pfcx/)
210
+
211
+
167
212
  ### Binding `contenteditable` elements
168
213
 
169
214
  For `contenteditable` elements, you can bind the following properties:
@@ -172,12 +217,15 @@ For `contenteditable` elements, you can bind the following properties:
172
217
  - [textContent](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent)
173
218
 
174
219
  ```html
175
- <div &inner-html="html" contenteditable="true"></div>
220
+ <div &innerHtml="html" contenteditable="true"></div>
176
221
  ```
222
+ 🚀 [Live demo | Alpine.js x-bound: Contenteditable bindings](https://jsfiddle.net/rameel/n5sj0rdz/)
223
+
177
224
 
178
225
  ### Binding block-level element sizes
179
226
 
180
- 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:
227
+ You can bind to the following properties to get the **width** and **height** of block-level elements,
228
+ measured with a `ResizeObserver`. The values will update whenever the element's size changes:
181
229
 
182
230
  - [clientHeight](https://developer.mozilla.org/en-US/docs/Web/API/Element/clientHeight)
183
231
  - [clientWidth](https://developer.mozilla.org/en-US/docs/Web/API/Element/clientWidth)
@@ -187,36 +235,58 @@ You can bind to the following properties to get the **width** and **height** of
187
235
  ```html
188
236
  <div &client-width="width" &client-height="height"></div>
189
237
  ```
238
+ 🚀 [Live demo | Alpine.js x-bound: Binding element dimensions](https://jsfiddle.net/rameel/jc4eu921/)
190
239
 
191
240
  > [!NOTE]
192
241
  > These properties are read-only.
193
242
 
243
+ > [!IMPORTANT]
244
+ > Elements with `display: inline` don't have an explicit width or height (unless they are intrinsically sized, like `<img>` or `<canvas>`). Therefore, a `ResizeObserver` cannot track their size. If you need to observe their size, change their `display` style to something like `inline-block`.
245
+ >
246
+ > Also keep in mind that CSS transforms do not trigger `ResizeObserver` updates.
247
+
248
+
194
249
  ### Binding group of `<input type="radio">` and `<input type="checkbox">`
195
250
  The group of `<input>` elements that should function together can utilize the `group` bound property.
196
251
 
197
252
  ```html
198
253
  <div x-data="{ pets: ['goldfish', 'parrot'], contact: 'Email' }">
199
254
 
200
- <!-- grouped checkboxes are similar to "select multiple"
201
- and use an array for selected options -->
202
- <input &group="pets" type="checkbox" value="cat" />
203
- <input &group="pets" type="checkbox" value="goldfish" />
204
- <input &group="pets" type="checkbox" value="parrot" />
205
- <input &group="pets" type="checkbox" value="spider" />
255
+ <!-- grouped checkboxes are similar to "select multiple"
256
+ and use an array for selected options -->
257
+ <input &group="pets" type="checkbox" value="cat" />
258
+ <input &group="pets" type="checkbox" value="goldfish" />
259
+ <input &group="pets" type="checkbox" value="parrot" />
260
+ <input &group="pets" type="checkbox" value="spider" />
206
261
 
207
- <!-- grouped radio inputs are mutually exclusive -->
208
- <input &group="contact" type="radio" value="Email" />
209
- <input &group="contact" type="radio" value="Phone" />
210
- <input &group="contact" type="radio" value="Mail" />
262
+ <!-- grouped radio inputs are mutually exclusive -->
263
+ <input &group="contact" type="radio" value="Email" />
264
+ <input &group="contact" type="radio" value="Phone" />
265
+ <input &group="contact" type="radio" value="Mail" />
211
266
 
212
267
  </div>
213
268
  ```
269
+ 🚀 [Live demo | Alpine.js x-bound: Binding element dimensions](https://jsfiddle.net/rameel/f5jpry7b/)
270
+
214
271
 
215
272
  ### Binding `input[type="checkbox"]:indeterminate` property
216
273
  The `x-bound` directive supports binding the `indeterminate` property of `<input type="checkbox">` elements,
217
274
  allowing you to control the checkbox's indeterminate state (a state where the checkbox is neither checked nor unchecked,
218
275
  typically represented visually with a dash or similar indicator).
219
276
 
277
+ ```html
278
+ <div x-data="{ checked: false, indeterminate: true }">
279
+ <input type="checkbox" &checked &indeterminate />
280
+
281
+ <template x-match>
282
+ <span x-case="indeterminate">Waiting...</span>
283
+ <span x-case="checked">Checked</span>
284
+ <span x-default>Unchecked</span>
285
+ </template>
286
+ </div>
287
+ ```
288
+ 🚀 [Live demo | Alpine.js x-bound: Binding indeterminate](https://jsfiddle.net/rameel/o8ubzac0/)
289
+
220
290
  This is useful for scenarios like selecting a subset of items in a list, such as in a table header checkbox:
221
291
  ```html
222
292
  <table>
@@ -233,6 +303,8 @@ This is useful for scenarios like selecting a subset of items in a list, such as
233
303
  ...
234
304
  </table>
235
305
  ```
306
+ 🚀 [Live demo | Alpine.js x-bound: Binding indeterminate (table)](https://jsfiddle.net/rameel/ryvhw3jt/)
307
+
236
308
  In this example, the `indeterminate` property of the checkbox is bound to the `isPartialSelected` data property.
237
309
  When `isPartialSelected` is `true`, the checkbox will be in the indeterminate state.
238
310
 
@@ -252,6 +324,7 @@ The directive also supports synchronizing values between two data properties.
252
324
  Number: <span x-text="number"></span>
253
325
  </div>
254
326
  ```
327
+ 🚀 [Live demo | Alpine.js x-bound: Binding data properties](https://jsfiddle.net/rameel/972qyomn/)
255
328
 
256
329
  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.
257
330
 
@@ -280,6 +353,41 @@ You can find the source code for this plugin on GitHub:
280
353
 
281
354
  https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/bound
282
355
 
356
+ ## Related projects
357
+
358
+ **[@ramstack/alpinegear-main](https://www.npmjs.com/package/@ramstack/alpinegear-main)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/main))<br>
359
+ Provides a combined plugin that includes several useful directives.
360
+ This package aggregates multiple individual plugins, offering a convenient all-in-one bundle.
361
+ Included directives: `x-bound`, `x-format`, `x-fragment`, `x-match`, `x-template`, and `x-when`.
362
+
363
+ **[@ramstack/alpinegear-format](https://www.npmjs.com/package/@ramstack/alpinegear-format)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/format))<br>
364
+ Provides the `x-format` directive, which allows you to easily interpolate text using a template syntax similar to what's available in `Vue.js`.
365
+
366
+ **[@ramstack/alpinegear-template](https://www.npmjs.com/package/@ramstack/alpinegear-template)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/template))<br>
367
+ Provides the `x-template` directive, which allows you to define a template once anywhere in the DOM and reference it by its ID.
368
+
369
+ **[@ramstack/alpinegear-fragment](https://www.npmjs.com/package/@ramstack/alpinegear-fragment)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/fragment))<br>
370
+ Provides the `x-fragment` directive, which allows for fragment-like behavior similar to what's available in frameworks
371
+ like `Vue.js` or `React`, where multiple root elements can be grouped together.
372
+
373
+ **[@ramstack/alpinegear-match](https://www.npmjs.com/package/@ramstack/alpinegear-match)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/match))<br>
374
+ Provides the `x-match` directive, which functions similarly to the `switch` statement in many programming languages,
375
+ allowing you to conditionally render elements based on matching cases.
376
+
377
+ **[@ramstack/alpinegear-when](https://www.npmjs.com/package/@ramstack/alpinegear-when)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/when))<br>
378
+ Provides the `x-when` directive, which allows for conditional rendering of elements similar to `x-if`, but supports multiple root elements.
379
+
380
+ **[@ramstack/alpinegear-destroy](https://www.npmjs.com/package/@ramstack/alpinegear-destroy)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/destroy))<br>
381
+ Provides the `x-destroy` directive, which is the opposite of `x-init` and allows you to hook into the cleanup phase
382
+ of any element, running a callback when the element is removed from the DOM.
383
+
384
+ **[@ramstack/alpinegear-hotkey](https://www.npmjs.com/package/@ramstack/alpinegear-hotkey)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/hotkey))<br>
385
+ Provides the `x-hotkey` directive, which allows you to easily handle keyboard shortcuts within your Alpine.js components or application.
386
+
387
+ **[@ramstack/alpinegear-router](https://www.npmjs.com/package/@ramstack/alpinegear-router)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/router))<br>
388
+ Provides the `x-router` and `x-route` directives, which enable client-side navigation and routing functionality within your Alpine.js application.
389
+
390
+
283
391
  ## Contributions
284
392
  Bug reports and contributions are welcome.
285
393
 
@@ -321,6 +321,7 @@ function plugin({ directive, entangle, evaluateLater, mapAttributes, mutateDom,
321
321
  if (el.type === "checkbox") {
322
322
  is_nullish(get_value()) && update_variable();
323
323
  effect(update_property);
324
+ cleanup(listen(el, "change", update_variable));
324
325
  processed = true;
325
326
  }
326
327
  }
@@ -1 +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("alpinegear.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,indeterminate,open,group".split(",").map(e=>[e.trim().toLowerCase(),e.trim()]));function b({directive:b,entangle:g,evaluateLater:m,mapAttributes:k,mutateDom:x,prefixed:y}){k(e=>({name:e.name.replace(/^&/,y("bound:")),value:e.value})),b("bound",(b,{expression:k,value:y,modifiers:T},{effect:w,cleanup:E})=>{if(!y)return void a("x-bound directive expects the presence of a bound property name");const H=b.tagName.toUpperCase();k=k?.trim();const L=h.get(y.trim().replace("-","").toLowerCase());k||=L;const _=e(m,b,k),S=t(m,b,k),W=()=>u(b[L],_())||x(()=>b[L]=_()),A=()=>S((e=>"number"===e.type||"range"===e.type)(b)?function(e){return""===e?null:+e}(b[L]):b[L]);let C;switch(L){case"value":!function(){switch(H){case"INPUT":case"TEXTAREA":o(_())&&A(),w(W),E(f(b,"input",A)),C=!0;break;case"SELECT":setTimeout(()=>{o(_())&&A(),w(()=>function(e,t){for(const n of e.options)n.selected=l(t,n.value)>=0}(b,s(_()??[]))),E(f(b,"change",()=>S(function(e){return e.multiple?[...e.selectedOptions].map(e=>e.value):e.value}(b))))},0),C=!0}}();break;case"checked":c(b)&&(w(W),E(f(b,"change",A)),C=!0);break;case"files":"file"===b.type&&(_()instanceof FileList||A(),w(W),E(f(b,"input",A)),C=!0);break;case"innerHTML":case"innerText":case"textContent":"true"===b.contentEditable&&(o(_())&&A(),w(W),E(f(b,"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":E(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)}}(b,A)),C=!0;break;case"indeterminate":"checkbox"===b.type&&(o(_())&&A(),w(W),C=!0);break;case"open":"DETAILS"===H&&(o(_())&&A(),w(W),E(f(b,"toggle",A)),C=!0);break;case"group":c(b)&&(b.name||x(()=>b.name=k),w(()=>x(()=>function(e,t){e.checked=r(t)?l(t,e.value)>=0:u(e.value,t)}(b,_()??[]))),E(f(b,"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}(b,_())))),C=!0)}if(!C){const n=d(T,"in")?"in":d(T,"out")?"out":"inout",i=k===y?((e,t)=>{for(;e&&!t(e);)e=(e._x_teleportBack??e).parentElement;return e})(b.parentNode,e=>e._x_dataStack):b;if(!b._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,b,y),set:t(m,b,y)};switch(n){case"in":E(v(()=>r.get(),e=>o.set(p(e))));break;case"out":E(v(()=>o.get(),e=>r.set(p(e))));break;default:E(g(r,o))}}function N(e,t){H===e&&(A(),E(f(b,t,A)),C=!0)}})}export{b as bound};
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("alpinegear.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 h(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 v=new Map("value,checked,files,innerHTML,innerText,textContent,videoHeight,videoWidth,naturalHeight,naturalWidth,clientHeight,clientWidth,offsetHeight,offsetWidth,indeterminate,open,group".split(",").map(e=>[e.trim().toLowerCase(),e.trim()]));function b({directive:b,entangle:g,evaluateLater:m,mapAttributes:k,mutateDom:x,prefixed:y}){k(e=>({name:e.name.replace(/^&/,y("bound:")),value:e.value})),b("bound",(b,{expression:k,value:y,modifiers:T},{effect:w,cleanup:E})=>{if(!y)return void a("x-bound directive expects the presence of a bound property name");const H=b.tagName.toUpperCase();k=k?.trim();const L=v.get(y.trim().replace("-","").toLowerCase());k||=L;const _=e(m,b,k),S=t(m,b,k),W=()=>u(b[L],_())||x(()=>b[L]=_()),A=()=>S((e=>"number"===e.type||"range"===e.type)(b)?function(e){return""===e?null:+e}(b[L]):b[L]);let C;switch(L){case"value":!function(){switch(H){case"INPUT":case"TEXTAREA":o(_())&&A(),w(W),E(f(b,"input",A)),C=!0;break;case"SELECT":setTimeout(()=>{o(_())&&A(),w(()=>function(e,t){for(const n of e.options)n.selected=l(t,n.value)>=0}(b,s(_()??[]))),E(f(b,"change",()=>S(function(e){return e.multiple?[...e.selectedOptions].map(e=>e.value):e.value}(b))))},0),C=!0}}();break;case"checked":c(b)&&(w(W),E(f(b,"change",A)),C=!0);break;case"files":"file"===b.type&&(_()instanceof FileList||A(),w(W),E(f(b,"input",A)),C=!0);break;case"innerHTML":case"innerText":case"textContent":"true"===b.contentEditable&&(o(_())&&A(),w(W),E(f(b,"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":E(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)}}(b,A)),C=!0;break;case"indeterminate":"checkbox"===b.type&&(o(_())&&A(),w(W),E(f(b,"change",A)),C=!0);break;case"open":"DETAILS"===H&&(o(_())&&A(),w(W),E(f(b,"toggle",A)),C=!0);break;case"group":c(b)&&(b.name||x(()=>b.name=k),w(()=>x(()=>function(e,t){e.checked=r(t)?l(t,e.value)>=0:u(e.value,t)}(b,_()??[]))),E(f(b,"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}(b,_())))),C=!0)}if(!C){const n=d(T,"in")?"in":d(T,"out")?"out":"inout",i=k===y?((e,t)=>{for(;e&&!t(e);)e=(e._x_teleportBack??e).parentElement;return e})(b.parentNode,e=>e._x_dataStack):b;if(!b._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,b,y),set:t(m,b,y)};switch(n){case"in":E(h(()=>r.get(),e=>o.set(p(e))));break;case"out":E(h(()=>o.get(),e=>r.set(p(e))));break;default:E(g(r,o))}}function N(e,t){H===e&&(A(),E(f(b,t,A)),C=!0)}})}export{b as bound};
@@ -324,6 +324,7 @@
324
324
  if (el.type === "checkbox") {
325
325
  is_nullish(get_value()) && update_variable();
326
326
  effect(update_property);
327
+ cleanup(listen(el, "change", update_variable));
327
328
  processed = true;
328
329
  }
329
330
  }
@@ -1 +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("alpinegear.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,indeterminate,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:L,cleanup:T})=>{if(!y)return void a("x-bound directive expects the presence of a bound property name");const w=g.tagName.toUpperCase();k=k?.trim();const H=h.get(y.trim().replace("-","").toLowerCase());k||=H;const _=e(m,g,k),S=t(m,g,k),A=()=>u(g[H],_())||x(()=>g[H]=_()),W=()=>S((e=>"number"===e.type||"range"===e.type)(g)?function(e){return""===e?null:+e}(g[H]):g[H]);let C;switch(H){case"value":!function(){switch(w){case"INPUT":case"TEXTAREA":o(_())&&W(),L(A),T(p(g,"input",W)),C=!0;break;case"SELECT":setTimeout(()=>{o(_())&&W(),L(()=>function(e,t){for(const n of e.options)n.selected=l(t,n.value)>=0}(g,s(_()??[]))),T(p(g,"change",()=>S(function(e){return e.multiple?[...e.selectedOptions].map(e=>e.value):e.value}(g))))},0),C=!0}}();break;case"checked":c(g)&&(L(A),T(p(g,"change",W)),C=!0);break;case"files":"file"===g.type&&(_()instanceof FileList||W(),L(A),T(p(g,"input",W)),C=!0);break;case"innerHTML":case"innerText":case"textContent":"true"===g.contentEditable&&(o(_())&&W(),L(A),T(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":T(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"indeterminate":"checkbox"===g.type&&(o(_())&&W(),L(A),C=!0);break;case"open":"DETAILS"===w&&(o(_())&&W(),L(A),T(p(g,"toggle",W)),C=!0);break;case"group":c(g)&&(g.name||x(()=>g.name=k),L(()=>x(()=>function(e,t){e.checked=r(t)?l(t,e.value)>=0:u(e.value,t)}(g,_()??[]))),T(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,t)=>{for(;e&&!t(e);)e=(e._x_teleportBack??e).parentElement;return e})(g.parentNode,e=>e._x_dataStack):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":T(v(()=>r.get(),e=>o.set(f(e))));break;case"out":T(v(()=>o.get(),e=>r.set(f(e))));break;default:T(b(r,o))}}function N(e,t){w===e&&(W(),T(p(g,t,W)),C=!0)}})}document.addEventListener("alpine:init",()=>{Alpine.plugin(g)})}();
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("alpinegear.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,indeterminate,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:L,cleanup:T})=>{if(!y)return void a("x-bound directive expects the presence of a bound property name");const w=g.tagName.toUpperCase();k=k?.trim();const H=h.get(y.trim().replace("-","").toLowerCase());k||=H;const _=e(m,g,k),S=t(m,g,k),A=()=>u(g[H],_())||x(()=>g[H]=_()),W=()=>S((e=>"number"===e.type||"range"===e.type)(g)?function(e){return""===e?null:+e}(g[H]):g[H]);let C;switch(H){case"value":!function(){switch(w){case"INPUT":case"TEXTAREA":o(_())&&W(),L(A),T(p(g,"input",W)),C=!0;break;case"SELECT":setTimeout(()=>{o(_())&&W(),L(()=>function(e,t){for(const n of e.options)n.selected=l(t,n.value)>=0}(g,s(_()??[]))),T(p(g,"change",()=>S(function(e){return e.multiple?[...e.selectedOptions].map(e=>e.value):e.value}(g))))},0),C=!0}}();break;case"checked":c(g)&&(L(A),T(p(g,"change",W)),C=!0);break;case"files":"file"===g.type&&(_()instanceof FileList||W(),L(A),T(p(g,"input",W)),C=!0);break;case"innerHTML":case"innerText":case"textContent":"true"===g.contentEditable&&(o(_())&&W(),L(A),T(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":T(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"indeterminate":"checkbox"===g.type&&(o(_())&&W(),L(A),T(p(g,"change",W)),C=!0);break;case"open":"DETAILS"===w&&(o(_())&&W(),L(A),T(p(g,"toggle",W)),C=!0);break;case"group":c(g)&&(g.name||x(()=>g.name=k),L(()=>x(()=>function(e,t){e.checked=r(t)?l(t,e.value)>=0:u(e.value,t)}(g,_()??[]))),T(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,t)=>{for(;e&&!t(e);)e=(e._x_teleportBack??e).parentElement;return e})(g.parentNode,e=>e._x_dataStack):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":T(v(()=>r.get(),e=>o.set(f(e))));break;case"out":T(v(()=>o.get(),e=>r.set(f(e))));break;default:T(b(r,o))}}function N(e,t){w===e&&(W(),T(p(g,t,W)),C=!0)}})}document.addEventListener("alpine:init",()=>{Alpine.plugin(g)})}();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramstack/alpinegear-bound",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
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
5
  "author": "Rameel Burhan",
6
6
  "license": "MIT",
@@ -11,7 +11,10 @@
11
11
  },
12
12
  "keywords": [
13
13
  "alpine.js",
14
- "alpinejs"
14
+ "alpinejs",
15
+ "alpinejs-binding",
16
+ "alpinejs-directive",
17
+ "alpinejs-plugin"
15
18
  ],
16
19
  "main": "alpinegear-bound.js",
17
20
  "module": "alpinegear-bound.esm.js"