Forráskód Böngészése

Merge branch 'master' into fix/add-check-for-template-sibling

Kevin Batdorf 4 éve
szülő
commit
1e0d3c7e03
24 módosított fájl, 1990 hozzáadás és 199 törlés
  1. 789 0
      README.de.md
  2. 8 6
      README.es.md
  3. 796 0
      README.id.md
  4. 4 4
      README.ja.md
  5. 39 19
      README.md
  6. 9 9
      README.pt.md
  7. 10 10
      README.ru.md
  8. 6 6
      README.zh-TW.md
  9. 72 72
      dist/alpine-ie11.js
  10. 40 31
      dist/alpine.js
  11. 15 1
      examples/index.html
  12. 1 1
      package-lock.json
  13. 1 1
      package.json
  14. 14 4
      src/component.js
  15. 13 13
      src/directives/bind.js
  16. 5 3
      src/directives/for.js
  17. 2 2
      src/directives/model.js
  18. 2 4
      src/index.js
  19. 17 13
      src/utils.js
  20. 18 0
      test/bind.spec.js
  21. 41 0
      test/for.spec.js
  22. 23 0
      test/model.spec.js
  23. 41 0
      test/spread.spec.js
  24. 24 0
      test/watch.spec.js

+ 789 - 0
README.de.md

@@ -0,0 +1,789 @@
+# Alpine.js
+
+![npm bundle size](https://img.shields.io/bundlephobia/minzip/alpinejs)
+![npm version](https://img.shields.io/npm/v/alpinejs)
+[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true)](https://alpinejs.codewithhugo.com/chat/)
+
+Alpine.js bietet dir die gewohnte Reaktivität und deklarative Natur von Vue und React, verzichtet jedoch auf den gegebenen Ballast, der bei solchen Frameworks anfallen kann.
+
+Du erweiterst dein DOM nur dort um zusätzliche Funktionalität, wo du es für richtig hältst.
+
+Unsere Philosophie erinnert dich vielleicht an [Tailwind](https://tailwindcss.com/), nur eben für Javascript.
+
+> Hinweis: Alpines Syntax baut fast gänzlich auf der Syntax von [Vue](https://vuejs.org/) auf (zum Teil auch von [Angular](https://angularjs.org/)). Ich bin für immer dankbar für den Mehrwert, den diese Frameworks der Entwicklung des Webs gebracht haben.
+
+## Übersetzungen der Dokumentation
+
+| Language | Link for documentation |
+| --- | --- |
+| Chinese Traditional | [**繁體中文說明文件**](./README.zh-TW.md) |
+| German | [**Dokumentation in Deutsch**](./README.de.md) |
+| Indonesian | [**Dokumentasi Bahasa Indonesia**](./README.id.md) |
+| Japanese | [**日本語ドキュメント**](./README.ja.md) |
+| Portuguese | [**Documentação em Português**](./README.pt.md) |
+| Russian | [**Документация на русском**](./README.ru.md) |
+| Spanish | [**Documentación en Español**](./README.es.md) |
+
+## Installation
+
+**Von einem CDN:** Erweitere deinen HTML-Kopf (`<head>`) um folgendes Skript.
+```html
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
+```
+
+Das wars. Die Initialisierung passiert automatisch.
+
+Für die Produktionsumgebung wird empfohlen, den Link mit einer spezifischen Versionsnummer zu versehen. Somit kann präventiv sichergestellt werden, dass keine unerwarteten Fehler durch Versionsaktualisierungen zustande kommen.
+Als Beispiel wird hier die (letzte) Version `2.7.2` spezifiziert:
+```html
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.2/dist/alpine.min.js" defer></script>
+```
+
+**Über npm:** Installiere das Paket über npm.
+```js
+npm i alpinejs
+```
+
+Inkludiere es in deinem Skript.
+```js
+import 'alpinejs'
+```
+
+**Für IE11 Support** Nutze stattdessen die folgenden Skripts.
+```html
+<script type="module" src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js"></script>
+<script nomodule src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine-ie11.min.js" defer></script>
+```
+
+Da obige Schema wird als [module/nomodule pattern](https://philipwalton.com/articles/deploying-es2015-code-in-production-today/) bezeichnet. Dadurch wird bezweckt, dass in modernen Browsern automatisch das "modern"-Bundle geladen wird, und das IE11-spezifische Bundle automatisch in IE11 und anderen "legacy"-Browsern geladen wird.
+
+## Use
+
+*Dropdown/Modal*
+```html
+<div x-data="{ open: false }">
+    <button @click="open = true">Öffne Dropdown</button>
+
+    <ul
+        x-show="open"
+        @click.away="open = false"
+    >
+        Dropdown Inhalt
+    </ul>
+</div>
+```
+
+*Tabs*
+```html
+<div x-data="{ tab: 'foo' }">
+    <button :class="{ 'active': tab === 'foo' }" @click="tab = 'foo'">Foo</button>
+    <button :class="{ 'active': tab === 'bar' }" @click="tab = 'bar'">Bar</button>
+
+    <div x-show="tab === 'foo'">Tab Foo</div>
+    <div x-show="tab === 'bar'">Tab Bar</div>
+</div>
+```
+
+Du kannst es sogar für nicht-triviale Dinge verwenden:
+*Pre-fetching a dropdown's HTML content on hover*
+```html
+<div x-data="{ open: false }">
+    <button
+        @mouseenter.once="
+            fetch('/dropdown-partial.html')
+                .then(response => response.text())
+                .then(html => { $refs.dropdown.innerHTML = html })
+        "
+        @click="open = true"
+    >Zeige Dropdown</button>
+
+    <div x-ref="dropdown" x-show="open" @click.away="open = false">
+        Lade Spinner...
+    </div>
+</div>
+```
+
+## Lerne
+
+Es stehen 14 Direktiven zur Verfügung:
+
+| Direktive | Beschreibung |
+| --- | --- |
+| [`x-data`](#x-data) | Deklariert einen neuen Komponenten-Geltungsbereich. |
+| [`x-init`](#x-init) | Wertet einen Ausdruck aus, sobald die Komponente initialisiert wurde. |
+| [`x-show`](#x-show) | Schaltet anhand des Ausdrucks (true oder false) das Element auf `display: none;`. |
+| [`x-bind`](#x-bind) | Setzt den Wert eines Attributs auf das Ergebnis eines JS-Ausdrucks. |
+| [`x-on`](#x-on) | Verbindet einen EventHandler mit einem HTML-Elemment. Der spezifizierte JS-Code wird nur dann aufgerufen, wenn das jeweilige Ereignis empfangen wird. |
+| [`x-model`](#x-model) | Das Direktive sorgt für das Databinding mit Input-Elementen. Hierbei wird ein Databinding in beide Richtungen ermöglicht ("Two way databinding"). |
+| [`x-text`](#x-text) | Funktioniert ähnlich wie `x-bind`, wobei hier das `innerText` eines Elements aktualisiert wird. |
+| [`x-html`](#x-html) | Funktioniert ähnlich wie `x-bind`, wobei hier das `innerHTML` eines Elements aktualisiert wird. |
+| [`x-ref`](#x-ref) | Ermöglicht es, die Elemente einer Komponente im DOM zu referenzieren. |
+| [`x-if`](#x-if) | Entfernt ein Element aus dem DOM. Kann nur in Kombination mit `<template>`-Tags benutzt werden. |
+| [`x-for`](#x-for) | Erstellt einen neuen DOM-Knoten (node) für jedes Element in einem Array. Kann nur in Kombination mit `<template>`-Tags benutzt werden. |
+| [`x-transition`](#x-transition) | Ein Direktive zur Anwedung von Klassen auf unterschiedliche Phasen der Transition eines Elements. |
+| [`x-spread`](#x-spread) | Ermöglicht die Bindung von einem Objekt aus Alpine Direktiven an ein Element. Dies erlaubt eine bessere Wiederverwendbarkeit von Direktiven. |
+| [`x-cloak`](#x-cloak) | Dieses Attribut wird entfernt, sobald Alpine initalisiert wird. Das Direktive wird genutzt, um das pre-initalisierte DOM auszublenden. |
+
+
+Und 6 magische Eigenschaften (englisch *magic properties*):
+
+| Magische Eigenschaft | Beschreibung |
+| --- | --- |
+| [`$el`](#el) | Liefert den DOM-Knoten der Stammkomponente. |
+| [`$refs`](#refs) | Liefert jene Elemente des DOM innerhalb der Komponente, welche mit `x-ref` markiert sind. |
+| [`$event`](#event) | Liefert das native "Event"-Objekt innerhalb eines EventHandlers.  |
+| [`$dispatch`](#dispatch) | Erstellt ein `CustomEvent`, welches intern via `.dispatchEvent()` versendet werden kann. |
+| [`$nextTick`](#nexttick) | Führt den gegebenen Ausdruck aus, NACHDEM Alpine die reaktiven DOM-Aktualisierungen durchgeführt hat. |
+| [`$watch`](#watch) | Wenn sich der Wert einer beobachteten (englisch *watched*) Eigenschaft einer Komponente ändert, wird die angegebene Callback-Funktion aufgerufen. |
+
+
+## Sponsoren
+
+<img width="33%" src="https://refactoringui.nyc3.cdn.digitaloceanspaces.com/tailwind-logo.svg" alt="Tailwind CSS">
+
+**Du möchtest Sponsor werden? [Schreib mir auf Twitter](https://twitter.com/calebporzio)**
+
+## Gemeinschaftsprojekte
+
+* [AlpineJS Weekly Newsletter](https://alpinejs.codewithhugo.com/newsletter/)
+* [Spruce (State Management)](https://github.com/ryangjchandler/spruce)
+* [Turbolinks Adapter](https://github.com/SimoTod/alpine-turbolinks-adapter)
+* [Alpine Magic Helpers](https://github.com/KevinBatdorf/alpine-magic-helpers)
+* [Awesome Alpine](https://github.com/ryangjchandler/awesome-alpine)
+
+### Direktiven
+
+---
+
+### `x-data`
+
+**Beispiel:** `<div x-data="{ foo: 'bar' }">...</div>`
+
+**Struktur:** `<div x-data="[JSON data object]">...</div>`
+
+`x-data` deklariert einen neuene Komponenten-Geltungsbereich. Jede neu erstellte Komponente wird nun mit der angegebenen Datenquelle initialisiert.
+
+Dies verhält sich ähnlich wie die `data`-Eigenschaft einer Vue-Komponente.
+
+**Extrahieren von Komponenten-Logik**
+
+Datenquellen und zugehörige Funktionalität können in wiederverwendbare Funktionen extrahiert werden.
+
+```html
+<div x-data="dropdown()">
+    <button x-on:click="open">Öffne</button>
+
+    <div x-show="isOpen()" x-on:click.away="close">
+        // Dropdown
+    </div>
+</div>
+
+<script>
+    function dropdown() {
+        return {
+            show: false,
+            open() { this.show = true },
+            close() { this.show = false },
+            isOpen() { return this.show === true },
+        }
+    }
+</script>
+```
+
+> **Für Nutzer von Modul-Packern**: Alpine.js ruft Funktionen auf, welche sich im globalen Geltungsbereich (`window`) befinden. Um Funktionen mit `x-data` zu benutzen, müssen sie daher explizit dem Geltungsbereich `window` zugewießen werden. Zum Beispiel `window.dropdown = function () {}` (Dieses Verhalten ist auf Webpack, Rollup, Parcel etc. zurückzuführen. Hier leben selbstdefinierte Funktionen defaultmäßig im Geltungsbereich des Moduls, und nicht `window`).
+
+Durch Objektdestrukturierung können mehrere Datenobjekte an `x-data` übergeben werden:
+
+```html
+<div x-data="{...dropdown(), ...tabs()}">
+```
+
+---
+
+### `x-init`
+**Beispiel:** `<div x-data="{ foo: 'bar' }" x-init="foo = 'baz'"></div>`
+
+**Struktur:** `<div x-data="..." x-init="[expression]"></div>`
+
+`x-init` Wertet einen Ausdruck aus, sobald die Komponente initialisiert wurde.
+
+Wenn der Code erst aufgerufen werden soll, NACHDEM Alpine die initialen Aktualisierungen des DOM vorgenommen hat (ähnlich zum `mounted()` Lebenszyklus in VueJS), kann eine Callback-Funktion von `x-init` retourniert werden:
+
+`x-init="() => { // Im Funktionsblock haben wir Zugriff auf den Zustand nach der DOM-Initialisierung // }"`
+
+---
+
+### `x-show`
+**Beispiel:** `<div x-show="open"></div>`
+
+**Struktur:** `<div x-show="[expression]"></div>`
+
+`x-show` schaltet die Eigenschaft `display: none;` des Elements, je nachdem ob der Ausdruck `true` oder `false` zurückliefert.
+
+**x-show.transition**
+
+`x-show.transition` ist eine convenience API, um Übergänge mit `x-show` durch CSS Transitionen ansprechender zu gestalten.
+
+```html
+<div x-show.transition="open">
+    Die Inhalte werden mithilfe von Transitionen ein- und ausgeblendet.
+</div>
+```
+
+| Direktive | Beschreibung |
+| --- | --- |
+| `x-show.transition` | Ein simultaner Fading- und Skalierungseffekt. (opacity, scale: 0.95, timing-function: cubic-bezier(0.4, 0.0, 0.2, 1), duration-in: 150ms, duration-out: 75ms)
+| `x-show.transition.in` | Die Transition gilt nur für den anfänglichen Übergang. |
+| `x-show.transition.out` | Die Transition gilt nur für den abschließenden Übergang. |
+| `x-show.transition.opacity` | Nur das Fading wird genutzt. |
+| `x-show.transition.scale` | Nur die Skalierung wird genutzt. |
+| `x-show.transition.scale.75` | Zur Anpassung des Skalierungswerts `transform: scale(.75)`. |
+| `x-show.transition.duration.200ms` | Setzt den Wert der anfänglichen Transition auf 200ms. Der Wert der abschließenden Transition nimmt die Hälfte des angegebenen Werts an (in diesem Fall 100ms). |
+| `x-show.transition.origin.top.right` | Zur Anpassung des Ursprungs der CSS-Transformation `transform-origin: top right`. |
+| `x-show.transition.in.duration.200ms.out.duration.50ms` | Zur Spezifizierung der einzelnen Dauern einer Transition. |
+
+> Hinweis: Alle oben genannten Modifikatoren können miteinander kombiniert werden. Auch solche Spielerein sind möglich (lol): `x-show.transition.in.duration.100ms.origin.top.right.opacity.scale.85.out.duration.200ms.origin.bottom.left.opacity.scale.95`
+
+> Hinweis: Defaultmäßig wartet `x-show`, bis jede untergeordnete Komponente seine Transition beendet hat. Um dieses Verhalten zu umgehen, kann der Modifikator `.immediate` eingesetzt werden:
+```html
+<div x-show.immediate="open">
+    <div x-show.transition="open">
+</div>
+```
+---
+
+### `x-bind`
+
+> Hinweis: Es kann auch folgende Kurzschreibweise genutzt werden: `:type="..."`
+
+**Beispiel:** `<input x-bind:type="inputType">`
+
+**Struktur:** `<input x-bind:[attribute]="[expression]">`
+
+`x-bind` setzt den Wert eines Attributs auf das Ergebnis eines JavaScript Ausdrucks. Dabei hat der Ausdruck Zugang zu allen Datenquellen der Komponente und wird jedes Mal aktualisiert, wenn sich eine der Datenquellen ändert.
+
+> Hinweis: Attributbindungen aktualisieren sich nur dann, WENN sich deren Abhängigkeiten aktualisieren. Das Framework erkennt solche Abhängigkeiten automatisch und führt dementsprechend Aktualisierungen durch.
+
+**`x-bind` für Klassenattribute**
+
+`x-bind` hat eine spezielle Verhaltensweise, wenn es mit dem Attribut `class` verknüpft wird.
+
+Hierbei wird ein Objekt übergeben, dessen Namen die potenziellen Klassenselektoren sind. Die Werte dieser Paare sind boolesche Ausdrücke, durch welche determiniert wird, ob die Klasse auf das Element angewendet wird oder nicht.
+
+Zum Beispiel:
+`<div x-bind:class="{ 'hidden': foo }"></div>`
+
+In diesem Beispiel wird die Klasse "hidden" nur dann angewendet, wenn `foo` den Wert `true` liefert.
+
+**`x-bind` für boolesche Attribute**
+
+`x-bind` unterstützt sowohl Variablen als auch JavaScript Ausdrücke in seiner Bedingung, wenn diese einen booleschen Wert (`true` oder `false`) zurückliefern.
+
+Zum Beispiel:
+```html
+<!-- Gegeben: -->
+<button x-bind:disabled="myVar">Klick mich</button>
+
+<!-- Wenn myVar == true: -->
+<button disabled="disabled">Klick mich</button>
+
+<!-- Wenn myVar == false: -->
+<button>Klick mich</button>
+```
+
+Hier wird das Attribut `disabled` je nach der Auswertung von `myVar` hinzugefügt oder entfernt.
+
+
+Alle in der [HTML Spezifikation](https://html.spec.whatwg.org/multipage/indices.html#attributes-3:boolean-attribute) angeführten booleschen Attribute werden unterstützt. Dazu zählen `disabled`, `readonly`, `required`, `checked`, `hidden`, `selected`, `open`, etc.
+
+**`.camel` Modifikator**
+**Beispiel:** `<svg x-bind:view-box.camel="viewBox">`
+
+Mithilfe des Modifikators `camel` kann die Bedingung an die camel-case Schreibweise des angegebenen Attributnamens gebunden werden. In obigen Beispiel wird der Wert von `viewBox` mit dem Attribut `viewBox` (anstelle von `view-box`) verknüpft.
+
+---
+
+### `x-on`
+
+> Hinweis: Es kann auch folgende Kurzschreibweise genutzt werden: `@click="..."`
+
+**Beispiel:** `<button x-on:click="foo = 'bar'"></button>`
+
+**Struktur:** `<button x-on:[event]="[expression]"></button>`
+
+`x-on` bindet einen EventHandler an das HTML-Element. Der spezifizierte JavaScript-Ausdruck wird genau dann ausgewertet, wenn das Ereignis ausgelöst wurde.
+
+Andere Attribute des Elements welche an diese Datenquelle gebunden sind, werden aktualisiert, sobald Daten im Ausdruck modifiziert werden.
+
+> Hinweis: Wahlweise kann auch der Name einer JavaScript-Funktion angegeben werden
+
+**Beispiel:** `<button x-on:click="myFunction"></button>`
+
+Ist äquivalent zu: `<button x-on:click="myFunction($event)"></button>`
+
+**`keydown` Modifikator**
+
+**Beispiel:** `<input type="text" x-on:keydown.escape="open = false">`
+
+Um auf bestimmte Tastaturereignisse zu reagieren, können keydown-Modifikatoren an das Direktive `x-on:keydown` angehängt werden. Die Modifikatoren sind hierbei Werte von `Event.key`, jedoch in kebab-case-Schreibweise.
+
+Beispiele: `enter`, `escape`, `arrow-up`, `arrow-down`
+
+> Hinweis: Zusätzlich kann auch auf System-Modifikator-Tastenkombinationen reagiert werden: `x-on:keydown.cmd.enter="foo"`
+
+**`.away` Modifikator**
+
+**Beispiel:** `<div x-on:click.away="showModal = false"></div>`
+
+Durch die Nutzung des Modifikators `.away`, wird der Ausdruck eines EventHandlers nur dann ausgewertet, wenn das Ereignis nicht vom Element selbst (oder dessen untergeordnete Komponenten) ausgelöst wird.
+
+Dies kann zum Beispiel genutzt werden, um ein Dropdown-Menü oder ein Modal auszublenden, sobald der Nutzer außerhalb des Elements einen Mausklick durchführt.
+
+**`.prevent` Modifikator**
+**Beispiel:** `<input type="checkbox" x-on:click.prevent>`
+
+Durch das Anhängen von `.prevent` an einen EventHandler, wir die Methode `preventDefault` auf dem ausgelösten Ereignis ausgeführt. Im obigen Beispiel wird somit die native Funktionalität des `<input>`-Elements unterdrückt, wenn der Nutzer das Element anklickt.
+
+**`.stop` Modifikator**
+**Beispiel:** `<div x-on:click="foo = 'bar'"><button x-on:click.stop></button></div>`
+
+Durch das Anhängen von `.stop` an einen EventHandler, wird die Methode `stopPropagation` auf dem ausgelösten Ereignis ausgeführt. Klickt ein Nutzer im obigen Beispiel auf das `<button>`-Element, wird das Ereignis "click" nicht an das übergeordnete Element `<div>` gesendet. Anders gesagt wird im Falle eine Klicks `foo` nicht auf `'bar'` gesetzt.
+
+**`.self` Modifikator**
+**Beispiel:** `<div x-on:click.self="foo = 'bar'"><button></button></div>`
+
+Durch das Anhängen von `.self` an einen EventHandler, wird das Ergeinis nur dann behandelt, wenn das `$event.target` das Element selbst ist. Klickt ein Nutzer im obigen Beispiel auf das `<button>`-Element, wird das Ereignis "click" somit **NICHT** im übergeordnete Element `<div>` behandelt.
+
+**`.window` Modifikator**
+**Beispiel:** `<div x-on:resize.window="isOpen = window.outerWidth > 768 ? false : open"></div>`
+
+Durch das Anhängen von `.window` an einen EventHandler, wird der Listener auf das globale Window-Objekt anstelle des zugrundeliegenden DOM-Knotens registriert. Dieses Vorgehen ist beispielsweise hilfreich, wenn du den Zustand einer Komponente modifizieren willst, sobald sich die Eigenschaften des Window-Objekts ändern (zB das Event "resize"). Im obigen Beispiel wird das Modal/Dropdown genau dann geschlossen, wenn die Breite des Window-Objekts 768 Pixel überschreitet. Andernfalls bleibt der momentante Zustand bestehen.
+
+> Hinweis: Nach dem selben Prinzup kann der Modifikator `.document` genutzt werden, um auf Änderungen im Document-Objekt zu reagieren.
+
+**`.once` Modifikator**
+**Beispiel:** `<button x-on:mouseenter.once="fetchSomething()"></button>`
+
+Durch das Anhängen von `.once` an einen EventHandler wird sichergestellt, dass das Ereignis nur ein einziges Mal behandelt wird.
+
+**`.passive` Modifikator**
+**Beispiel:** `<button x-on:mousedown.passive="interactive = true"></button>`
+
+Durch das Anhängen von `.passive` an einen EventHandler, wird der gegebene Listener passiv. Dadurch wird verhindert, dass das spezifizierte Ereignis abgebrochen werden kann (`preventDefault()` wird ignoriert). Dieses Vorgehen ist zum Beispiel für die Bildlaufleistung auf Touch-Geräten relevant.
+
+**`.debounce` Modifikator**
+**Beispiel:** `<input x-on:input.debounce="fetchSomething()">`
+
+Mithilfe des Modifikators `debounce` kann ein EventHandler "debounced" werden. Hiermit wird sichergestellt, dass das spezifierte Ereignis nur dann behandelt wird, wenn eine gewisse Zeitspanne zum letzten Vorkommnis des Ereignisses vergangen ist. Erst wenn der Handler bereit ist, wird die Ereignisbehandlung ausgeführt.
+
+Die defaultmäßige Wartezeit beträgt 250 Millisekunden.
+
+Die Wartezeit kann folgendermaßen individualisiert werden:
+
+```
+<input x-on:input.debounce.750="fetchSomething()">
+<input x-on:input.debounce.750ms="fetchSomething()">
+```
+
+**`.camel` Modifikator**
+**Beispiel:** `<input x-on:event-name.camel="doSomething()">`
+
+Mithilfe des Modifikators `camel` kann ein EventHandler an die camel-case Schreibweise des angegebenen Ereignisnamens gebunden werden. Im obigen Beispiel wird der Ausdruck ausgewertet, sobald ein Ereignis namens `eventName` auf dem Element empfangen wird.
+
+---
+
+### `x-model`
+**Beispiel:** `<input type="text" x-model="foo">`
+
+**Struktur:** `<input type="text" x-model="[data item]">`
+
+`x-model` erweitert ein Element um ein "two-way data binding" (d.h. Databinding ist in beide Richtungen möglich). Der Wert des `<input>`-Elements wird mit dem Wert der Komponenten-Datenquelle `item` synchronisiert.
+
+> Hinweis: `x-model` erkennt automatisch Änderungen auf den folgenen Elementen: text inputs, checkboxes, radio buttons, textareas, selects, multiple selects. In den genannten Szenarien sollte die Funktionsweise von `x-model` das [Verhalten von Vue](https://vuejs.org/v2/guide/forms.html) widerspiegeln.
+
+**`.number` Modifikator**
+**Beispiel:** `<input x-model.number="age">`
+
+Durch die Nutzung des Modifikators `number` wird der Wert des `<input>`-Elements in eine Zahl umgewandelt. Wenn der Wert hierbei nicht als valide Zahl ausgelesen werden kann, wird der ursprüngliche Wert retourniert.
+
+**`.debounce` Modifikator**
+**Beispiel:** `<input x-model.debounce="search">`
+
+Mithilfe des Modifikators `debounce` kann der Aktualisierung eines Wertes ein "debounce" hinzugefügt werden. Hiermit wird sichergestellt, dass das spezifierte Ereignis nur dann behandelt wird, wenn eine gewisse Zeitspanne zum letzten Vorkommnis des Ereignisses vergangen ist. Erst wenn der Handler bereit ist, wird die Ereignisbehandlung ausgeführt.
+
+Die defaultmäßige Wartezeit beträgt 250 Millisekunden.
+
+Die Wartezeit kann folgendermaßen individualisiert werden:
+
+```
+<input x-model.debounce.750="search">
+<input x-model.debounce.750ms="search">
+```
+
+---
+
+### `x-text`
+**Beispiel:** `<span x-text="foo"></span>`
+
+**Struktur:** `<span x-text="[expression]"`
+
+`x-text` funktioniert ähnlich wie `x-bind`, außer dass anstelle des Wertes eine Attributs, das `innerText` eines Elements aktualisiert wird.
+
+---
+
+### `x-html`
+**Beispiel:** `<span x-html="foo"></span>`
+
+**Struktur:** `<span x-html="[expression]"`
+
+`x-html` funktioniert ähnlich wie `x-bind`, außer dass anstelle des Wertes eine Attributs, das `innerHTML` eines Elements aktualisiert wird.
+
+> :warning: **Es wird empfohlen, in diesem Fall nur vertrauenswürdige bzw. selbsterstellte Inhalte zu nutzen und auf nutzererstellte Inhalte zu verzichten.** :warning:
+>
+> Dynamisch gerendertes HTML von Drittparteien kann leicht zu Sicherheitslücken wie [XSS](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) führen.
+
+---
+
+### `x-ref`
+**Beispiel:** `<div x-ref="foo"></div><button x-on:click="$refs.foo.innerText = 'bar'"></button>`
+
+**Struktur:** `<div x-ref="[ref name]"></div><button x-on:click="$refs.[ref name].innerText = 'bar'"></button>`
+
+`x-ref` Ermöglicht es, die Elemente einer Komponente im DOM zu referenzieren. Wird das Attribut `x-ref` auf ein Element gesetzt, wird das Element durch das `$refs`-Objekt für alle EventHandler verfügbar geemacht.
+
+Dieses Vorgehen präsentiert sich als hilfreiche Alternative, wenn vermehrt der Befehl `document.querySelector` zur Referenzierung von Elementen eingesetzt werden muss.
+
+> Hinweis: Auch dynamische Werte können an x-ref gebunden werden: `<span :x-ref="item.id"></span>`.
+
+---
+
+### `x-if`
+**Beispiel:** `<template x-if="true"><div>Some Element</div></template>`
+
+**Struktur:** `<template x-if="[expression]"><div>Some Element</div></template>`
+
+Falls die Funktionalität von `x-show` (`x-show` setzt ein Element auf `display: none`, wenn die Bedingung `false` liefert) nicht ausreichen sollte, kann stattdessen `x-if` genutzt werden, um ein Element vollständig aus dem DOM zu entfernen.
+
+Da Alpine über kein virtuelles DOM verfügt, muss `x-if` unbedingt mit `<template></template>`-Tags genutzt werden. Diese Implementierung erlaubt es Alpine stabil zu bleiben und auf das echte DOM zuzugreifen.
+
+> Hinweis: Im Zuge der Nutzung von `x-if` muss der HTML-Stammknoten innerhalb des `<template></template>`-Tags ein einzelnes Element sein.
+
+> Hinweis: Im Zuge der Nutzung von `template` innerhalb eines `svg`-Tags, muss auf ein [polyfill](https://github.com/alpinejs/alpine/issues/637#issuecomment-654856538) zurückgegriffen werden. Die Ausführung des polyfills sollte vor der Initialisierung von Alpine.js stattfinden.
+
+---
+
+### `x-for`
+**Beispiel:**
+```html
+<template x-for="item in items" :key="item">
+    <div x-text="item"></div>
+</template>
+```
+
+> Hinweis: Das `:key`-Binding ist optional, wird jedoch DRINGEND empfohlen.
+
+`x-for` ist sinnvoll für Fälle, in denen für jedes Element in einem Array ein DOM-Knoten erstellt werden soll. Zwar erinnert die Funktionsweise an `v-for` in Vue, jedoch kann `x-for` nur in Kombination mit `template`-Tags eingesetzt werden (d.h. es besteht keine Kompatibilität mit regulären DOM-Elementen).
+
+Wird Zugriff auf den Index des momentanten Schleifendurchgangs benötigt, kann die folgende Schreibweise eingesetzt werden:
+
+```html
+<template x-for="(item, index) in items" :key="index">
+    <!-- Bei Bedarf kann "index" innerhalb der Schleife referenziert werden. -->
+    <div x-text="index"></div>
+</template>
+```
+
+Wird Zugriff auf das Array-Ojekt (colletion) des momentanten Schleifendurchgangs benötigt, kann die folgende Schreibweise eingesetzt werden:
+
+```html
+<template x-for="(item, index, collection) in items" :key="index">
+    <!-- Bei Bedarf kann "collection" innerhalb der Schleife referenziert werden. -->
+    <!-- Momentantes Element (item). -->
+    <div x-text="item"></div>
+    <!-- Selbiges Elemenet wie oben. -->
+    <div x-text="collection[index]"></div>
+    <!-- Element an der vorherigen Position. -->
+    <div x-text="collection[index - 1]"></div>
+</template>
+```
+
+> Hinweis: Im Zuge der Nutzung von `x-for` muss der HTML-Stammknoten innerhalb des `<template></template>`-Tags ein einzelnes Element sein.
+
+> Hinweis: Im Zuge der Nutzung von `template` innerhalb eines `svg`-Tags, muss auf ein [polyfill](https://github.com/alpinejs/alpine/issues/637#issuecomment-654856538) zurückgegriffen werden. Die Ausführung des polyfills sollte vor der Initialisierung von Alpine.js stattfinden.
+
+#### Verschachteln von `x-for`-Schleifen
+`x-for`-Schleifen können verschachtelt werden, jedoch MUSS jede Schleife innerhalb eines regulären Elements liegen. Zum Beispiel:
+
+```html
+<template x-for="item in items">
+    <div>
+        <template x-for="subItem in item.subItems">
+            <div x-text="subItem"></div>
+        </template>
+    </div>
+</template>
+```
+
+#### Iterieren über einen Bereich
+
+Alpine unterstützt die `i in n` Syntax, wobei `n` eine Ganzzahl ist. Dies ermöglicht es, über einen bestimmten Bereich an Elementen zu iterieren.
+
+```html
+<template x-for="i in 10">
+    <span x-text="i"></span>
+</template>
+```
+
+---
+
+### `x-transition`
+**Beispiel:**
+```html
+<div
+    x-show="open"
+    x-transition:enter="transition ease-out duration-300"
+    x-transition:enter-start="opacity-0 transform scale-90"
+    x-transition:enter-end="opacity-100 transform scale-100"
+    x-transition:leave="transition ease-in duration-300"
+    x-transition:leave-start="opacity-100 transform scale-100"
+    x-transition:leave-end="opacity-0 transform scale-90"
+>...</div>
+```
+
+```html
+<template x-if="open">
+    <div
+        x-transition:enter="transition ease-out duration-300"
+        x-transition:enter-start="opacity-0 transform scale-90"
+        x-transition:enter-end="opacity-100 transform scale-100"
+        x-transition:leave="transition ease-in duration-300"
+        x-transition:leave-start="opacity-100 transform scale-100"
+        x-transition:leave-end="opacity-0 transform scale-90"
+    >...</div>
+</template>
+```
+
+> Das obenstehende Beispiel nutzt Klassen aus [Tailwind CSS](https://tailwindcss.com)
+
+Alpine bietet 6 verschiedene Transitionen-Direktiven, um den verschiedenen Phasen einer Transition einen Elements Klassen hinzuzufügen, während sich diese zwischen den Zuständen "hidden" und "shown" befindet. Die Direktiven sind sowohl mit `x-show` als auch mit `x-if` kompatibel.
+
+Sieht man über die Differenzen in der Namensgebung hinweg, verhalten sie sich exakt gleich wie die Transitionen-Direktiven von VueJs. 
+
+| Direktive | Beschreibung |
+| --- | --- |
+| `:enter` | Wird während der gesamten Eintrittsphase angewendet. |
+| `:enter-start` | Wird hinzugefügt, bevor das Element eingefügt wurde. Wird entfernt, ein Frame nachdem das Element eingefügt wurde. |
+| `:enter-end` | Wird hinzugefügt, ein Frame nachdem das Element eingefügt wurde (zum selben Zeitpunkt, an dem `enter-start` entfernt wird). Wird enfernt, sobald der Übergang/die Animation beendet ist.
+| `:leave` | Wird während der gesamten Austrittsphase angewendet. |
+| `:leave-start` |  Wird sofort hinzugefügt, sobald eine ausgehende Transition ausgelöst wird. Wird nach einem Frame entfernt. |
+| `:leave-end` | Wird hinzugefügt, ein Frame nachdem eine ausgehende Transition ausgelöst wurde (zum selben Zeitpunkt, an dem `leave-start` entfernt wird). Wird enfernt, sobald der Übergang/die Animation beendet ist. |
+
+---
+
+### `x-spread`
+**Beispiel:**
+```html
+<div x-data="dropdown()">
+    <button x-spread="trigger">Öffne Dropdown</button>
+
+    <span x-spread="dialogue">Dropdown Inhalt</span>
+</div>
+
+<script>
+    function dropdown() {
+        return {
+            open: false,
+            trigger: {
+                ['@click']() {
+                    this.open = true
+                },
+            },
+            dialogue: {
+                ['x-show']() {
+                    return this.open
+                },
+                ['@click.away']() {
+                    this.open = false
+                },
+            }
+        }
+    }
+</script>
+```
+
+`x-spread` ermöglicht es, die Alpine-Bindings eines Elements in ein wiederverwendbares Objekt auszulagern.
+
+Die Namen des Objekts sind die Direktiven (jedes mögliche Direktive inklusive Modifikatoren). Die Werte sind Callback-Funktionen, welche von Alpine ausgewertet werden.
+
+> Hinweis: Der einzige Sonderfall von `x-spread` ist dessen Nutzung in Kombination mit `x-for`. Wenn es auf das Direktive `x-for` angewendet wird, sollte ein normaler String-Ausdruck von der Callback-Funktion retourniert werden. Zum Beispiel: `['x-for']() { return 'item in items' }`.
+
+---
+
+### `x-cloak`
+**Beispiel:** `<div x-data="{}" x-cloak></div>`
+
+`x-cloak`-Attribute werden vom Element entfernt, sobald Alpine initialisiert wird. Das Direktive wird genutzt, um das pre-initalisierte DOM auszublenden. Typischerweise wird das folgende Styling im globalen Geltungsbereich gesetzt, um die Funktionalität von `x-cloak` sicherzustellen:
+
+```html
+<style>
+    [x-cloak] { display: none; }
+</style>
+```
+
+### Magische Eigenschaften
+
+> Mit der Ausnahme von `$el`, sind magische Eigenschaften **nicht innerhalb von `x-data` verfügbar**, da die Komponente noch nicht initialisiert wurde.
+
+---
+
+### `$el`
+**Beispiel:**
+```html
+<div x-data>
+    <button @click="$el.innerHTML = 'foo'">Tausch mich mit "foo" aus</button>
+</div>
+```
+
+`$el` ist eine magische Eigenschaft, um auf den DOM-Knoten der Stammkomponente zuzugreifen.
+
+### `$refs`
+**Beispiel:**
+```html
+<span x-ref="foo"></span>
+
+<button x-on:click="$refs.foo.innerText = 'bar'"></button>
+```
+
+`$refs` ist eine magische Eigenschaft, um jene DOM-Elemente innerhalb einer Komponente aufzurufen, welche mit `x-ref` markiert sind. Dies ist nützlich, wenn DOM-Elemente manuell bearbeitet werden müssen.
+
+---
+
+### `$event`
+**Beispiel:**
+```html
+<input x-on:input="alert($event.target.value)">
+```
+
+`$event` ist eine magische Eigenschaft, um innerhalb eines EventHandlers auf das native Event-Objekt des Browsers zuzugreifen.
+
+> Hinweis: Die Eigenschaft $event ist nur in DOM-Ausdrücken verfügbar.
+
+Um innerhalb einer JavaScript-Funktion auf $event zuzugreifen, kann dieses als Argument an die Funktion übergeben werden.
+
+`<button x-on:click="myFunction($event)"></button>`
+
+---
+
+### `$dispatch`
+**Beispiel:**
+```html
+<div @custom-event="console.log($event.detail.foo)">
+    <button @click="$dispatch('custom-event', { foo: 'bar' })">
+    <!-- Wenn geklickt, wird "bar" in der Konsole ausgegeben -->
+</div>
+```
+
+**Hinweise bezüglich der Ereignisverbreitung (engl. "Event Propagation")**
+
+Wenn Ereignisse abgefangen werden sollen, welche von HTML-Knoten innerhalb derselben Verschachtelungshierarchie ausgelöst werden, muss der Modifikator [`.window`](https://github.com/alpinejs/alpine#x-on) eingesetzt werden. Dieses Verhalten ist auf das [Bubbling von Ereignissen](https://en.wikipedia.org/wiki/Event_bubbling) zurückzuführen:
+
+**Beispiel:**
+
+```html
+<div x-data>
+    <span @custom-event="console.log($event.detail.foo)"></span>
+    <button @click="$dispatch('custom-event', { foo: 'bar' })">
+<div>
+```
+
+> Das oben beschriebene Konstrukt wird nicht die gewünschte Funktionsweise erfüllen. Wird das Ereignis `custom-event` ausgelöst, wird es an das gemeinsame übergeordnete Element `div` propagiert.
+
+**Versand von Ereignissen an Komponenten**
+
+Die oben beschriebene Methode kann auch dazu verwendet werden, um die Kommunikation zwischen Komponenten zu ermöglichen:
+
+**Beispiel:**
+
+```html
+<div x-data @custom-event.window="console.log($event.detail)"></div>
+
+<button x-data @click="$dispatch('custom-event', 'Hello World!')">
+<!-- Wenn geklickt, wird "Hello World!" in der Konsole ausgegeben. -->
+```
+
+`$dispatch` ist eine Kurzschreibweise für die Erstellung von `CustomEvent` und dessen interner Versand mittels `.dispatchEvent()`. Es gibt zahlreiche Anwendungsfälle, in welchen der Versand von Daten durch benutzerdefinierten Ereignisse zwischen Komponenten eine sinnvolle Option darstellt. [Hier](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events) gibt es weitere Informationen bezüglich dem zugrundenliegenden `CustomEvent`-System in Browsern.
+
+Jegliche Datenquelle, welche als zweiter Parameter an `$dispatch('some-event', { some: 'data' })` weitergegeben wird, kann mithilfe der Eigenschaft "detail" aufgerufen werden: `$event.detail.some`. Das Hinzufügen von benutzerdefinierten Ereignis-Daten zur `.detail`-Eigenschaft ist Standard für `CustomEvent`s in Browsern. [Hier](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) gibt es mehr Informationen dazu.
+
+`$dispatch()` kann zusätzlich genutzt werden, um die Aktualisierung von Daten von `x-model`-Bindings auszulösen. Zum Beispiel:
+
+```html
+<div x-data="{ foo: 'bar' }">
+    <span x-model="foo">
+        <button @click="$dispatch('input', 'baz')">
+        <!-- Nachdem das Element `<button>` angeklickt wurde, wird `x-model` das durch Bubbling verbreitete "input"-Ereignis abfangen und foo auf "baz" setzen. -->
+    </span>
+</div>
+```
+
+> Hinweis: Die Eigenschaft $dispatch ist nur innerhalb von DOM-Ausdrücken verfügbar.
+
+Um innerhalb einer JavaScript-Funktion auf $dispatch zuzugreifen, kann dieses als Argument an die Funktion übergeben werden:
+
+`<button x-on:click="myFunction($dispatch)"></button>`
+
+---
+
+### `$nextTick`
+**Beispiel:**
+```html
+<div x-data="{ fruit: 'apple' }">
+    <button
+        x-on:click="
+            fruit = 'pear';
+            $nextTick(() => { console.log($event.target.innerText) });
+        "
+        x-text="fruit"
+    ></button>
+</div>
+```
+
+`$nextTick` ist eine magische Eigenschaft, durch welche der gegebene Ausdruck erst dann ausgeführt wird, NACHDEM Alpine die reaktiven DOM-Aktualisierungen durchgeführt hat. Dieses Verhalten ist nützlich, wenn mit dem DOM erst interagiert werden soll, NACHDEM alle Datenaktualisierungen durchgeführt wurden.
+
+---
+
+### `$watch`
+**Beispiel:**
+```html
+<div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))">
+    <button @click="open = ! open">Toggle Open</button>
+</div>
+```
+
+Die Eigenschaft einer Komponente kann mithilfe der magischen Methode `$watch` "beobachtet" werden. Erst wenn das `<button>`-Element im obigen Beispiel angeklickt wird und die Eigenschaft `open` aktualisiert wurde, wird die angegebene Callback-Funktion ausgelöst. Der neue Wert wird dann in der Konsole ausgegeben.
+
+## Sicherheit
+Wenn du eine Sicherheitslücke findest, sende bitte eine E-mail an [calebporzio@gmail.com]().
+
+Alpine basiert auf einer benutzerdefinierten Implementierung, welche das `Function`-Objekt nutzt, um seine Direktiven auszuwerten. Obwohl dieses Vorgehen sicherer ist als die Auswertung mittels `eval()`, ist dessen Nutzung in manchen Umgebungen nicht gestattet (z.B. in der Google Chrome App, aufgrund der restriktiven Content Security Policy (CSP)).
+
+Wenn du Alpine innerhalb einer Website nutzt, welche sensible Daten verarbeitet und auf [CSP](https://csp.withgoogle.com/docs/strict-csp.html) angewiesen ist, muss die Eigenschaft `unsafe-eval` in die policy eingefügt werden. Eine korrekt konfigurierte policy ist ein sinnvoller Mechanismus, um die persönlichen und finanziellen Daten von Nutzern zu schützen.
+
+Da sich die policy auf alle Skripts einer Seite auswirkt, ist es wichtig sicherzustellen, dass alle benutzten externen Bibliotheken und Pakete auf ihre Sicherheit geprüft wurden. Somit soll verhindert werden, dass deine Webiste Opfer von Cross Site Scripting Attacken (durch `eval()` oder DOM-Manipulation) wird.
+
+## V3 Roadmap
+* Move from `x-ref` to `ref` for Vue parity?
+* Add `Alpine.directive()`
+* Add `Alpine.component('foo', {...})` (With magic `__init()` method)
+* Dispatch Alpine events for "loaded", "transition-start", etc... ([#299](https://github.com/alpinejs/alpine/pull/299)) ?
+* Remove "object" (and array) syntax from `x-bind:class="{ 'foo': true }"` ([#236](https://github.com/alpinejs/alpine/pull/236) to add support for object syntax for the `style` attribute)
+* Improve `x-for` mutation reactivity ([#165](https://github.com/alpinejs/alpine/pull/165))
+* Add "deep watching" support in V3 ([#294](https://github.com/alpinejs/alpine/pull/294))
+* Add `$el` shortcut
+* Change `@click.away` to `@click.outside`?
+
+## License
+
+Copyright © 2019-2020 Caleb Porzio and contributors
+
+Licensed under the MIT license, see [LICENSE.md](LICENSE.md) for details.

+ 8 - 6
README.es.md

@@ -21,12 +21,12 @@ Podríamos considerarlo como un [Tailwind](https://tailwindcss.com/) para JavaSc
 
 Eso es todo. Se inicializará solo.
 
-Para entornos de producción, se recomienda especificar una número de versión en concreto en el enlace para evitar comportamientos inesperados que puedan romper las nuevas versiones. Por ejemplo, para usar la versión `2.7.0` (la última):
+Para entornos de producción, se recomienda especificar una número de versión en concreto en el enlace para evitar comportamientos inesperados que puedan romper las nuevas versiones. Por ejemplo, para usar la versión `2.7.2` (la última):
 ```html
-<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.0/dist/alpine.min.js" defer></script>
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.2/dist/alpine.min.js" defer></script>
 ```
 
-**Desde NPM:** Instalar el paquete desde NPM.
+**Desde npm:** Instalar el paquete desde npm.
 ```js
 npm i alpinejs
 ```
@@ -145,7 +145,7 @@ Y 6 propiedades mágicas:
 
 **Ejemplo:** `<div x-data="{ foo: 'bar' }">...</div>`
 
-**Estructura:** `<div x-data="[JSON data object]">...</div>`
+**Estructura:** `<div x-data="[object literal]">...</div>`
 
 `x-data` declara un nuevo *scope* del componente. Indica al *framework* que debe inicializar un nuevo componente con el objeto especificado.
 
@@ -576,7 +576,9 @@ Estas, funcionan exactamente igual que las directivas de transición de VueJS co
 
 Las claves del objeto son las directivas (puede ser cualquiera, incluyendo modificadores), y los valores son *callbacks* a evaluar por Alpine.
 
-> Note: La única anomalía con x-spread ocurre cuando se usa junto a `x-for`. Cuando la directiva a extender es `x-for`, es necesario retornar una expresion en formato de *string* en el *callback*. Por ejemplo: `['x-for']() { return 'item in items' }`.
+> Note: Excepciónes con `x-spread`:
+> - Cuando la directiva a extender es `x-for`, es necesario retornar una expresion en formato de *string* en el *callback*. Por ejemplo: `['x-for']() { return 'item in items' }`.
+> - `x-init` y `x-data` no se pueden usar dentro de un objeto para "spread".
 
 ---
 
@@ -724,7 +726,7 @@ Si necesitas acceder a $dispatch desde dentro de una función de JavaScript, pue
 Puedes hacer "watch" a la propiedad de un componente con el método mágico `$watch`. En el ejemplo de arriba, cuando se pulsa el botón y `open` cambia, la retrollamada provista ejecutara el `console.log` con el nuevo valor.
 
 ## Seguridad
-Si encuentras una brecha de seguridad, por favor envía un email a [calebporzio@gmail.com]()
+Si encuentras una brecha de seguridad, por favor envía un email a [calebporzio@gmail.com]().
 
 Alpine depende de una implementación personalizada utilizando el objeto `Function` para evaluar las directivas. A pesar de ser más seguro que `eval()`, su uso está prohibido en algunos entornos, tels como Google Chrome App, utilizando Content Security Policy restrictivas (CSP).
 

+ 796 - 0
README.id.md

@@ -0,0 +1,796 @@
+# Alpine.js
+
+![npm bundle size](https://img.shields.io/bundlephobia/minzip/alpinejs)
+![npm version](https://img.shields.io/npm/v/alpinejs)
+[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true)](https://alpinejs.codewithhugo.com/chat/)
+
+Alpine.js menawarkan kepada Anda sifat reaktif dan deklaratif dari framework besar seperti Vue atau React dengan biaya yang jauh lebih rendah.
+
+Anda bisa mempertahankan DOM Anda, dan taburkan dalam perilaku sesuai keinginan Anda.
+
+Anggap saja seperti [Tailwind](https://tailwindcss.com/) untuk JavaScript.
+
+> Catatan: Hampir seluruh sintaks dipinjam dari [Vue](https://vuejs.org/) (dan dengan ekstensi [Angular](https://angularjs.org/)). Saya selamanya berterima kasih atas hadiah mereka untuk web.
+
+## Dokumentasi yang diterjemahkan
+
+| Bahasa | Tautan untuk dokumentasi |
+| --- | --- |
+| Chinese Traditional | [**繁體中文說明文件**](./README.zh-TW.md) |
+| Indonesian | [**Dokumentasi Bahasa Indonesia**](./README.id.md) |
+| Japanese | [**日本語ドキュメント**](./README.ja.md) |
+| Portuguese | [**Documentação em Português**](./README.pt.md) |
+| Russian | [**Документация на русском**](./README.ru.md) |
+| Spanish | [**Documentación en Español**](./README.es.md) |
+
+## Instalasi
+
+**Dari CDN:** Tambahkan skrip berikut ke akhir bagian `<head>` Anda.
+```html
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
+```
+
+Itu saja. Itu akan menginisialisasi dirinya sendiri.
+
+Untuk lingkungan produksi, disarankan untuk memasang pin pada nomor versi tertentu di link untuk menghindari kerusakan yang tidak terduga dari versi yang lebih baru. Misalnya, untuk menggunakan versi `2.7.2` (terbaru):
+```html
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.2/dist/alpine.min.js" defer></script>
+```
+
+**Dari NPM:** Instal paket dari NPM.
+```js
+npm i alpinejs
+```
+
+Sertakan dalam skrip Anda.
+```js
+import 'alpinejs'
+```
+
+**Untuk dukungan IE11** Gunakan skrip berikut sebagai gantinya.
+```html
+<script type="module" src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js"></script>
+<script nomodule src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine-ie11.min.js" defer></script>
+```
+
+Pola di atas adalah pola modul / nomodule yang akan membuat bundel modern dimuat secara otomatis di browser modern, dan bundel IE11 dimuat secara otomatis di IE11 dan browser lama lainnya.
+
+## Penggunaan
+
+*Dropdown/Modal*
+```html
+<div x-data="{ open: false }">
+    <button @click="open = true">Open Dropdown</button>
+
+    <ul
+        x-show="open"
+        @click.away="open = false"
+    >
+        Dropdown Body
+    </ul>
+</div>
+```
+
+*Tabs*
+```html
+<div x-data="{ tab: 'foo' }">
+    <button :class="{ 'active': tab === 'foo' }" @click="tab = 'foo'">Foo</button>
+    <button :class="{ 'active': tab === 'bar' }" @click="tab = 'bar'">Bar</button>
+
+    <div x-show="tab === 'foo'">Tab Foo</div>
+    <div x-show="tab === 'bar'">Tab Bar</div>
+</div>
+```
+
+Anda bahkan dapat menggunakannya untuk hal-hal yang tidak sepele: 
+*Mengambil konten HTML dari dropdown terlebih dahulu saat mengarahkan kursor*
+```html
+<div x-data="{ open: false }">
+    <button
+        @mouseenter.once="
+            fetch('/dropdown-partial.html')
+                .then(response => response.text())
+                .then(html => { $refs.dropdown.innerHTML = html })
+        "
+        @click="open = true"
+    >Show Dropdown</button>
+
+    <div x-ref="dropdown" x-show="open" @click.away="open = false">
+        Loading Spinner...
+    </div>
+</div>
+```
+
+## Belajar
+
+Ada 14 perintah yang tersedia untuk Anda:
+
+| Perintah | Deskripsi |
+| --- | --- |
+| [`x-data`](#x-data) | Mendeklarasikan cakupan komponen baru. |
+| [`x-init`](#x-init) | Menjalankan ekspresi saat komponen diinisialisasi. |
+| [`x-show`](#x-show) | Merubah properti `display: none;` pada elemen tergantung pada ekspresi (true atau false). |
+| [`x-bind`](#x-bind) | Menetapkan nilai atribut ke hasil ekspresi JS |
+| [`x-on`](#x-on) | Melampirkan event listener ke elemen. Menjalankan ekspresi JS saat dipancarkan. |
+| [`x-model`](#x-model) | Menambahkan "two-way data binding (pengikatan data dua arah)" ke sebuah elemen. Menjaga elemen masukan tetap sinkron dengan data komponen. |
+| [`x-text`](#x-text) | Bekerja mirip dengan `x-bind`, tetapi akan memperbarui `innerText` dari sebuah elemen. |
+| [`x-html`](#x-html) | Bekerja mirip dengan `x-bind`, tetapi akan memperbarui `innerHTML` dari sebuah elemen. |
+| [`x-ref`](#x-ref) | Cara mudah untuk mengambil elemen DOM mentah dari komponen Anda. |
+| [`x-if`](#x-if) | Hapus elemen sepenuhnya dari DOM. Harus digunakan pada tag `<template>`. |
+| [`x-for`](#x-for) | Buat node DOM baru untuk setiap item dalam array. Harus digunakan pada tag `<template>`. |
+| [`x-transition`](#x-transition) | Arahan untuk menerapkan kelas ke berbagai tahapan transisi elemen. |
+| [`x-spread`](#x-spread) | Memungkinkan Anda mengikat objek arahan Alpine ke elemen agar dapat digunakan kembali dengan lebih baik. |
+| [`x-cloak`](#x-cloak) | Atribut ini dihapus saat Alpine menginisialisasi. Berguna untuk menyembunyikan DOM yang sudah diinisialisasi. |
+
+dan 6 properti-properti ajaib:
+
+| Properti Ajaib | Deskripsi |
+| --- | --- |
+| [`$el`](#el) |  Ambil node DOM komponen akar. |
+| [`$refs`](#refs) | Ambil elemen DOM yang ditandai dengan `x-ref` di dalam komponen. |
+| [`$event`](#event) | Mengambil objek "Event" browser asli dalam event listener.  |
+| [`$dispatch`](#dispatch) | Buat `CustomEvent` dan kirim menggunakan `.dispatchEvent()` secara internal. |
+| [`$nextTick`](#nexttick) | Jalankan ekspresi tertentu SETELAH Alpine membuat pembaruan DOM reaktifnya. |
+| [`$watch`](#watch) | Akan mengaktifkan callback yang diberikan saat properti komponen yang Anda "awasi" berubah. |
+
+
+## Sponsor
+
+<img width="33%" src="https://refactoringui.nyc3.cdn.digitaloceanspaces.com/tailwind-logo.svg" alt="Tailwind CSS">
+
+**Ingin logo Anda di sini? [DM di Twitter](https://twitter.com/calebporzio)**
+
+## Proyek Komunitas
+
+* [AlpineJS Weekly Newsletter](https://alpinejs.codewithhugo.com/newsletter/)
+* [Spruce (State Management)](https://github.com/ryangjchandler/spruce)
+* [Turbolinks Adapter](https://github.com/SimoTod/alpine-turbolinks-adapter)
+* [Alpine Magic Helpers](https://github.com/KevinBatdorf/alpine-magic-helpers)
+* [Awesome Alpine](https://github.com/ryangjchandler/awesome-alpine)
+
+### Perintah
+
+---
+
+### `x-data`
+
+**Contoh:** `<div x-data="{ foo: 'bar' }">...</div>`
+
+**Struktur:** `<div x-data="[object literal]">...</div>`
+
+`x-data` mendeklarasikan cakupan komponen baru. Ini memberi tahu kerangka kerja untuk menginisialisasi komponen baru dengan objek data berikut.
+
+Anggap saja seperti properti data dari komponen Vue.
+
+**Ekstrak Logika Komponen**
+
+Anda dapat mengekstrak data (dan perilaku) menjadi fungsi yang dapat digunakan kembali:
+
+```html
+<div x-data="dropdown()">
+    <button x-on:click="open">Open</button>
+
+    <div x-show="isOpen()" x-on:click.away="close">
+        // Dropdown
+    </div>
+</div>
+
+<script>
+    function dropdown() {
+        return {
+            show: false,
+            open() { this.show = true },
+            close() { this.show = false },
+            isOpen() { return this.show === true },
+        }
+    }
+</script>
+```
+
+> **Untuk pengguna bundler**, perhatikan bahwa Alpine.js mengakses fungsi yang ada dalam cakupan global (`window`), Anda harus secara eksplisit menetapkan fungsi Anda ke `window` untuk menggunakannya dengan `x-data` misalnya `window.dropdown = function () {}` (ini karena dengan fungsi Webpack, Rollup, Parcel, dll. yang Anda tentukan akan secara default ke lingkup modul, bukan `window`).
+
+
+Anda juga dapat mencampur beberapa objek data menggunakan penghancuran objek:
+
+```html
+<div x-data="{...dropdown(), ...tabs()}">
+```
+
+---
+
+### `x-init`
+**Contoh:** `<div x-data="{ foo: 'bar' }" x-init="foo = 'baz'"></div>`
+
+**Struktur:** `<div x-data="..." x-init="[expression]"></div>`
+
+`x-init` menjalankan ekspresi saat komponen diinisialisasi.
+
+Jika Anda ingin menjalankan kode SETELAH Alpine telah melakukan pembaruan awal ke DOM (sesuatu seperti hook `mounted()` yang di VueJS), Anda dapat mengembalikan callback dari `x-init`, dan itu akan dijalankan setelah:
+
+`x-init="() => { // kami memiliki akses ke status inisialisasi post-dom di sini // }"`
+
+---
+
+### `x-show`
+**Contoh:** `<div x-show="open"></div>`
+
+**Struktur:** `<div x-show="[expression]"></div>`
+
+`x-show` mengubah gaya `display: none;` pada elemen tergantung apakah ekspresi ditetapkan ke `true` atau `false`.
+
+**x-show.transition**
+
+`x-show.transition` adalah API kenyamanan untuk membuat `x-show` Anda lebih menyenangkan menggunakan transisi CSS.
+
+```html
+<div x-show.transition="open">
+    Konten ini akan ditransisikan masuk dan keluar.
+</div>
+```
+
+| Perintah | Deskripsi |
+| --- | --- |
+| `x-show.transition` | Fade dan skala simultan. (opacity, scale: 0.95, timing-function: cubic-bezier(0.4, 0.0, 0.2, 1), duration-in: 150ms, duration-out: 75ms)
+| `x-show.transition.in` | Hanya transisi masuk. |
+| `x-show.transition.out` | Hanya transisi keluar. |
+| `x-show.transition.opacity` | Hanya menggunakan fade saja. |
+| `x-show.transition.scale` | Hanya menggunakan timbangan saja. |
+| `x-show.transition.scale.75` | Sesuaikan transformasi skala CSS `transform: scale(.75)`. |
+| `x-show.transition.duration.200ms` | Menyetel transisi "masuk" ke 200ms. Transisi "keluar" akan disetel menjadi setengahnya (100ms). |
+| `x-show.transition.origin.top.right` | Sesuaikan asal transformasi CSS `transform-origin: top right`. |
+| `x-show.transition.in.duration.200ms.out.duration.50ms` | Durasi berbeda untuk "masuk" dan "keluar". |
+
+> Catatan: Semua pengubah transisi ini dapat digunakan bersama satu sama lain. Ini mungkin (meskipun konyol lol): `x-show.transition.in.duration.100ms.origin.top.right.opacity.scale.85.out.duration.200ms.origin.bottom.left.opacity.scale.95`
+
+> Catatan: `x-show` akan menunggu setiap anak menyelesaikan transisi keluar. Jika Anda ingin mengabaikan perilaku ini, tambahkan modifer `.immediate`:
+```html
+<div x-show.immediate="open">
+    <div x-show.transition="open">
+</div>
+```
+---
+
+### `x-bind`
+
+> Catatan: Anda bebas menggunakan sintaks ":" yang lebih pendek: `:type = "..."`
+
+**Contoh:** `<input x-bind:type="inputType">`
+
+**Struktur:** `<input x-bind:[attribute]="[expression]">`
+
+`x-bind` menyetel nilai atribut ke hasil ekspresi JavaScript. Ekspresi memiliki akses ke semua kunci objek data komponen, dan akan diperbarui setiap kali datanya diperbarui.
+
+> Catatan: Binding atribut HANYA diperbarui ketika dependensinya diperbarui. Framework ini cukup pintar untuk mengamati perubahan data dan mendeteksi binding mana yang mempedulikannya.
+
+**`x-bind` untuk atribut kelas**
+
+`x-bind` berperilaku sedikit berbeda saat mengikat ke atribut class.
+
+Untuk kelas, Anda meneruskan objek yang kuncinya adalah nama kelas, dan nilai adalah ekspresi boolean untuk menentukan apakah nama kelas tersebut diterapkan atau tidak.
+
+Sebagai contoh:
+`<div x-bind:class="{ 'hidden': foo }"></div>`
+
+Dalam contoh ini, kelas "hidden" hanya akan diterapkan jika nilai atribut data `foo` adalah `true`.
+
+**`x-bind` untuk atribut boolean**
+
+`x-bind` mendukung atribut boolean dengan cara yang sama seperti atribut nilai, menggunakan variabel sebagai kondisi atau ekspresi JavaScript apa pun yang menghasilkan `true` atau `false`.
+
+Sebagai contoh:
+```html
+<!-- Given: -->
+<button x-bind:disabled="myVar">Click me</button>
+
+<!-- When myVar == true: -->
+<button disabled="disabled">Click me</button>
+
+<!-- When myVar == false: -->
+<button>Click me</button>
+```
+
+Ini akan menambah atau menghapus atribut `disabled` ketika `myVar` masing-masing bernilai `true` atau `false`.
+
+Atribut Boolean didukung sesuai dengan [HTML spesifikasi](https://html.spec.whatwg.org/multipage/indices.html#attributes-3:boolean-attribute), sebagai contoh `disabled`, `readonly`, `required`, `checked`, `hidden`, `selected`, `open`, dll.
+
+**pengubah `.camel`**
+
+**Contoh:** `<svg x-bind:view-box.camel="viewBox">`
+
+Pengubah `camel` akan mengikat kasus unta yang setara dengan nama atribut. Dalam contoh di atas, nilai `viewBox` akan terikat pada atribut `viewBox` sebagai lawan dari atribut `view-box`.
+
+---
+
+### `x-on`
+
+> Catatan: Anda bebas menggunakan sintaks "@" yang lebih pendek: `@click="..."`
+
+**Contoh:** `<button x-on:click="foo = 'bar'"></button>`
+
+**Struktur:** `<button x-on:[event]="[expression]"></button>`
+
+`x-on` melampirkan event listener ke elemen tempatnya dideklarasikan. Saat peristiwa itu dipancarkan, ekspresi JavaScript disetel sebagai nilainya dijalankan.
+
+Jika ada data yang diubah dalam ekspresi, atribut elemen yang lain "bound" ke data ini, akan diperbarui.
+
+> Catatan: Anda juga dapat menentukan nama fungsi JavaScript
+
+**Contoh:** `<button x-on:click="myFunction"></button>`
+
+Ini sama dengan: `<button x-on:click="myFunction($event)"></button>`
+
+**pengubah `keydown`**
+
+**Contoh:** `<input type="text" x-on:keydown.escape="open = false">`
+
+Anda dapat menentukan kunci tertentu untuk didengarkan menggunakan pengubah keydown yang ditambahkan ke perintah `x-on: keydown`. Perhatikan bahwa pengubah adalah versi nilai `Event.key` berbasis kebab.
+
+Contoh: `enter`, `escape`, `arrow-up`, `arrow-down`
+
+> Catatan: Anda juga dapat mendengarkan kombinasi tombol pengubah sistem seperti: `x-on:keydown.cmd.enter="foo"`
+
+**pengubah `.away`**
+
+**Contoh:** `<div x-on:click.away="showModal = false"></div>`
+
+Saat pengubah `.away` ada, pengendali kejadian hanya akan dijalankan ketika kejadian berasal dari sumber selain dirinya sendiri, atau turunannya.
+
+Ini berguna untuk menyembunyikan dropdown dan modals saat pengguna mengkliknya.
+
+**pengubah `.prevent`**
+
+**Contoh:** `<input type="checkbox" x-on:click.prevent>`
+
+Menambahkan `.prevent` ke pemroses acara akan memanggil `preventDefault` pada acara yang dipicu. Dalam contoh di atas, ini berarti kotak centang tidak akan benar-benar dicentang ketika pengguna mengkliknya.
+
+**pengubah `.stop`**
+
+**Contoh:** `<div x-on:click="foo = 'bar'"><button x-on:click.stop></button></div>`
+
+Menambahkan `.stop` ke event listener akan memanggil `stopPropagation` pada event yang dipicu. Dalam contoh di atas, ini berarti peristiwa "click" tidak akan menggelembung dari tombol ke luar `<div>`. Atau dengan kata lain, saat pengguna mengklik tombol, `foo` tidak akan disetel ke `'bar'`.
+
+**pengubah `.self`**
+
+**Contoh:** `<div x-on:click.self="foo = 'bar'"><button></button></div>`
+
+Menambahkan `.self` ke pemroses acara hanya akan memicu penangan jika `$event.target` adalah elemen itu sendiri. Dalam contoh di atas, ini berarti peristiwa "klik" yang menggelembung dari tombol ke luar `<div>` **tidak akan** menjalankan penangan.
+
+**pengubah `.window`**
+
+**Contoh:** `<div x-on:resize.window="isOpen = window.outerWidth > 768 ? false : open"></div>`
+
+Menambahkan `.window` ke event listener akan menginstal listener di objek global window, bukan di simpul DOM tempat ia dideklarasikan. Ini berguna ketika Anda ingin mengubah status komponen ketika sesuatu berubah dengan jendela, seperti acara pengubahan ukuran. Dalam contoh ini, ketika jendela tumbuh lebih besar dari lebar 768 piksel, kami akan menutup modal / dropdown, jika tidak, pertahankan status yang sama.
+
+>Catatan: Anda juga bisa menggunakan pengubah `.document` untuk melampirkan listener ke `dokumen`, bukan `window`
+
+**pengubah `.once`**
+
+**Contoh:** `<button x-on:mouseenter.once="fetchSomething()"></button>`
+
+Menambahkan pengubah `.once` ke event listener akan memastikan bahwa listener hanya akan ditangani satu kali. Ini berguna untuk hal-hal yang hanya ingin Anda lakukan sekali, seperti mengambil sebagian HTML dan semacamnya.
+
+**pengubah `.passive`**
+
+**Contoh:** `<button x-on:mousedown.passive="interactive = true"></button>`
+
+Menambahkan `.passive` modifier ke event listener akan membuat pemroses menjadi pasif, yang berarti `preventDefault()` tidak akan berfungsi pada acara apa pun yang sedang diproses, hal ini dapat membantu, misalnya dengan kinerja scroll pada perangkat sentuh.
+
+**pengubah `.debounce`**
+
+**Contoh:** `<input x-on:input.debounce="fetchSomething()">`
+
+Pengubah `debounce` memungkinkan Anda untuk "melepaskan" pengendali event. Dengan kata lain, event handler TIDAK akan berjalan hingga waktu tertentu berlalu sejak event terakhir yang diaktifkan. Saat penangan siap dipanggil, pemanggilan penangan terakhir akan dijalankan.
+
+Waktu "tunggu" debounce default adalah 250 milidetik.
+
+Jika Anda ingin menyesuaikan ini, Anda dapat menentukan waktu tunggu khusus seperti:
+
+```
+<input x-on:input.debounce.750="fetchSomething()">
+<input x-on:input.debounce.750ms="fetchSomething()">
+```
+
+**pengubah `.camel`**
+
+**Contoh:** `<input x-on:event-name.camel="doSomething()">`
+
+Pengubah `camel` akan melampirkan pendengar acara untuk nama acara yang setara dengan kasus unta. Dalam contoh di atas, ekspresi akan dievaluasi ketika event `eventName` diaktifkan pada elemen.
+
+---
+
+### `x-model`
+**Contoh:** `<input type="text" x-model="foo">`
+
+**Struktur:** `<input type="text" x-model="[data item]">`
+
+`x-model` menambahkan "pengikatan data dua arah" ke elemen. Dengan kata lain, nilai elemen input akan tetap sinkron dengan nilai item data komponen.
+
+> Catatan: `x-model` cukup pintar untuk mendeteksi perubahan pada input teks, kotak centang, tombol radio, textarea, pemilihan, dan beberapa pilihan. Ini harus berperilaku [seperti Vue](https://vuejs.org/v2/guide/forms.html) dalam skenario tersebut.
+
+**pengubah `.number`**
+**Contoh:** `<input x-model.number="age">`
+
+Pengubah angka akan mengubah nilai input menjadi angka. Jika nilai tidak dapat diurai sebagai angka yang valid, nilai asli dikembalikan.
+
+**pengubah `.debounce`**
+**Contoh:** `<input x-model.debounce="search">`
+
+Pengubah `debounce` memungkinkan Anda menambahkan "debounce" ke pembaruan nilai. Dengan kata lain, event handler TIDAK akan berjalan hingga waktu tertentu berlalu sejak event terakhir yang diaktifkan. Saat penangan siap dipanggil, pemanggilan penangan terakhir akan dijalankan.
+
+Waktu "tunggu" debounce default adalah 250 milidetik.
+
+Jika Anda ingin menyesuaikan ini, Anda dapat menentukan waktu tunggu khusus seperti:
+
+```
+<input x-model.debounce.750="search">
+<input x-model.debounce.750ms="search">
+```
+
+---
+
+### `x-text`
+**Contoh:** `<span x-text="foo"></span>`
+
+**Struktur:** `<span x-text="[expression]"`
+
+`x-text` bekerja mirip dengan `x-bind`, kecuali memperbarui nilai atribut, itu akan memperbarui `innerText` dari sebuah elemen.
+
+---
+
+### `x-html`
+**Contoh:** `<span x-html="foo"></span>`
+
+**Struktur:** `<span x-html="[expression]"`
+
+`x-html` bekerja mirip dengan `x-bind`, kecuali untuk memperbarui nilai atribut, itu akan memperbarui `innerHTML` dari sebuah elemen.
+
+> :warning: **Hanya gunakan pada konten tepercaya dan jangan pernah pada konten yang disediakan pengguna.** :warning:
+>
+> Merender HTML secara dinamis dari pihak ketiga dapat dengan mudah menyebabkan kerentanan [XSS](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting).
+
+---
+
+### `x-ref`
+**Contoh:** `<div x-ref="foo"></div><button x-on:click="$refs.foo.innerText = 'bar'"></button>`
+
+**Struktur:** `<div x-ref="[ref name]"></div><button x-on:click="$refs.[ref name].innerText = 'bar'"></button>`
+
+`x-ref` menyediakan cara mudah untuk mengambil elemen DOM mentah dari komponen Anda. Dengan menyetel atribut `x-ref` pada sebuah elemen, Anda membuatnya tersedia untuk semua penangan kejadian di dalam sebuah objek yang disebut `$refs`.
+
+Ini adalah alternatif yang berguna untuk menyetel id dan menggunakan `document.querySelector` di semua tempat.
+
+> Catatan: Anda juga dapat mengikat nilai dinamis untuk x-ref: `<span: x-ref = "item.id"> </span>` jika perlu.
+
+---
+
+### `x-if`
+**Contoh:** `<template x-if="true"><div>Some Element</div></template>`
+
+**Struktur:** `<template x-if="[expression]"><div>Some Element</div></template>`
+
+Untuk kasus di mana `x-show` tidak cukup (`x-show` menyetel elemen ke `display: none` if false), `x-if` dapat digunakan untuk benar-benar menghapus elemen sepenuhnya dari DOM.
+
+Penting bahwa `x-if` digunakan pada tag `<template></template>` karena Alpine tidak menggunakan DOM virtual. Implementasi ini memungkinkan Alpine untuk tetap bertahan dan menggunakan DOM asli untuk melakukan keajaibannya.
+
+> Catatan: `x-if` harus memiliki root elemen tunggal di dalam tag `<template></template>`.
+
+> Catatan: Saat menggunakan `template` di tag `svg`, Anda perlu menambahkan [polyfill](https://github.com/alpinejs/alpine/issues/637#issuecomment-654856538) yang harus dijalankan sebelum Alpine.js diinisialisasi.
+---
+
+### `x-for`
+**Contoh:**
+```html
+<template x-for="item in items" :key="item">
+    <div x-text="item"></div>
+</template>
+```
+
+> Catatan: pengikat `:key` bersifat opsional, tetapi SANGAT disarankan.
+
+`x-for` tersedia untuk kasus ketika Anda ingin membuat simpul DOM baru untuk setiap item dalam larik. Ini akan tampak mirip dengan `v-for` di Vue, dengan satu pengecualian yaitu harus ada di tag `template`, dan bukan elemen DOM biasa.
+
+Jika Anda ingin mengakses indeks iterasi saat ini, gunakan sintaks berikut:
+
+```html
+<template x-for="(item, index) in items" :key="index">
+    <!-- You can also reference "index" inside the iteration if you need. -->
+    <div x-text="index"></div>
+</template>
+```
+
+Jika Anda ingin mengakses objek array (kumpulan) dari iterasi, gunakan sintaks berikut:
+
+```html
+<template x-for="(item, index, collection) in items" :key="index">
+    <!-- You can also reference "collection" inside the iteration if you need. -->
+    <!-- Current item. -->
+    <div x-text="item"></div>
+    <!-- Same as above. -->
+    <div x-text="collection[index]"></div>
+    <!-- Previous item. -->
+    <div x-text="collection[index - 1]"></div>
+</template>
+```
+
+> Catatan: `x-for` harus memiliki root elemen tunggal di dalam tag `<template></template>`.
+
+> Catatan: Saat menggunakan `template` di tag `svg`, Anda perlu menambahkan [polyfill](https://github.com/alpinejs/alpine/issues/637#issuecomment-654856538) yang harus dijalankan sebelum Alpine.js diinisialisasi.
+
+#### Nesting `x-for`s
+Anda dapat melapisi pengulangan `x-for`, tetapi Anda HARUS membungkus setiap pengulangan dalam sebuah elemen. Sebagai contoh:
+
+```html
+<template x-for="item in items">
+    <div>
+        <template x-for="subItem in item.subItems">
+            <div x-text="subItem"></div>
+        </template>
+    </div>
+</template>
+```
+
+#### Iterasi dalam rentang tertentu
+
+Alpine mendukung sintaks `i in n`, di mana `n` adalah bilangan bulat, memungkinkan Anda untuk melakukan iterasi pada rentang elemen yang tetap.
+
+```html
+<template x-for="i in 10">
+    <span x-text="i"></span>
+</template>
+```
+
+---
+
+### `x-transition`
+**Contoh:**
+```html
+<div
+    x-show="open"
+    x-transition:enter="transition ease-out duration-300"
+    x-transition:enter-start="opacity-0 transform scale-90"
+    x-transition:enter-end="opacity-100 transform scale-100"
+    x-transition:leave="transition ease-in duration-300"
+    x-transition:leave-start="opacity-100 transform scale-100"
+    x-transition:leave-end="opacity-0 transform scale-90"
+>...</div>
+```
+
+```html
+<template x-if="open">
+    <div
+        x-transition:enter="transition ease-out duration-300"
+        x-transition:enter-start="opacity-0 transform scale-90"
+        x-transition:enter-end="opacity-100 transform scale-100"
+        x-transition:leave="transition ease-in duration-300"
+        x-transition:leave-start="opacity-100 transform scale-100"
+        x-transition:leave-end="opacity-0 transform scale-90"
+    >...</div>
+</template>
+```
+
+> Contoh di atas menggunakan kelas dari [Tailwind CSS](https://tailwindcss.com)
+
+Alpine menawarkan 6 arahan transisi berbeda untuk menerapkan kelas ke berbagai tahap transisi elemen antara status "tersembunyi" dan "ditampilkan". Arahan ini bekerja dengan `x-show` DAN `x-if`.
+
+Ini berperilaku persis seperti arahan transisi VueJs, kecuali mereka memiliki nama yang berbeda dan lebih masuk akal:
+
+| Perintah | Deskripsi |
+| --- | --- |
+| `:enter` | Diterapkan selama seluruh fase masuk. |
+| `:enter-start` | Ditambahkan sebelum elemen dimasukkan, dihapus satu bingkai setelah elemen dimasukkan. |
+| `:enter-end` | Menambahkan satu bingkai setelah elemen dimasukkan (pada saat yang sama `enter-start` dihapus), dihapus ketika transisi / animasi selesai.
+| `:leave` | Diterapkan selama seluruh fase keluar. |
+| `:leave-start` | Ditambahkan segera ketika transisi keluar dipicu, dihapus setelah satu frame. |
+| `:leave-end` | Ditambahkan satu frame setelah transisi keluar dipicu (pada saat yang sama `leave-start` dihapus), dihapus ketika transisi / animasi selesai.
+
+---
+
+### `x-spread`
+**Contoh:**
+```html
+<div x-data="dropdown()">
+    <button x-spread="trigger">Open Dropdown</button>
+
+    <span x-spread="dialogue">Dropdown Contents</span>
+</div>
+
+<script>
+    function dropdown() {
+        return {
+            open: false,
+            trigger: {
+                ['@click']() {
+                    this.open = true
+                },
+            },
+            dialogue: {
+                ['x-show']() {
+                    return this.open
+                },
+                ['@click.away']() {
+                    this.open = false
+                },
+            }
+        }
+    }
+</script>
+```
+
+`x-spread` memungkinkan Anda untuk mengekstrak binding Alpine sebuah elemen menjadi objek yang dapat digunakan kembali.
+
+Kunci objek adalah arahan (Bisa berupa arahan apa pun termasuk pengubah), dan nilainya adalah callback untuk dievaluasi oleh Alpine.
+
+> Catatan: Ada beberapa peringatan untuk x-spread:
+> - Jika direktif yang menjadi "spread" adalah `x-for`, Anda harus mengembalikan string ekspresi normal dari callback. Misalnya: `['x-for'] () {return 'item in items'}`.
+> - `x-data` dan `x-init` tidak dapat digunakan di dalam objek "spread"
+
+---
+
+### `x-cloak`
+**Contoh:** `<div x-data="{}" x-cloak></div>`
+
+`x-cloak` atribut dihapus dari elemen saat Alpine menginisialisasi. Ini berguna untuk menyembunyikan DOM yang telah diinisialisasi sebelumnya. Biasanya untuk menambahkan gaya global berikut agar ini berfungsi:
+
+```html
+<style>
+    [x-cloak] { display: none; }
+</style>
+```
+
+### Properti Sihir
+
+> Dengan pengecualian `$el`, properti ajaib tidak tersedia dalam `x-data` karena komponen belum diinisialisasi.
+
+---
+
+### `$el`
+**Contoh:**
+```html
+<div x-data>
+    <button @click="$el.innerHTML = 'foo'">Replace me with "foo"</button>
+</div>
+```
+
+`$el` adalah properti ajaib yang bisa digunakan untuk mengambil simpul DOM komponen akar.
+
+### `$refs`
+**Contoh:**
+```html
+<span x-ref="foo"></span>
+
+<button x-on:click="$refs.foo.innerText = 'bar'"></button>
+```
+
+`$refs` adalah properti ajaib yang bisa digunakan untuk mengambil elemen DOM yang ditandai dengan `x-ref` di dalam komponen. Ini berguna saat Anda perlu memanipulasi elemen DOM secara manual.
+
+---
+
+### `$event`
+**Contoh:**
+```html
+<input x-on:input="alert($event.target.value)">
+```
+
+`$event` adalah properti ajaib yang bisa digunakan dalam event listener untuk mengambil objek "Event" dari browser asli.
+
+> Catatan: Properti $event hanya tersedia dalam ekspresi DOM.
+
+If you need to access $event inside of a JavaScript function you can pass it in directly:
+
+`<button x-on:click="myFunction($event)"></button>`
+
+---
+
+### `$dispatch`
+**Contoh:**
+```html
+<div @custom-event="console.log($event.detail.foo)">
+    <button @click="$dispatch('custom-event', { foo: 'bar' })">
+    <!-- When clicked, will console.log "bar" -->
+</div>
+```
+
+**Catatan tentang Propagasi Peristiwa**
+
+Perhatikan bahwa, karena [peristiwa menggelegak](https://en.wikipedia.org/wiki/Event_bubbling), saat Anda perlu merekam peristiwa yang dikirim dari node yang berada di bawah hierarki bersarang yang sama, Anda harus menggunakan pengubah [`.window`](https://github.com/alpinejs/alpine#x-on) :
+
+**Contoh:**
+
+```html
+<div x-data>
+    <span @custom-event="console.log($event.detail.foo)"></span>
+    <button @click="$dispatch('custom-event', { foo: 'bar' })">
+<div>
+```
+
+> Ini tidak akan berfungsi karena saat `custom-event` dikirim, itu akan disebarkan ke leluhur yang sama, `div`.
+
+**Pengiriman ke Komponen**
+
+Anda juga dapat memanfaatkan teknik sebelumnya untuk membuat komponen Anda saling berhubungan:
+
+**Contoh:**
+
+```html
+<div x-data @custom-event.window="console.log($event.detail)"></div>
+
+<button x-data @click="$dispatch('custom-event', 'Hello World!')">
+<!-- Saat diklik, akan console.log "Hello World!". -->
+```
+
+`$dispatch` adalah jalan pintas untuk membuat `CustomEvent` dan mengirimkannya menggunakan `.dispatchEvent()` secara internal. Ada banyak kasus penggunaan yang baik untuk meneruskan data di sekitar dan di antara komponen menggunakan peristiwa khusus. [Baca di sini](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events) untuk informasi lebih lanjut tentang sistem `CustomEvent` yang mendasari di browser.
+
+Anda akan melihat bahwa setiap data yang diteruskan sebagai parameter kedua ke `$dispatch('some-event', { some: 'data' })`, tersedia melalui properti "detail" event baru: $ event.detail.some. Melampirkan data peristiwa khusus ke properti .detail adalah praktik standar untuk `CustomEvents` di browser. [Baca di sini](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) untuk info lebih lanjut.
+
+Anda juga dapat menggunakan `$dispatch()` untuk memicu pembaruan data untuk binding `x-model`. Sebagai contoh:
+
+```html
+<div x-data="{ foo: 'bar' }">
+    <span x-model="foo">
+        <button @click="$dispatch('input', 'baz')">
+        <!-- After the button is clicked, `x-model` will catch the bubbling "input" event, and update foo to "baz". -->
+    </span>
+</div>
+```
+
+> Catatan: Properti $dispatch hanya tersedia dalam ekspresi DOM.
+
+Jika Anda perlu mengakses $dispatch di dalam fungsi JavaScript, Anda dapat mengirimkannya secara langsung:
+
+`<button x-on:click="myFunction($dispatch)"></button>`
+
+---
+
+### `$nextTick`
+**Contoh:**
+```html
+<div x-data="{ fruit: 'apple' }">
+    <button
+        x-on:click="
+            fruit = 'pear';
+            $nextTick(() => { console.log($event.target.innerText) });
+        "
+        x-text="fruit"
+    ></button>
+</div>
+```
+
+`$nextTick` adalah properti ajaib yang memungkinkan Anda untuk hanya mengeksekusi ekspresi tertentu SETELAH Alpine melakukan pembaruan DOM reaktifnya. Ini berguna pada saat Anda ingin berinteraksi dengan status DOM SETELAH itu tercermin setiap pembaruan data yang Anda buat.
+
+---
+
+### `$watch`
+**Contoh:**
+```html
+<div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))">
+    <button @click="open = ! open">Toggle Open</button>
+</div>
+```
+
+Anda bisa "menonton" properti komponen dengan metode ajaib $watch. Dalam contoh di atas, ketika tombol diklik dan `buka` diubah, callback yang disediakan akan aktif dan `console.log` nilai baru.
+
+## Keamanan
+Jika Anda menemukan kerentanan keamanan, silakan kirim email ke [calebporzio@gmail.com]()
+
+Alpine mengandalkan implementasi kustom yang menggunakan objek Function untuk mengevaluasi arahannya. Meskipun lebih aman daripada `eval()`, penggunaannya dilarang di beberapa lingkungan, seperti Aplikasi Google Chrome, menggunakan Kebijakan Keamanan Konten yang membatasi.
+
+Jika Anda menggunakan Alpine di situs web yang berurusan dengan data sensitif dan membutuhkan [CSP](https://csp.withgoogle.com/docs/strict-csp.html), Anda perlu menyertakan `unsafe-eval` dalam kebijakan Anda. Kebijakan kuat yang dikonfigurasi dengan benar akan membantu melindungi pengguna Anda saat menggunakan data pribadi atau keuangan.
+
+Karena kebijakan berlaku untuk semua skrip di halaman Anda, penting agar pustaka eksternal lain yang termasuk dalam situs web ditinjau dengan cermat untuk memastikan bahwa mereka dapat dipercaya dan tidak akan menimbulkan kerentanan Cross Site Scripting baik menggunakan fungsi `eval()` atau memanipulasi DOM untuk memasukkan kode berbahaya ke halaman Anda.
+
+## Tujuan selanjutnya di V3
+* Pindah dari `x-ref` ke `ref` untuk paritas Vue?
+* Menambahkan `Alpine.directive()`
+* Menambahkan `Alpine.component('foo', {...})` (Dengan metode sihir `__init()`)
+* Mengirim peristiwa Alpine untuk "loaded", "transition-start", dll ... ([#299](https://github.com/alpinejs/alpine/pull/299)) ?
+* Menghapus "object" (dan array) sintaks dari `x-bind:class="{ 'foo': true }"` ([#236](https://github.com/alpinejs/alpine/pull/236) untuk menambah dukungan untuk sintaks objek untuk atribut `style`)
+* Memperbaiki `x-for` reaktivitas mutasi ([#165](https://github.com/alpinejs/alpine/pull/165))
+* Menambahkan "deep watching" dukungan di V3 ([#294](https://github.com/alpinejs/alpine/pull/294))
+* Menambahkan jalan pintas `$el`
+* Perubahan `@click.away` ke `@click.outside`?
+
+## Lisensi
+
+hak cipta © 2019-2020 Caleb Porzio dan kontributor
+
+Berlisensi di bawah lisensi MIT, lihat [LICENSE.md](LICENSE.md) untuk detailnya.

+ 4 - 4
README.ja.md

@@ -14,12 +14,12 @@ DOM を保持し、適切な動作を施すことができます。
 
 **CDNより:** `<head>` セクションの最後に次のスクリプトを追加します。
 ```html
-<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.0/dist/alpine.js" defer></script>
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.2/dist/alpine.js" defer></script>
 ```
 
 それだけです。初期は自身で行われます。
 
-**NPMより:** NPM からパッケージをインストールします。
+**npmより:** npm からパッケージをインストールします。
 ```js
 npm i alpinejs
 ```
@@ -120,7 +120,7 @@ IE11 では、ポリフィルを提供する必要があります。次のスク
 
 **例:** `<div x-data="{ foo: 'bar' }">...</div>`
 
-**構造:** `<div x-data="[JSON data object]">...</div>`
+**構造:** `<div x-data="[object literal]">...</div>`
 
 `x-data` は新しいコンポーネントスコープを宣言します。フレームワークに、データオブジェクトを使用して新しいコンポーネントを初期化するよう指示します。
 
@@ -365,7 +365,7 @@ Alpine は仮想 DOM を使用しないため、`<template></template>` タグ
 </template>
 ```
 
-Alpine は、「非表示」と「表示」の遷移間のさまざまな段階にクラスを要素に適用するための6つの異なるトランジションディレクティブを提供します。これらのディレクティブは、`x-show` と `x-if` の両方で機能します。
+Alpine は、「非表示」と「表示」の遷移間のさまざまな段階にクラスを要素に適用するための6つの異なるトランジションディレクティブを提供します。これらのディレクティブは、`x-show` と `x-if` の両方で機能します。
 
 これらは、VueJs のトランジションディレクティブとまったく同じように動作しますが、より理にかなった異なる名前を持っています。
 

+ 39 - 19
README.md

@@ -16,10 +16,12 @@ Think of it like [Tailwind](https://tailwindcss.com/) for JavaScript.
 
 | Language | Link for documentation |
 | --- | --- |
-| Japanese | [**日本語ドキュメント**](./README.ja.md) | 
-| Chinese Traditional | [**繁體中文說明文件**](./README.zh-TW.md) | 
-| Russian | [**Документация на русском**](./README.ru.md) | 
+| Chinese Traditional | [**繁體中文說明文件**](./README.zh-TW.md) |
+| German | [**Dokumentation in Deutsch**](./README.de.md) |
+| Indonesian | [**Dokumentasi Bahasa Indonesia**](./README.id.md) |
+| Japanese | [**日本語ドキュメント**](./README.ja.md) |
 | Portuguese | [**Documentação em Português**](./README.pt.md) |
+| Russian | [**Документация на русском**](./README.ru.md) |
 | Spanish | [**Documentación en Español**](./README.es.md) |
 
 ## Install
@@ -32,12 +34,12 @@ Think of it like [Tailwind](https://tailwindcss.com/) for JavaScript.
 That's it. It will initialize itself.
 
 For production environments, it's recommended to pin a specific version number in the link to avoid unexpected breakage from newer versions.
-For example, to use version `2.7.0` (latest):
+For example, to use version `2.7.2` (latest):
 ```html
-<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.0/dist/alpine.min.js" defer></script>
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.2/dist/alpine.min.js" defer></script>
 ```
 
-**From NPM:** Install the package from NPM.
+**From npm:** Install the package from npm.
 ```js
 npm i alpinejs
 ```
@@ -83,7 +85,7 @@ The pattern above is the [module/nomodule pattern](https://philipwalton.com/arti
 ```
 
 You can even use it for non-trivial things:
-*Pre-fetching a dropdown's HTML content on hover*
+*Pre-fetching a dropdown's HTML content on hover.*
 ```html
 <div x-data="{ open: false }">
     <button
@@ -110,7 +112,7 @@ There are 14 directives available to you:
 | [`x-data`](#x-data) | Declares a new component scope. |
 | [`x-init`](#x-init) | Runs an expression when a component is initialized. |
 | [`x-show`](#x-show) | Toggles `display: none;` on the element depending on expression (true or false). |
-| [`x-bind`](#x-bind) | Sets the value of an attribute to the result of a JS expression |
+| [`x-bind`](#x-bind) | Sets the value of an attribute to the result of a JS expression. |
 | [`x-on`](#x-on) | Attaches an event listener to the element. Executes JS expression when emitted. |
 | [`x-model`](#x-model) | Adds "two-way data binding" to an element. Keeps input element in sync with component data. |
 | [`x-text`](#x-text) | Works similarly to `x-bind`, but will update the `innerText` of an element. |
@@ -118,8 +120,8 @@ There are 14 directives available to you:
 | [`x-ref`](#x-ref) | Convenient way to retrieve raw DOM elements out of your component. |
 | [`x-if`](#x-if) | Remove an element completely from the DOM. Needs to be used on a `<template>` tag. |
 | [`x-for`](#x-for) | Create new DOM nodes for each item in an array. Needs to be used on a `<template>` tag. |
-| [`x-transition`](#x-transition) | Directives for applying classes to various stages of an element's transition |
-| [`x-spread`](#x-spread) | Allows you to bind an object of Alpine directives to an element for better reusability |
+| [`x-transition`](#x-transition) | Directives for applying classes to various stages of an element's transition. |
+| [`x-spread`](#x-spread) | Allows you to bind an object of Alpine directives to an element for better reusability. |
 | [`x-cloak`](#x-cloak) | This attribute is removed when Alpine initializes. Useful for hiding pre-initialized DOM. |
 
 And 6 magic properties:
@@ -156,7 +158,7 @@ And 6 magic properties:
 
 **Example:** `<div x-data="{ foo: 'bar' }">...</div>`
 
-**Structure:** `<div x-data="[JSON data object]">...</div>`
+**Structure:** `<div x-data="[object literal]">...</div>`
 
 `x-data` declares a new component scope. It tells the framework to initialize a new component with the following data object.
 
@@ -252,7 +254,7 @@ If you wish to run code AFTER Alpine has made its initial updates to the DOM (so
 
 ### `x-bind`
 
-> Note: You are free to use the shorter ":" syntax: `:type="..."`
+> Note: You are free to use the shorter ":" syntax: `:type="..."`.
 
 **Example:** `<input x-bind:type="inputType">`
 
@@ -293,6 +295,8 @@ This will add or remove the `disabled` attribute when `myVar` is true or false r
 
 Boolean attributes are supported as per the [HTML specification](https://html.spec.whatwg.org/multipage/indices.html#attributes-3:boolean-attribute), for example `disabled`, `readonly`, `required`, `checked`, `hidden`, `selected`, `open`, etc.
 
+> Note: If you need a false state to show for your attribute, such as `aria-*`, chain `.toString()` to the value while binding to the attribute. For example: `:aria-expanded="isOpen.toString()"` would persist whether  `isOpen` was `true` or `false`.
+
 **`.camel` modifier**
 **Example:** `<svg x-bind:view-box.camel="viewBox">`
 
@@ -302,7 +306,7 @@ The `camel` modifier will bind to the camel case equivalent of the attribute nam
 
 ### `x-on`
 
-> Note: You are free to use the shorter "@" syntax: `@click="..."`
+> Note: You are free to use the shorter "@" syntax: `@click="..."`.
 
 **Example:** `<button x-on:click="foo = 'bar'"></button>`
 
@@ -312,7 +316,7 @@ The `camel` modifier will bind to the camel case equivalent of the attribute nam
 
 If any data is modified in the expression, other element attributes "bound" to this data, will be updated.
 
-> Note: You can also specify a JavaScript function name
+> Note: You can also specify a JavaScript function name.
 
 **Example:** `<button x-on:click="myFunction"></button>`
 
@@ -375,7 +379,7 @@ The `debounce` modifier allows you to "debounce" an event handler. In other word
 
 The default debounce "wait" time is 250 milliseconds.
 
-If you wish to customize this, you can specifiy a custom wait time like so:
+If you wish to customize this, you can specify a custom wait time like so:
 
 ```
 <input x-on:input.debounce.750="fetchSomething()">
@@ -490,6 +494,20 @@ If you want to access the current index of the iteration, use the following synt
 </template>
 ```
 
+If you want to access the array object (collection) of the iteration, use the following syntax:
+
+```html
+<template x-for="(item, index, collection) in items" :key="index">
+    <!-- You can also reference "collection" inside the iteration if you need. -->
+    <!-- Current item. -->
+    <div x-text="item"></div>
+    <!-- Same as above. -->
+    <div x-text="collection[index]"></div>
+    <!-- Previous item. -->
+    <div x-text="collection[index - 1]"></div>
+</template>
+```
+
 > Note: `x-for` must have a single element root inside of the `<template></template>` tag.
 
 > Note: When using `template` in a `svg` tag, you need to add a [polyfill](https://github.com/alpinejs/alpine/issues/637#issuecomment-654856538) that should be run before Alpine.js is initialized.
@@ -546,11 +564,11 @@ Alpine supports the `i in n` syntax, where `n` is an integer, allowing you to it
 </template>
 ```
 
-> The example above uses classes from [Tailwind CSS](https://tailwindcss.com)
+> The example above uses classes from [Tailwind CSS](https://tailwindcss.com).
 
 Alpine offers 6 different transition directives for applying classes to various stages of an element's transition between "hidden" and "shown" states. These directives work both with `x-show` AND `x-if`.
 
-These behave exactly like VueJs's transition directives, except they have different, more sensible names:
+These behave exactly like VueJS's transition directives, except they have different, more sensible names:
 
 | Directive | Description |
 | --- | --- |
@@ -598,7 +616,9 @@ These behave exactly like VueJs's transition directives, except they have differ
 
 The object keys are the directives (Can be any directive including modifiers), and the values are callbacks to be evaluated by Alpine.
 
-> Note: The only anomaly with x-spread is when used with `x-for`. When the directive being "spread" is `x-for`, you should return a normal expression string from the callback. For example: `['x-for']() { return 'item in items' }`.
+> Note: There are a couple of caveats to x-spread:
+> - When the directive being "spread" is `x-for`, you should return a normal expression string from the callback. For example: `['x-for']() { return 'item in items' }`.
+> - `x-data` and `x-init` can't be used inside a "spread" object.
 
 ---
 
@@ -746,7 +766,7 @@ If you need to access $dispatch inside of a JavaScript function you can pass it
 You can "watch" a component property with the `$watch` magic method. In the above example, when the button is clicked and `open` is changed, the provided callback will fire and `console.log` the new value.
 
 ## Security
-If you find a security vulnerability, please send an email to [calebporzio@gmail.com]()
+If you find a security vulnerability, please send an email to [calebporzio@gmail.com]().
 
 Alpine relies on a custom implementation using the `Function` object to evaluate its directives. Despite being more secure then `eval()`, its use is prohibited in some environments, such as Google Chrome App, using restrictive Content Security Policy (CSP).
 

+ 9 - 9
README.pt.md

@@ -23,13 +23,13 @@ Pensem nisso como o [Tailwind](https://tailwindcss.com/) para JavaScript.
 E é isso. Ele vai se inicializar.
 
 Para ambiente de produção, é recomendado fixar o número da versão específico no link para evitar problemas inesperadas das versões mais recentes.
-Por exemplo, para usar a versão `2.7.0`:
+Por exemplo, para usar a versão `2.7.2`:
 
 ```html
-<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.0/dist/alpine.min.js" defer></script>
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.2/dist/alpine.min.js" defer></script>
 ```
 
-**Via NPM:** Instale o pacote pelo NPM.
+**Via npm:** Instale o pacote pelo npm.
 
 ```js
 npm i alpinejs
@@ -81,7 +81,7 @@ _Tabs_
 ```
 
 Podemos até usá-lo para coisas não triviais:
-_Pré pedido de conteudo para o HTML da dropdown ao passar com o rato_
+_Pré pedido de conteudo para o HTML da dropdown ao passar com o rato_.
 
 ```html
 <div x-data="{ open: false }">
@@ -159,7 +159,7 @@ E 6 propriedades mágicas:
 
 **Exemplo:** `<div x-data="{ foo: 'bar' }">...</div>`
 
-**Estrutura:** `<div x-data="[JSON data object]">...</div>`
+**Estrutura:** `<div x-data="[object literal]">...</div>`
 
 `x-data` declara um novo scope do componente. Diz à estrutura para inicializar um novo componente com o seguinte objeto de dados.
 
@@ -338,7 +338,7 @@ Podemos especificar chaves específicas para escutar usando modificadores de key
 
 Exemplos: `enter`, `escape`, `arrow-up`, `arrow-down`
 
-> Nota: Também podemos ouvir a combinações de teclas do sistema como: `x-on:keydown.cmd.enter="foo"`
+> Nota: Também podemos ouvir a combinações de teclas do sistema como: `x-on:keydown.cmd.enter="foo"`.
 
 **`.away` modificador**
 
@@ -397,7 +397,7 @@ Caso desejem personalizar isso, pode especificar um tempo de espera personalizad
 **`.camel` modificador**
 **Exemplo:** `<input x-on:event-name.camel="doSomething()">`
 
-O modificador `camel` anexa um evento de escuta ao nome em camel case do evento equivalente. No exemplo acima, a expressão é avaliada quando o evento `eventName` for disparado no elemento
+O modificador `camel` anexa um evento de escuta ao nome em camel case do evento equivalente. No exemplo acima, a expressão é avaliada quando o evento `eventName` for disparado no elemento.
 
 ---
 
@@ -552,7 +552,7 @@ Podemos ter encadeamento de ciclos `x-for`, mas DEVEMOS envolver cada ciclo em u
 </template>
 ```
 
-> O exemplo acima usa classes de [Tailwind CSS](https://tailwindcss.com)
+> O exemplo acima usa classes de [Tailwind CSS](https://tailwindcss.com).
 
 Alpine oferece 6 diretivas de transição diferentes para aplicar classes a vários estágios da transição de um elemento entre os estados "oculto" e "mostrado". Essas diretivas funcionam tanto com `x-show` E`x-if`.
 
@@ -774,7 +774,7 @@ Podemos "assistir" uma propriedade de componente com o método mágico `$watch`.
 
 ## Segurança
 
-Caso encontrarem uma vulnerabilidade de segurança, envie um email para [calebporzio@gmail.com](mailto:calebporzio@gmail.com)
+Caso encontrarem uma vulnerabilidade de segurança, envie um email para [calebporzio@gmail.com](mailto:calebporzio@gmail.com).
 
 O Alpine conta com uma implementação personalizada usando o objeto `Function` para avaliar suas diretivas. Apesar de ser mais seguro que o `eval()`, o seu uso é proibido em alguns ambientes, como o Google Chrome App, usando a Política de Segurança de Conteúdo restritiva (CSP).
 

+ 10 - 10
README.ru.md

@@ -21,13 +21,13 @@ Alpine.js предлагает вам реактивность и деклара
 
 Вот и всё. Он инициализируется самостоятельно.
 
-Для продакшн-окружения, рекомедуется использовать ссылку с конкретным номером версии, чтобы избежать неожиданных поломок после выпуска новых версий.
-Например, чтобы использовать версию `2.7.0`:
+Для продакшн-окружения, рекомендуется использовать ссылку с конкретным номером версии, чтобы избежать неожиданных поломок после выпуска новых версий.
+Например, чтобы использовать версию `2.7.2`:
 ```html
-<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.0/dist/alpine.min.js" defer></script>
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.2/dist/alpine.min.js" defer></script>
 ```
 
-**С помощью NPM:** Установите пакет из NPM.
+**С помощью npm:** Установите пакет из npm.
 ```js
 npm i alpinejs
 ```
@@ -100,7 +100,7 @@ Alpine.js можно использовать и для более серьез
 | [`x-data`](#x-data) | Объявляет новый компонент и его данные. |
 | [`x-init`](#x-init) | Выполняет переданное выражение, когда компонент инициализируется. |
 | [`x-show`](#x-show) | Переключает `display: none;` на элементе, в зависимости от результата переданного выражения (true или false). |
-| [`x-bind`](#x-bind) | Устанавливает значение атрибута равным результату переданного JS-выражения |
+| [`x-bind`](#x-bind) | Устанавливает значение атрибута равным результату переданного JS-выражения. |
 | [`x-on`](#x-on) | Устанавливает обработчик события на элемент. Когда событие срабатывает, выполняет переданное JS-выражение. |
 | [`x-model`](#x-model) | Добавляет "двустороннюю привязку данных" (two-way data binding) на элемент. Синхронизирует элемент и данные компонента. |
 | [`x-text`](#x-text) | Устанавливает значение `innerText` элемента равным результату переданного JS-выражения. |
@@ -108,7 +108,7 @@ Alpine.js можно использовать и для более серьез
 | [`x-ref`](#x-ref) | Удобный способ получения DOM-элементов вашего компонента. |
 | [`x-if`](#x-if) | При невыполнении переданного условия полностью удаляет элемент из DOM. Должна использоваться в теге `<template>`. |
 | [`x-for`](#x-for) | Создает новые DOM узлы для каждого элемента в массиве. Должна использоваться в теге `<template>`. |
-| [`x-transition`](#x-transition) | Директивы для добавления классов различным стадиям перехода (transition) элемента |
+| [`x-transition`](#x-transition) | Директивы для добавления классов различным стадиям перехода (transition) элемента. |
 | [`x-spread`](#x-spread) | Позволяет вам привязывать объект с директивами Alpine к элементам, улучшая переиспользуемость. |
 | [`x-cloak`](#x-cloak) | Удаляется при инициализации Alpine. Полезна для скрытия DOM до инициализации. |
 
@@ -297,7 +297,7 @@ Alpine.js можно использовать и для более серьез
 
 **Синтаксис:** `<button x-on:[событие]="[выражение]"></button>`
 
-`x-on` цепляет прослушиватель события на элемент, на котором был объявлен. Когда событие срабатывает, выполняется переданное JS-выражение.
+`x-on` цепляет слушатель события на элемент, на котором был объявлен. Когда событие срабатывает, выполняется переданное JS-выражение.
 
 Если в этом выражении меняются какие-либо данные, другие элементы, "привязанные" к этим данным, будут обновлены.
 
@@ -349,7 +349,7 @@ Alpine.js можно использовать и для более серьез
 
 При добавлении `.window` прослушиватель события установится не на узел DOM, на котором был вызван, а на глобальный объект window. Это полезно, когда нужно изменить состояние компонента при изменении чего-либо в window, например, при событии "resize". В примере выше, когда ширина окна будет больше 768 пикселей, мы закроем модальное окно/дропдаун, иначе сохраним то же состояние.
 
->Замечание: Также можно использовать модификатор `.document` для добавления прослушивателей к `document`.
+>Замечание: Также можно использовать модификатор `.document` для добавления слушателей к `document`.
 
 **Модификатор `.once`**
 
@@ -620,7 +620,7 @@ Alpine предлагает 6 разных transition-директив для д
 <input x-on:input="alert($event.target.value)">
 ```
 
-`$event` – это магическое свойство, которое можно использовать в прослушивателе событий для получения нативного объекта "Event".
+`$event` – это магическое свойство, которое можно использовать в слушателе событий для получения нативного объекта "Event".
 
 ---
 
@@ -713,7 +713,7 @@ Alpine предлагает 6 разных transition-директив для д
 Магический метод `$watch` позволяет следить за выбранным свойством компонента. В примере выше при нажатии на кнопку: 1) значение `open` изменится; 2) выполнится переданный в `$watch` колбэк; 3) в консоль выведется новое значение.
 
 ## Безопасность
-Если вы нашли уязвимость, пожалуйста, отправьте письмо на [calebporzio@gmail.com]()
+Если вы нашли уязвимость, пожалуйста, отправьте письмо на [calebporzio@gmail.com]().
 
 Alpine полагается на собственную реализацию, которая использует объект `Function` для оценки своих директив. Несмотря на то, что он безопаснее, чем `eval()`, его использование запрещено в некоторых средах, таких как Google Chrome App, т.е. использующих Политику защиты контента (CSP).
 

+ 6 - 6
README.zh-TW.md

@@ -22,12 +22,12 @@ Alpine.js 提供了 Vue 與 React 等大框架的互動式與宣告式的功能
 就這樣。Alpine.js 會自行初始化。
 
 在正式環境中,建議在連結中固定特定版本,以避免新版本使功能無法使用。
-如,要使用 `2.7.0` 版則可以這樣寫:
+如,要使用 `2.7.2` 版則可以這樣寫:
 ```html
-<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.0/dist/alpine.min.js" defer></script>
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.7.2/dist/alpine.min.js" defer></script>
 ```
 
-**使用 NPM:** 從 NPM 安裝套件。
+**使用 npm:** 從 npm 安裝套件。
 ```js
 npm i alpinejs
 ```
@@ -109,8 +109,8 @@ import 'alpinejs'
 | [`x-ref`](#x-ref) |從元素中取得原始 DOM 元素的簡便方法。 |
 | [`x-if`](#x-if) | 從 DOM 中完全移除元素。必須在 `<template>` 標籤上使用。 |
 | [`x-for`](#x-for) | 為陣列中的每個項目建立新 DOM 節點。必須在 `<template>` 標籤上使用。 |
-| [`x-transition`](#x-transition) | 用於在轉場的各個階段為元素設定 Class 的指示詞 |
-| [`x-spread`](#x-spread) | 為了更佳的可複用性,可將包含 Alpine 指示詞的物件繫結至元素上 |
+| [`x-transition`](#x-transition) | 用於在轉場的各個階段為元素設定 Class 的指示詞 |
+| [`x-spread`](#x-spread) | 為了更佳的可複用性,可將包含 Alpine 指示詞的物件繫結至元素上 |
 | [`x-cloak`](#x-cloak) | 該屬性會在 Alpine 初始化後移除。適合用來隱藏還未初始化的 DOM。 |
 
 以及 6 個魔法屬性:
@@ -734,7 +734,7 @@ Alpine 中提供了 6 中不同的變換指示詞,可用於在元素變換的
 可通過 `$watch` 魔法方法來「監聽 (Watch)」元件屬性。在上述例子中,當按鈕點擊後 `open` 會該表,接著會指定給定的回呼並以新的值來執行 `console.log`。
 
 ## 安全性 Security
-若你發現安全性漏洞,請傳送電子郵件至 [calebporzio@gmail.com]()
+若你發現安全性漏洞,請傳送電子郵件至 [calebporzio@gmail.com]()
 
 Alpine 仰賴與使用 `Function` 物件來自定實作以對指示詞取值。雖然比 `eval()` 來的安全,但這個做法依然在一些環境下被禁止,如 Google Chrome App 使用了限制性的 CSP (Content Security Policy,內容安全性原則)。
 

+ 72 - 72
dist/alpine-ie11.js

@@ -5299,6 +5299,8 @@
     }
   }
 
+  var _this10 = undefined;
+
   // Thanks @stimulus:
   // https://github.com/stimulusjs/stimulus/blob/master/packages/%40stimulus/core/src/application.ts
   function domReady() {
@@ -5320,6 +5322,9 @@
   function isTesting() {
     return navigator.userAgent.includes("Node.js") || navigator.userAgent.includes("jsdom");
   }
+  function checkedAttrLooseCompare(valueA, valueB) {
+    return valueA == valueB;
+  }
   function warnIfMalformedTemplate(el, directive) {
     if (el.tagName.toLowerCase() !== 'template') {
       console.warn("Alpine: [".concat(directive, "] directive should only be added to <template> tags. See https://github.com/alpinejs/alpine#").concat(directive));
@@ -5688,66 +5693,67 @@
     };
     transition(el, stages, type);
   }
-  function transitionClassesIn(el, component, directives, showCallback) {
-    var _this10 = this;
 
-    var ensureStringExpression = function ensureStringExpression(expression) {
-      _newArrowCheck(this, _this10);
+  var ensureStringExpression = function ensureStringExpression(expression, el, component) {
+    _newArrowCheck(this, _this10);
 
-      return typeof expression === 'function' ? component.evaluateReturnExpression(el, expression) : expression;
-    }.bind(this);
+    return typeof expression === 'function' ? component.evaluateReturnExpression(el, expression) : expression;
+  }.bind(undefined);
+
+  function transitionClassesIn(el, component, directives, showCallback) {
+    var _this11 = this;
 
     var enter = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
-      _newArrowCheck(this, _this10);
+      _newArrowCheck(this, _this11);
 
       return i.value === 'enter';
     }.bind(this)) || {
       expression: ''
-    }).expression));
+    }).expression, el, component));
     var enterStart = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
-      _newArrowCheck(this, _this10);
+      _newArrowCheck(this, _this11);
 
       return i.value === 'enter-start';
     }.bind(this)) || {
       expression: ''
-    }).expression));
+    }).expression, el, component));
     var enterEnd = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
-      _newArrowCheck(this, _this10);
+      _newArrowCheck(this, _this11);
 
       return i.value === 'enter-end';
     }.bind(this)) || {
       expression: ''
-    }).expression));
+    }).expression, el, component));
     transitionClasses(el, enter, enterStart, enterEnd, showCallback, function () {
-      _newArrowCheck(this, _this10);
+      _newArrowCheck(this, _this11);
     }.bind(this), TRANSITION_TYPE_IN);
   }
   function transitionClassesOut(el, component, directives, hideCallback) {
-    var _this11 = this;
+    var _this12 = this;
 
-    var leave = convertClassStringToArray((directives.find(function (i) {
-      _newArrowCheck(this, _this11);
+    var leave = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
+      _newArrowCheck(this, _this12);
 
       return i.value === 'leave';
     }.bind(this)) || {
       expression: ''
-    }).expression);
-    var leaveStart = convertClassStringToArray((directives.find(function (i) {
-      _newArrowCheck(this, _this11);
+    }).expression, el, component));
+    var leaveStart = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
+      _newArrowCheck(this, _this12);
 
       return i.value === 'leave-start';
     }.bind(this)) || {
       expression: ''
-    }).expression);
-    var leaveEnd = convertClassStringToArray((directives.find(function (i) {
-      _newArrowCheck(this, _this11);
+    }).expression, el, component));
+    var leaveEnd = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
+      _newArrowCheck(this, _this12);
 
       return i.value === 'leave-end';
     }.bind(this)) || {
       expression: ''
-    }).expression);
+    }).expression, el, component));
     transitionClasses(el, leave, leaveStart, leaveEnd, function () {
-      _newArrowCheck(this, _this11);
+      _newArrowCheck(this, _this12);
     }.bind(this), hideCallback, TRANSITION_TYPE_OUT);
   }
   function transitionClasses(el, classesDuring, classesStart, classesEnd, hook1, hook2, type) {
@@ -5774,12 +5780,12 @@
       },
       end: function end() {
         var _el$classList3,
-            _this12 = this,
+            _this13 = this,
             _el$classList4;
 
         // Don't remove classes that were in the original class attribute.
         (_el$classList3 = el.classList).remove.apply(_el$classList3, _toConsumableArray(classesStart.filter(function (i) {
-          _newArrowCheck(this, _this12);
+          _newArrowCheck(this, _this13);
 
           return !originalClasses.includes(i);
         }.bind(this))));
@@ -5791,17 +5797,17 @@
       },
       cleanup: function cleanup() {
         var _el$classList5,
-            _this13 = this,
+            _this14 = this,
             _el$classList6;
 
         (_el$classList5 = el.classList).remove.apply(_el$classList5, _toConsumableArray(classesDuring.filter(function (i) {
-          _newArrowCheck(this, _this13);
+          _newArrowCheck(this, _this14);
 
           return !originalClasses.includes(i);
         }.bind(this))));
 
         (_el$classList6 = el.classList).remove.apply(_el$classList6, _toConsumableArray(classesEnd.filter(function (i) {
-          _newArrowCheck(this, _this13);
+          _newArrowCheck(this, _this14);
 
           return !originalClasses.includes(i);
         }.bind(this))));
@@ -5810,7 +5816,7 @@
     transition(el, stages, type);
   }
   function transition(el, stages, type) {
-    var _this14 = this;
+    var _this15 = this;
 
     el.__x_transition = {
       // Set transition type so we can avoid clearing transition if the direction is the same
@@ -5819,7 +5825,7 @@
       // from different point and early terminate it. Once will ensure that function
       // is only called one time.
       callback: once(function () {
-        _newArrowCheck(this, _this14);
+        _newArrowCheck(this, _this15);
 
         stages.hide(); // Adding an "isConnected" check, in case the callback
         // removed the element from the DOM.
@@ -5836,9 +5842,9 @@
     stages.start();
     stages.during();
     el.__x_transition.nextFrame = requestAnimationFrame(function () {
-      var _this15 = this;
+      var _this16 = this;
 
-      _newArrowCheck(this, _this14);
+      _newArrowCheck(this, _this15);
 
       // Note: Safari's transitionDuration property will list out comma separated transition durations
       // for every single transition property. Let's grab the first one and call it a day.
@@ -5850,7 +5856,7 @@
 
       stages.show();
       el.__x_transition.nextFrame = requestAnimationFrame(function () {
-        _newArrowCheck(this, _this15);
+        _newArrowCheck(this, _this16);
 
         stages.end();
         setTimeout(el.__x_transition.callback, duration);
@@ -5858,7 +5864,7 @@
     }.bind(this));
   }
   function isNumeric(subject) {
-    return !isNaN(subject);
+    return !Array.isArray(subject) && !isNaN(subject);
   } // Thanks @vuejs
   // https://github.com/vuejs/vue/blob/4de4649d9637262a9b007720b59f80ac72a5620c/src/shared/util.js
 
@@ -5976,18 +5982,19 @@
 
     if (ifAttribute && !component.evaluateReturnExpression(el, ifAttribute.expression)) {
       return [];
-    } // This adds support for the `i in n` syntax.
+    }
 
+    var items = component.evaluateReturnExpression(el, iteratorNames.items, extraVars); // This adds support for the `i in n` syntax.
 
-    if (isNumeric(iteratorNames.items)) {
-      return Array.from(Array(parseInt(iteratorNames.items, 10)).keys(), function (i) {
+    if (isNumeric(items) && items > 0) {
+      items = Array.from(Array(items).keys(), function (i) {
         _newArrowCheck(this, _this4);
 
         return i + 1;
       }.bind(this));
     }
 
-    return component.evaluateReturnExpression(el, iteratorNames.items, extraVars);
+    return items;
   }
 
   function addElementInLoopAfterCurrentEl(templateEl, currentEl) {
@@ -6055,14 +6062,14 @@
         if (el.attributes.value === undefined && attrType === 'bind') {
           el.value = value;
         } else if (attrType !== 'bind') {
-          el.checked = el.value == value;
+          el.checked = checkedAttrLooseCompare(el.value, value);
         }
       } else if (el.type === 'checkbox') {
         // If we are explicitly binding a string to the :value, set the string,
         // If the value is a boolean, leave it alone, it will be set to "on"
         // automatically.
-        if (typeof value === 'string' && attrType === 'bind') {
-          el.value = value;
+        if (typeof value !== 'boolean' && ![null, undefined].includes(value) && attrType === 'bind') {
+          el.value = String(value);
         } else if (attrType !== 'bind') {
           if (Array.isArray(value)) {
             // I'm purposely not using Array.includes here because it's
@@ -6071,7 +6078,7 @@
             el.checked = value.some(function (val) {
               _newArrowCheck(this, _this);
 
-              return val == el.value;
+              return checkedAttrLooseCompare(val, el.value);
             }.bind(this));
           } else {
             el.checked = !!value;
@@ -6544,10 +6551,10 @@
         // If the data we are binding to is an array, toggle its value inside the array.
         if (Array.isArray(currentValue)) {
           var newValue = modifiers.includes('number') ? safeParseNumber(event.target.value) : event.target.value;
-          return event.target.checked ? currentValue.concat([newValue]) : currentValue.filter(function (i) {
+          return event.target.checked ? currentValue.concat([newValue]) : currentValue.filter(function (el) {
             _newArrowCheck(this, _this3);
 
-            return i !== newValue;
+            return !checkedAttrLooseCompare(el, newValue);
           }.bind(this));
         } else {
           return event.target.checked;
@@ -6711,11 +6718,22 @@
       this.unobservedData.$el = null;
       this.unobservedData.$refs = null;
       this.unobservedData.$nextTick = null;
-      this.unobservedData.$watch = null;
-      Object.keys(Alpine.magicProperties).forEach(function (name) {
+      this.unobservedData.$watch = null; // The IE build uses a proxy polyfill which doesn't allow properties
+      // to be defined after the proxy object is created so,
+      // for IE only, we need to define our helpers earlier.
+
+      Object.entries(Alpine.magicProperties).forEach(function (_ref3) {
         _newArrowCheck(this, _this);
 
-        this.unobservedData["$".concat(name)] = null;
+        var _ref4 = _slicedToArray(_ref3, 2),
+            name = _ref4[0],
+            callback = _ref4[1];
+
+        Object.defineProperty(this.unobservedData, "$".concat(name), {
+          get: function get() {
+            return callback(canonicalComponentElementReference);
+          }
+        });
       }.bind(this));
       /* IE11-ONLY:END */
       // Construct a Proxy-based observable. This will be used to handle reactivity.
@@ -6745,22 +6763,8 @@
 
         if (!this.watchers[property]) this.watchers[property] = [];
         this.watchers[property].push(callback);
-      }.bind(this); // Register custom magic properties.
-
-
-      Object.entries(Alpine.magicProperties).forEach(function (_ref3) {
-        _newArrowCheck(this, _this);
-
-        var _ref4 = _slicedToArray(_ref3, 2),
-            name = _ref4[0],
-            callback = _ref4[1];
+      }.bind(this);
 
-        Object.defineProperty(this.unobservedData, "$".concat(name), {
-          get: function get() {
-            return callback(canonicalComponentElementReference);
-          }
-        });
-      }.bind(this));
       this.showDirectiveStack = [];
       this.showDirectiveLastElement;
       componentForClone || Alpine.onBeforeComponentInitializeds.forEach(function (callback) {
@@ -6855,7 +6859,7 @@
                 }
 
                 return comparisonData[part];
-              }.bind(this), self.getUnobservedData());
+              }.bind(this), self.unobservedData);
             }.bind(this));
           } else {
             // Let's walk through the watchers with "dot-notation" (foo.bar) and see
@@ -6890,7 +6894,7 @@
                 }
 
                 return comparisonData[part];
-              }.bind(this), self.getUnobservedData());
+              }.bind(this), self.unobservedData);
             }.bind(this));
           } // Don't react to data changes for cases like the `x-created` hook.
 
@@ -7284,7 +7288,7 @@
   }();
 
   var Alpine = {
-    version: "2.7.0",
+    version: "2.7.2",
     pauseMutationObserver: false,
     magicProperties: {},
     onComponentInitializeds: [],
@@ -7325,11 +7329,7 @@
                     this.initializeComponent(el);
                   }.bind(this));
                 }.bind(this));
-                this.listenForNewUninitializedComponentsAtRunTime(function (el) {
-                  _newArrowCheck(this, _this);
-
-                  this.initializeComponent(el);
-                }.bind(this));
+                this.listenForNewUninitializedComponentsAtRunTime();
 
               case 6:
               case "end":
@@ -7370,7 +7370,7 @@
         callback(rootEl);
       }.bind(this));
     },
-    listenForNewUninitializedComponentsAtRunTime: function listenForNewUninitializedComponentsAtRunTime(callback) {
+    listenForNewUninitializedComponentsAtRunTime: function listenForNewUninitializedComponentsAtRunTime() {
       var _this5 = this;
 
       var targetNode = document.querySelector('body');

+ 40 - 31
dist/alpine.js

@@ -70,6 +70,9 @@
   function isTesting() {
     return navigator.userAgent.includes("Node.js") || navigator.userAgent.includes("jsdom");
   }
+  function checkedAttrLooseCompare(valueA, valueB) {
+    return valueA == valueB;
+  }
   function warnIfMalformedTemplate(el, directive) {
     if (el.tagName.toLowerCase() !== 'template') {
       console.warn(`Alpine: [${directive}] directive should only be added to <template> tags. See https://github.com/alpinejs/alpine#${directive}`);
@@ -381,32 +384,33 @@
     };
     transition(el, stages, type);
   }
-  function transitionClassesIn(el, component, directives, showCallback) {
-    let ensureStringExpression = expression => {
-      return typeof expression === 'function' ? component.evaluateReturnExpression(el, expression) : expression;
-    };
 
+  const ensureStringExpression = (expression, el, component) => {
+    return typeof expression === 'function' ? component.evaluateReturnExpression(el, expression) : expression;
+  };
+
+  function transitionClassesIn(el, component, directives, showCallback) {
     const enter = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'enter') || {
       expression: ''
-    }).expression));
+    }).expression, el, component));
     const enterStart = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'enter-start') || {
       expression: ''
-    }).expression));
+    }).expression, el, component));
     const enterEnd = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'enter-end') || {
       expression: ''
-    }).expression));
+    }).expression, el, component));
     transitionClasses(el, enter, enterStart, enterEnd, showCallback, () => {}, TRANSITION_TYPE_IN);
   }
   function transitionClassesOut(el, component, directives, hideCallback) {
-    const leave = convertClassStringToArray((directives.find(i => i.value === 'leave') || {
+    const leave = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'leave') || {
       expression: ''
-    }).expression);
-    const leaveStart = convertClassStringToArray((directives.find(i => i.value === 'leave-start') || {
+    }).expression, el, component));
+    const leaveStart = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'leave-start') || {
       expression: ''
-    }).expression);
-    const leaveEnd = convertClassStringToArray((directives.find(i => i.value === 'leave-end') || {
+    }).expression, el, component));
+    const leaveEnd = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'leave-end') || {
       expression: ''
-    }).expression);
+    }).expression, el, component));
     transitionClasses(el, leave, leaveStart, leaveEnd, () => {}, hideCallback, TRANSITION_TYPE_OUT);
   }
   function transitionClasses(el, classesDuring, classesStart, classesEnd, hook1, hook2, type) {
@@ -487,7 +491,7 @@
     });
   }
   function isNumeric(subject) {
-    return !isNaN(subject);
+    return !Array.isArray(subject) && !isNaN(subject);
   } // Thanks @vuejs
   // https://github.com/vuejs/vue/blob/4de4649d9637262a9b007720b59f80ac72a5620c/src/shared/util.js
 
@@ -577,14 +581,15 @@
 
     if (ifAttribute && !component.evaluateReturnExpression(el, ifAttribute.expression)) {
       return [];
-    } // This adds support for the `i in n` syntax.
+    }
 
+    let items = component.evaluateReturnExpression(el, iteratorNames.items, extraVars); // This adds support for the `i in n` syntax.
 
-    if (isNumeric(iteratorNames.items)) {
-      return Array.from(Array(parseInt(iteratorNames.items, 10)).keys(), i => i + 1);
+    if (isNumeric(items) && items > 0) {
+      items = Array.from(Array(items).keys(), i => i + 1);
     }
 
-    return component.evaluateReturnExpression(el, iteratorNames.items, extraVars);
+    return items;
   }
 
   function addElementInLoopAfterCurrentEl(templateEl, currentEl) {
@@ -642,20 +647,20 @@
         if (el.attributes.value === undefined && attrType === 'bind') {
           el.value = value;
         } else if (attrType !== 'bind') {
-          el.checked = el.value == value;
+          el.checked = checkedAttrLooseCompare(el.value, value);
         }
       } else if (el.type === 'checkbox') {
         // If we are explicitly binding a string to the :value, set the string,
         // If the value is a boolean, leave it alone, it will be set to "on"
         // automatically.
-        if (typeof value === 'string' && attrType === 'bind') {
-          el.value = value;
+        if (typeof value !== 'boolean' && ![null, undefined].includes(value) && attrType === 'bind') {
+          el.value = String(value);
         } else if (attrType !== 'bind') {
           if (Array.isArray(value)) {
             // I'm purposely not using Array.includes here because it's
             // strict, and because of Numeric/String mis-casting, I
             // want the "includes" to be "fuzzy".
-            el.checked = value.some(val => val == el.value);
+            el.checked = value.some(val => checkedAttrLooseCompare(val, el.value));
           } else {
             el.checked = !!value;
           }
@@ -971,7 +976,7 @@
         // If the data we are binding to is an array, toggle its value inside the array.
         if (Array.isArray(currentValue)) {
           const newValue = modifiers.includes('number') ? safeParseNumber(event.target.value) : event.target.value;
-          return event.target.checked ? currentValue.concat([newValue]) : currentValue.filter(i => i !== newValue);
+          return event.target.checked ? currentValue.concat([newValue]) : currentValue.filter(el => !checkedAttrLooseCompare(el, newValue));
         } else {
           return event.target.checked;
         }
@@ -1434,7 +1439,11 @@
       this.unobservedData.$watch = (property, callback) => {
         if (!this.watchers[property]) this.watchers[property] = [];
         this.watchers[property].push(callback);
-      }; // Register custom magic properties.
+      };
+      /* MODERN-ONLY:START */
+      // We remove this piece of code from the legacy build.
+      // In IE11, we have already defined our helpers at this point.
+      // Register custom magic properties.
 
 
       Object.entries(Alpine.magicProperties).forEach(([name, callback]) => {
@@ -1444,6 +1453,8 @@
           }
         });
       });
+      /* MODERN-ONLY:END */
+
       this.showDirectiveStack = [];
       this.showDirectiveLastElement;
       componentForClone || Alpine.onBeforeComponentInitializeds.forEach(callback => callback(this));
@@ -1501,7 +1512,7 @@
               }
 
               return comparisonData[part];
-            }, self.getUnobservedData());
+            }, self.unobservedData);
           });
         } else {
           // Let's walk through the watchers with "dot-notation" (foo.bar) and see
@@ -1520,7 +1531,7 @@
               }
 
               return comparisonData[part];
-            }, self.getUnobservedData());
+            }, self.unobservedData);
           });
         } // Don't react to data changes for cases like the `x-created` hook.
 
@@ -1782,7 +1793,7 @@
   }
 
   const Alpine = {
-    version: "2.7.0",
+    version: "2.7.2",
     pauseMutationObserver: false,
     magicProperties: {},
     onComponentInitializeds: [],
@@ -1803,9 +1814,7 @@
           this.initializeComponent(el);
         });
       });
-      this.listenForNewUninitializedComponentsAtRunTime(el => {
-        this.initializeComponent(el);
-      });
+      this.listenForNewUninitializedComponentsAtRunTime();
     },
     discoverComponents: function discoverComponents(callback) {
       const rootEls = document.querySelectorAll('[x-data]');
@@ -1819,7 +1828,7 @@
         callback(rootEl);
       });
     },
-    listenForNewUninitializedComponentsAtRunTime: function listenForNewUninitializedComponentsAtRunTime(callback) {
+    listenForNewUninitializedComponentsAtRunTime: function listenForNewUninitializedComponentsAtRunTime() {
       const targetNode = document.querySelector('body');
       const observerOptions = {
         childList: true,

+ 15 - 1
examples/index.html

@@ -77,7 +77,7 @@
                                 x-on:click.away="open= false"
                                 x-cloak>
                                 Dropdown Body
-                            </ul>
+                            </ul>6
                         </div>
                     </td>
                 </tr>
@@ -110,6 +110,8 @@
                             <span x-text="JSON.stringify(checkbox)"></span>
                             <input type="checkbox" x-model="checkbox"
                                 value="foo">
+                                <input type="checkbox" x-model="checkbox"
+                                :value="0">
                             <input type="checkbox" x-model="checkbox"
                                 value="bar">
                             Radio:
@@ -315,6 +317,18 @@
                     </td>
                 </tr>
 
+                <tr>
+                    <td>x-for over a range using <code>i in 10</code> syntax and data property</td>
+                    <td>
+                        <div x-data="{ count: 10 }">
+                            <template x-for="i in count" :key="i">
+                                <span x-text="i"></span>
+                            </template>
+                            <button type="button" @click="count++">Increment count</button>
+                        </div>
+                    </td>
+                </tr>
+
                 <tr>
                     <td>Transitions</td>
                     <td>

+ 1 - 1
package-lock.json

@@ -1,6 +1,6 @@
 {
     "name": "alpinejs",
-    "version": "2.6.0",
+    "version": "2.7.0",
     "lockfileVersion": 1,
     "requires": true,
     "dependencies": {

+ 1 - 1
package.json

@@ -1,7 +1,7 @@
 {
     "main": "dist/alpine.js",
     "name": "alpinejs",
-    "version": "2.7.0",
+    "version": "2.7.2",
     "repository": {
         "type": "git",
         "url": "git://github.com/alpinejs/alpine.git"

+ 14 - 4
src/component.js

@@ -37,8 +37,11 @@ export default class Component {
             this.unobservedData.$refs = null
             this.unobservedData.$nextTick = null
             this.unobservedData.$watch = null
-            Object.keys(Alpine.magicProperties).forEach(name => {
-                this.unobservedData[`$${name}`] = null
+            // The IE build uses a proxy polyfill which doesn't allow properties
+            // to be defined after the proxy object is created so,
+            // for IE only, we need to define our helpers earlier.
+            Object.entries(Alpine.magicProperties).forEach(([name, callback]) => {
+                Object.defineProperty(this.unobservedData, `$${name}`, { get: function () { return callback(canonicalComponentElementReference) } });
             })
         /* IE11-ONLY:END */
 
@@ -64,10 +67,16 @@ export default class Component {
             this.watchers[property].push(callback)
         }
 
+
+        /* MODERN-ONLY:START */
+        // We remove this piece of code from the legacy build.
+        // In IE11, we have already defined our helpers at this point.
+
         // Register custom magic properties.
         Object.entries(Alpine.magicProperties).forEach(([name, callback]) => {
             Object.defineProperty(this.unobservedData, `$${name}`, { get: function () { return callback(canonicalComponentElementReference) } });
         })
+        /* MODERN-ONLY:END */
 
         this.showDirectiveStack = []
         this.showDirectiveLastElement
@@ -134,7 +143,7 @@ export default class Component {
                             }
 
                             return comparisonData[part]
-                        }, self.getUnobservedData())
+                        }, self.unobservedData)
                     })
             } else {
                 // Let's walk through the watchers with "dot-notation" (foo.bar) and see
@@ -155,8 +164,9 @@ export default class Component {
                                 // Run the watchers.
                                 self.watchers[fullDotNotationKey].forEach(callback => callback(target[key]))
                             }
+
                             return comparisonData[part]
-                        }, self.getUnobservedData())
+                        }, self.unobservedData)
                     })
             }
 

+ 13 - 13
src/directives/bind.js

@@ -1,4 +1,4 @@
-import { arrayUnique, isBooleanAttr, convertClassStringToArray, camelCase } from '../utils'
+import { arrayUnique, checkedAttrLooseCompare, isBooleanAttr, convertClassStringToArray, camelCase } from '../utils'
 import Alpine from '../index'
 
 export function handleAttributeBindingDirective(component, el, attrName, expression, extraVars, attrType, modifiers) {
@@ -19,23 +19,23 @@ export function handleAttributeBindingDirective(component, el, attrName, express
             if (el.attributes.value === undefined && attrType === 'bind') {
                 el.value = value
             } else if (attrType !== 'bind') {
-                el.checked = el.value == value
+                el.checked = checkedAttrLooseCompare(el.value, value)
             }
         } else if (el.type === 'checkbox') {
             // If we are explicitly binding a string to the :value, set the string,
             // If the value is a boolean, leave it alone, it will be set to "on"
             // automatically.
-            if (typeof value === 'string' && attrType === 'bind') {
-                el.value = value
+            if (typeof value !== 'boolean' && ! [null, undefined].includes(value) && attrType === 'bind') {
+                el.value = String(value)
             } else if (attrType !== 'bind') {
-               if (Array.isArray(value)) {
-                // I'm purposely not using Array.includes here because it's
-                // strict, and because of Numeric/String mis-casting, I
-                // want the "includes" to be "fuzzy".
-                el.checked = value.some(val => val == el.value)
-              } else {
-                el.checked = !! value
-              }
+                if (Array.isArray(value)) {
+                    // I'm purposely not using Array.includes here because it's
+                    // strict, and because of Numeric/String mis-casting, I
+                    // want the "includes" to be "fuzzy".
+                    el.checked = value.some(val => checkedAttrLooseCompare(val, el.value))
+                } else {
+                    el.checked = !!value
+                }
             }
         } else if (el.tagName === 'SELECT') {
             updateSelect(el, value)
@@ -78,7 +78,7 @@ export function handleAttributeBindingDirective(component, el, attrName, express
 }
 
 function setIfChanged(el, attrName, value) {
-    if (el.getAttribute(attrName) != value){
+    if (el.getAttribute(attrName) != value) {
         el.setAttribute(attrName, value)
     }
 }

+ 5 - 3
src/directives/for.js

@@ -91,13 +91,15 @@ function evaluateItemsAndReturnEmptyIfXIfIsPresentAndFalseOnElement(component, e
     if (ifAttribute && ! component.evaluateReturnExpression(el, ifAttribute.expression)) {
         return []
     }
+    
+    let items = component.evaluateReturnExpression(el, iteratorNames.items, extraVars)
 
     // This adds support for the `i in n` syntax.
-    if (isNumeric(iteratorNames.items)) {
-        return Array.from(Array(parseInt(iteratorNames.items, 10)).keys(), i => i + 1)
+    if (isNumeric(items) && items > 0) {
+        items = Array.from(Array(items).keys(), i => i + 1)
     }
 
-    return component.evaluateReturnExpression(el, iteratorNames.items, extraVars)
+    return items
 }
 
 function addElementInLoopAfterCurrentEl(templateEl, currentEl) {

+ 2 - 2
src/directives/model.js

@@ -1,5 +1,5 @@
 import { registerListener } from './on'
-import { isNumeric } from '../utils'
+import { isNumeric, checkedAttrLooseCompare } from '../utils'
 
 export function registerModelListener(component, el, modifiers, expression, extraVars) {
     // If the element we are binding to is a select, a radio, or checkbox
@@ -35,7 +35,7 @@ function generateModelAssignmentFunction(el, modifiers, expression) {
             // If the data we are binding to is an array, toggle its value inside the array.
             if (Array.isArray(currentValue)) {
                 const newValue = modifiers.includes('number') ? safeParseNumber(event.target.value) : event.target.value
-                return event.target.checked ? currentValue.concat([newValue]) : currentValue.filter(i => i !== newValue)
+                return event.target.checked ? currentValue.concat([newValue]) : currentValue.filter(el => !checkedAttrLooseCompare(el, newValue))
             } else {
                 return event.target.checked
             }

+ 2 - 4
src/index.js

@@ -31,9 +31,7 @@ const Alpine = {
             })
         })
 
-        this.listenForNewUninitializedComponentsAtRunTime(el => {
-            this.initializeComponent(el)
-        })
+        this.listenForNewUninitializedComponentsAtRunTime()
     },
 
     discoverComponents: function (callback) {
@@ -54,7 +52,7 @@ const Alpine = {
             })
     },
 
-    listenForNewUninitializedComponentsAtRunTime: function (callback) {
+    listenForNewUninitializedComponentsAtRunTime: function () {
         const targetNode = document.querySelector('body');
 
         const observerOptions = {

+ 17 - 13
src/utils.js

@@ -20,6 +20,10 @@ export function isTesting() {
         || navigator.userAgent.includes("jsdom")
 }
 
+export function checkedAttrLooseCompare(valueA, valueB) {
+    return valueA == valueB
+}
+
 export function warnIfMalformedTemplate(el, directive) {
     if (el.tagName.toLowerCase() !== 'template') {
         console.warn(`Alpine: [${directive}] directive should only be added to <template> tags. See https://github.com/alpinejs/alpine#${directive}`)
@@ -379,24 +383,24 @@ export function transitionHelper(el, modifiers, hook1, hook2, styleValues, type)
     transition(el, stages, type)
 }
 
-export function transitionClassesIn(el, component, directives, showCallback) {
-    let ensureStringExpression = (expression) => {
-        return typeof expression === 'function'
-            ? component.evaluateReturnExpression(el, expression)
-            : expression
-    }
+const ensureStringExpression = (expression, el, component) => {
+    return typeof expression === 'function'
+        ? component.evaluateReturnExpression(el, expression)
+        : expression
+}
 
-    const enter = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'enter') || { expression: '' }).expression))
-    const enterStart = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'enter-start') || { expression: '' }).expression))
-    const enterEnd = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'enter-end') || { expression: '' }).expression))
+export function transitionClassesIn(el, component, directives, showCallback) {
+    const enter = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'enter') || { expression: '' }).expression, el, component))
+    const enterStart = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'enter-start') || { expression: '' }).expression, el, component))
+    const enterEnd = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'enter-end') || { expression: '' }).expression, el, component))
 
     transitionClasses(el, enter, enterStart, enterEnd, showCallback, () => {}, TRANSITION_TYPE_IN)
 }
 
 export function transitionClassesOut(el, component, directives, hideCallback) {
-    const leave = convertClassStringToArray((directives.find(i => i.value === 'leave') || { expression: '' }).expression)
-    const leaveStart = convertClassStringToArray((directives.find(i => i.value === 'leave-start') || { expression: '' }).expression)
-    const leaveEnd = convertClassStringToArray((directives.find(i => i.value === 'leave-end') || { expression: '' }).expression)
+    const leave = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'leave') || { expression: '' }).expression, el, component))
+    const leaveStart = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'leave-start') || { expression: '' }).expression, el, component))
+    const leaveEnd = convertClassStringToArray(ensureStringExpression((directives.find(i => i.value === 'leave-end') || { expression: '' }).expression, el, component))
 
     transitionClasses(el, leave, leaveStart, leaveEnd, () => {}, hideCallback, TRANSITION_TYPE_OUT)
 }
@@ -482,7 +486,7 @@ export function transition(el, stages, type) {
 }
 
 export function isNumeric(subject){
-    return ! isNaN(subject)
+    return ! Array.isArray(subject) && ! isNaN(subject)
 }
 
 // Thanks @vuejs

+ 18 - 0
test/bind.spec.js

@@ -509,3 +509,21 @@ test('attribute binding names can contain numbers', async () => {
     expect(document.querySelector('line').getAttribute('x2')).toEqual('3');
     expect(document.querySelector('line').getAttribute('y2')).toEqual('4');
 })
+
+test('non-string and non-boolean attributes are cast to string when bound to checkbox', () => {
+    document.body.innerHTML = `
+        <div x-data="{ number: 100, zero: 0, bool: true, nullProp: null }">
+            <input type="checkbox" id="number" :value="number">
+            <input type="checkbox" id="zero" :value="zero">
+            <input type="checkbox" id="boolean" :value="bool">
+            <input type="checkbox" id="null" :value="nullProp">
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('#number').value).toEqual('100')
+    expect(document.querySelector('#zero').value).toEqual('0')
+    expect(document.querySelector('#boolean').value).toEqual('on')
+    expect(document.querySelector('#null').value).toEqual('on')
+})

+ 41 - 0
test/for.spec.js

@@ -515,3 +515,44 @@ test('x-for over range using i in x syntax', async () => {
 
     expect(document.querySelectorAll('span').length).toEqual(10)
 })
+
+test('x-for over range using i in x syntax with data property', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ count: 10 }">
+            <template x-for="i in count">
+                <span x-text="i"></span>
+            </template>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelectorAll('span').length).toEqual(10)
+})
+
+test('x-for with an array of numbers', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ items: [] }">
+            <template x-for="i in items">
+                <span x-text="i"></span>
+            </template>
+            <button id="push-2" @click="items.push(2)"></button>
+            <button id="push-3" @click="items.push(3)"></button>
+        </div>
+    `
+
+    Alpine.start()
+
+    document.querySelector('#push-2').click()
+
+    await wait(() => {
+        expect(document.querySelector('span').textContent).toEqual('2')
+    })
+
+    document.querySelector('#push-3').click()
+
+    await wait(() => {
+        expect(document.querySelectorAll('span').length).toEqual(2)
+        expect(document.querySelectorAll('span')[1].textContent).toEqual('3')
+    })
+})

+ 23 - 0
test/model.spec.js

@@ -205,6 +205,29 @@ test('x-model checkbox array binding supports .number modifier', async () => {
     await wait(() => { expect(document.querySelector('span').getAttribute('bar')).toEqual("[3]") })
 })
 
+test('x-model checkbox array binding is consistent (if value is initially checked, it can be unchecked)', async () => {
+    // https://github.com/alpinejs/alpine/issues/814
+    document.body.innerHTML = `
+        <div
+            x-data="{
+                selected: [2]
+            }"
+        >
+            <input type="checkbox" value="2" x-model="selected" />
+
+            <span x-bind:bar="JSON.stringify(selected)"></span>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('input[type=checkbox]').checked).toEqual(true)
+    expect(document.querySelector('span').getAttribute('bar')).toEqual("[2]")
+
+    fireEvent.change(document.querySelector('input[type=checkbox]'), { target: { checked: false } })
+    await wait(() => { expect(document.querySelector('span').getAttribute('bar')).toEqual("[]") })
+})
+
 test('x-model binds radio value', async () => {
     document.body.innerHTML = `
         <div x-data="{ foo: 'bar' }">

+ 41 - 0
test/spread.spec.js

@@ -92,6 +92,9 @@ test('x-spread syntax supports x-transition', async () => {
                 ['x-transition:enter']() { return 'enter' },
                 ['x-transition:enter-start']() { return 'enter-start' },
                 ['x-transition:enter-end']() { return 'enter-end' },
+                ['x-transition:leave']() { return 'leave' },
+                ['x-transition:leave-start']() { return 'leave-start' },
+                ['x-transition:leave-end']() { return 'leave-end' },
             },
         }
     }
@@ -145,6 +148,44 @@ test('x-spread syntax supports x-transition', async () => {
             resolve();
         }, 10)
     )
+
+    document.querySelector('button').click()
+
+    // Wait out the intial Alpine refresh debounce.
+    await new Promise((resolve) =>
+        setTimeout(() => {
+            resolve();
+        }, 5)
+    )
+
+    expect(document.querySelector('span').classList.contains('leave')).toEqual(true)
+    expect(document.querySelector('span').classList.contains('leave-start')).toEqual(true)
+    expect(document.querySelector('span').classList.contains('leave-end')).toEqual(false)
+    expect(document.querySelector('span').getAttribute('style')).toEqual(null)
+
+    frameStack.pop()()
+
+    expect(document.querySelector('span').classList.contains('leave')).toEqual(true)
+    expect(document.querySelector('span').classList.contains('leave-start')).toEqual(true)
+    expect(document.querySelector('span').classList.contains('leave-end')).toEqual(false)
+    expect(document.querySelector('span').getAttribute('style')).toEqual(null)
+
+    frameStack.pop()()
+
+    expect(document.querySelector('span').classList.contains('leave')).toEqual(true)
+    expect(document.querySelector('span').classList.contains('leave-start')).toEqual(false)
+    expect(document.querySelector('span').classList.contains('leave-end')).toEqual(true)
+    expect(document.querySelector('span').getAttribute('style')).toEqual(null)
+
+    await new Promise((resolve) =>
+        setTimeout(() => {
+            expect(document.querySelector('span').classList.contains('leave')).toEqual(false)
+            expect(document.querySelector('span').classList.contains('leave-start')).toEqual(false)
+            expect(document.querySelector('span').classList.contains('leave-end')).toEqual(false)
+            expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;')
+            resolve();
+        }, 10)
+    )
 })
 
 

+ 24 - 0
test/watch.spec.js

@@ -146,3 +146,27 @@ test('$watch nested arrays', async () => {
         expect(document.querySelector('h2').textContent).toEqual('one,two')
     })
 })
+
+test('$watch with magic properties', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ foo: 'bar', bob: 'car' }" x-init="$watch('$self.foo', value => bob = value)">
+            <span x-text="bob"></span>
+
+            <button x-on:click="$self.foo = 'far'"></button>
+        </div>
+    `
+
+    Alpine.addMagicProperty('self', function (el) {
+        return el.__x.$data
+    })
+
+    Alpine.start()
+
+    expect(document.querySelector('span').textContent).toEqual('car')
+
+    document.querySelector('button').click()
+
+    await wait(() => {
+        expect(document.querySelector('span').textContent).toEqual('far')
+    })
+})