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

Merge branch 'master' of github.com:alpinejs/alpine

Caleb Porzio 5 éve
szülő
commit
9159e43028
10 módosított fájl, 1391 hozzáadás és 14 törlés
  1. 6 2
      README.md
  2. 651 0
      README.ru.md
  3. 674 0
      README_zh-TW.md
  4. 2 2
      src/component.js
  5. 2 8
      src/directives/for.js
  6. 2 2
      src/directives/if.js
  7. 2 0
      src/polyfills.js
  8. 8 0
      src/utils.js
  9. 32 0
      test/constructor.spec.js
  10. 12 0
      test/data.spec.js

+ 6 - 2
README.md

@@ -13,6 +13,8 @@ Think of it like [Tailwind](https://tailwindcss.com/) for JavaScript.
 > Note: This tool's syntax is almost entirely borrowed from [Vue](https://vuejs.org/) (and by extension [Angular](https://angularjs.org/)). I am forever grateful for the gift they are to the web.
 
 [**日本語ドキュメント**](./README.ja.md)
+[**繁體中文使用文件**](./README_zh-TW.md)
+[**Документация на русском**](./README.ru.md)
 
 ## Install
 
@@ -131,7 +133,7 @@ And 6 magic properties:
 
 **Want your logo here? [DM on Twitter](https://twitter.com/calebporzio)**
 
-## VIP Contributers
+## VIP Contributors
 
 <table>
   <tr>
@@ -224,7 +226,7 @@ If you wish to run code AFTER Alpine has made its initial updates to the DOM (so
 
 | Directive | Description |
 | --- | --- |
-| `x-show.transition` | A simultanious fade and scale. (opacity, scale: 0.95, timing-function: cubic-bezier(0.4, 0.0, 0.2, 1), duration-in: 150ms, duration-out: 75ms)
+| `x-show.transition` | A simultaneous fade and scale. (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` | Only transition in. |
 | `x-show.transition.out` | Only transition out. |
 | `x-show.transition.opacity` | Only use the fade. |
@@ -536,6 +538,8 @@ These behave exactly like VueJs's transition directives, except they have differ
 
 ### Magic Properties
 
+> With the exception of `$el`, magic properties are **not available within `x-data`** as the component isn't initialized yet.
+
 ---
 
 ### `$el`

+ 651 - 0
README.ru.md

@@ -0,0 +1,651 @@
+# 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 предлагает вам реактивность и декларативность таких больших фреймворков, как Vue или React, но за значительно меньшую цену.
+
+Вы сможете использовать обычный DOM, при этом изменяя поведение по своему усмотрению.
+
+Можете думать о Alpine.js как о [Tailwind](https://tailwindcss.com/) для JavaScript.
+
+> Замечание: синтаксис Alpine.js почти полностью заимствован из [Vue](https://vuejs.org/) (а, соответственно, и из [Angular](https://angularjs.org/)). Я безмерно благодарен разработчикам этих инструментов за тот вклад, который они внесли в Web.
+
+## Установка
+
+**С помощью CDN:** Добавьте следующий `<script>` в конец секции `<head>`.
+```html
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
+```
+
+Вот и всё. Он инициализируется самостоятельно.
+
+Для продакшн-окружения, рекомедуется использовать ссылку с конкретным номером версии, чтобы избежать неожиданных поломок после выпуска новых версий.
+Например, чтобы использовать версию `2.3.5`:
+```html
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.3.5/dist/alpine.min.js" defer></script>
+```
+
+**С помощью NPM:** Установите пакет из NPM.
+```js
+npm i alpinejs
+```
+
+Добавьте его в свой код.
+```js
+import 'alpinejs'
+```
+
+**Для поддержки IE11** используйте вместо указанных выше следующие скрипты:
+```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>
+```
+
+Паттерн, использующийся выше, называется [паттерн module/nomodule](https://philipwalton.com/articles/deploying-es2015-code-in-production-today/). Он позволяет автоматически загружать современный бандл в современных браузерах, а в IE11 и других устаревших браузерах – бандл для IE11.
+
+## Использование
+
+*Дропдаун/Модальное окно*
+```html
+<div x-data="{ open: false }">
+    <button @click="open = true">Открыть дропдаун</button>
+
+    <ul
+        x-show="open"
+        @click.away="open = false"
+    >
+        Содержимое дропдаун
+    </ul>
+</div>
+```
+
+*Вкладки*
+```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'">Вкладка Foo</div>
+    <div x-show="tab === 'bar'">Вкладка Bar</div>
+</div>
+```
+
+Alpine.js можно использовать и для более серьезных выражений:
+*Предзагрузка HTML-содержания дропдауна при наведении мыши*
+```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"
+    >Показать дропдаун</button>
+
+    <div x-ref="dropdown" x-show="open" @click.away="open = false">
+        Загрузка...
+    </div>
+</div>
+```
+
+## Изучение
+
+Всего в Alpine 13 директив:
+
+| Директива | Описание |
+| --- | --- |
+| [`x-data`](#x-data) | Объявляет новый компонент и его данные. |
+| [`x-init`](#x-init) | Выполняет переданное выражение, когда компонент инициализируется. |
+| [`x-show`](#x-show) | Переключает `display: none;` на элементе, в зависимости от результата переданного выражения (true или false). |
+| [`x-bind`](#x-bind) | Устанавливает значение атрибута равным результату переданного JS-выражения |
+| [`x-on`](#x-on) | Устанавливает обработчик события на элемент. Когда событие срабатывает, выполняет переданное JS-выражение. |
+| [`x-model`](#x-model) | Добавляет "двустороннюю привязку данных" (two-way data binding) на элемент. Синхронизирует элемент и данные компонента. |
+| [`x-text`](#x-text) | Устанавливает значение `innerText` элемента равным результату переданного JS-выражения. |
+| [`x-html`](#x-html) | Устанавливает значение `innerHTML` элемента равным результату переданного JS-выражения. |
+| [`x-ref`](#x-ref) | Удобный способ получения DOM-элементов вашего компонента. |
+| [`x-if`](#x-if) | При невыполнении переданного условия полностью удаляет элемент из DOM. Должна использоваться в теге `<template>`. |
+| [`x-for`](#x-for) | Создает новые DOM узлы для каждого элемента в массиве. Должна использоваться в теге `<template>`. |
+| [`x-transition`](#x-transition) | Директивы для добавления классов различным стадиям перехода (transition) элемента |
+| [`x-cloak`](#x-cloak) | Удаляется при инициализации Alpine. Полезна для скрытия DOM до инициализации. |
+
+И 6 волшебных свойств (magic properties):
+
+| Волшебное свойство | Описание |
+| --- | --- |
+| [`$el`](#el) |  Получить DOM-узел корневого компонента. |
+| [`$refs`](#refs) | Получить DOM-элементы компонента, отмеченные `x-ref`. |
+| [`$event`](#event) | В обработчике события получить нативный объект браузера "Event".  |
+| [`$dispatch`](#dispatch) | Создать `CustomEvent` и вызвать его, используя `.dispatchEvent()`. |
+| [`$nextTick`](#nexttick) | Выполнить переданное выражение ПОСЛЕ того, как Alpine сделает реактивное обновление DOM. |
+| [`$watch`](#watch) | Выполнить переданный колбэк, когда наблюдаемое свойство компонента изменится. |
+
+
+## Спонсоры
+
+<img width="33%" src="https://refactoringui.nyc3.cdn.digitaloceanspaces.com/tailwind-logo.svg" alt="Tailwind CSS">
+
+**Хочешь здесь своё лого? [Напиши мне сообщение в Twitter](https://twitter.com/calebporzio)**
+
+## VIP контрибьюторы
+
+<table>
+  <tr>
+    <td align="center"><a href="http://calebporzio.com"><img src="https://avatars2.githubusercontent.com/u/3670578?v=4" width="100px;" alt="Caleb Porzio"/><br /><sub><b>Caleb Porzio</b></sub></a><br /><sub>(Создатель)</sub></td>
+    <td align="center"><a href="https://github.com/HugoDF"><img src="https://avatars2.githubusercontent.com/u/6459679?v=4" width="100px;" alt="Hugo"/><br /><sub><b>Hugo</b></sub></a></td>
+    <td align="center"><a href="https://github.com/ryangjchandler"><img src="https://avatars2.githubusercontent.com/u/41837763?v=4" width="100px;" alt="Ryan Chandler"/><br /><sub><b>Ryan Chandler</b></sub></a></td>
+    <td align="center"><a href="https://github.com/SimoTod"><img src="https://avatars2.githubusercontent.com/u/8427737?v=4" width="100px;" alt="Simone Todaro"/><br /><sub><b>Simone Todaro</b></sub></a></td>
+  </tr>
+</table>
+
+
+### Директивы
+
+---
+
+### `x-data`
+
+**Пример:** `<div x-data="{ foo: 'bar' }">...</div>`
+
+**Синтаксис:** `<div x-data="[JSON-объект]">...</div>`
+
+`x-data` объявляет область видимости нового компонента с использованием переданного объекта данных.
+
+Аналогична свойству `data` в компонентах Vue.
+
+**Извлечение логики компонента**
+
+Вы можете извлечь данные (и поведение) в переиспользуемые функции:
+
+```html
+<div x-data="dropdown()">
+    <button x-on:click="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>
+```
+
+> **Для пользователей бандлеров**. Alpine.js получает доступ к функциям только из глобальной области видимости (`window`). Вам необходимо явно присвоить свои функции объекту `window`, чтобы использовать их с `x-data`. Например, вот так: `window.dropdown = function () {}` (с Webpack, Rollup, Parcel и другими бандлерами функции, которые вы объявляете, по умолчанию принадлежать области видимости бандлера, а не `window`).
+
+
+Вы также можете объединять несколько объектов с данными с помощью деструктуризации:
+
+```html
+<div x-data="{...dropdown(), ...tabs()}">
+```
+
+---
+
+### `x-init`
+**Пример:** `<div x-data="{ foo: 'bar' }" x-init="foo = 'baz'"></div>`
+
+**Синтаксис:** `<div x-data="..." x-init="[выражение]"></div>`
+
+`x-init` выполняет переданное выражение, когда компонент инициализируется.
+
+Если вы хотите выполнить код ПОСЛЕ первоначальных обновлений DOM Alpine (наподобие хука `mounted()` во VueJS), вы можете передать `x-init` колбэк, и он выполнит его после инициализации:
+
+`x-init="() => { // здесь уже есть доступ к стейту после инициализации DOM // }"`
+
+---
+
+### `x-show`
+**Пример:** `<div x-show="open"></div>`
+
+**Синтаксис:** `<div x-show="[выражение]"></div>`
+
+`x-show` переключает `display: none;` на элементе в зависимости от результата выполнения выражения (`true` или `false`).
+
+**x-show.transition**
+
+`x-show.transition` – удобный API для добавления `x-show` CSS-переходов.
+
+```html
+<div x-show.transition="open">
+    Это содержимое будет иметь переходы при появлении и исчезновении.
+</div>
+```
+
+| Директива | Описание |
+| --- | --- |
+| `x-show.transition` | Одновременный fade и scale. (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` | Переход только при появлении. |
+| `x-show.transition.out` | Переход только при исчезновении. |
+| `x-show.transition.opacity` | Использовать только fade. |
+| `x-show.transition.scale` | Использовать только scale. |
+| `x-show.transition.scale.75` | Кастомизация scale перехода `transform: scale(.75)`. |
+| `x-show.transition.duration.200ms` | Устанавливает время перехода при появлении на 200мс. Переход при исчезновении будет равен половине этого значения (100мс). |
+| `x-show.transition.origin.top.right` | Кастомизация места возникновения перехода `transform-origin: top right`. |
+| `x-show.transition.in.duration.200ms.out.duration.50ms` | Различные длительности для переходов при появлении и исчезновении. |
+
+> Замечание: Все эти модификаторы переходов могут использоваться в сочетании друг с другом. Это возможно (хоть и нелепо): `x-show.transition.in.duration.100ms.origin.top.right.opacity.scale.85.out.duration.200ms.origin.bottom.left.opacity.scale.95`
+
+> Замечание: `x-show` будет ждать окончания переходов всех дочерних элементов. Можно изменить это поведение модификатором `.immediate`:
+```html
+<div x-show.immediate="open">
+    <div x-show.transition="open">
+</div>
+```
+---
+
+### `x-bind`
+
+> Сокращенный синтаксис ":". Например: `:type="..."`
+
+**Пример:** `<input x-bind:type="inputType">`
+
+**Синтаксис:** `<input x-bind:[атрибут]="[выражение]">`
+
+`x-bind` устанавливает значение атрибута равным результату JS-выражения. Выражение имеет доступ ко всем ключам хранилища данных компонента и будет обновляться каждый раз при обновлении данных.
+
+> Замечание: обновление значения атрибута с `x-bind` будет происходить ТОЛЬКО при обновлении его зависимостей.
+
+**`x-bind` для атрибутов class**
+
+`x-bind` ведет себя немного иначе, когда привязан к атрибуту `class`.
+
+Для классов необходимо передавать объект, где ключи – это имена классов, а значения – логические выражения, которые определяют применяются эти классы или нет.
+
+Например:
+`<div x-bind:class="{ 'hidden': foo }"></div>`
+
+В этом примере, класс "hidden" будет применен только если значение выражения `foo` равно `true`.
+
+**`x-bind` для логических атрибутов**
+
+`x-bind` поддерживает логические атрибуты так же, как и атрибуты значения, используя переменную как условие или любое JS-выражение, которое разрешается в `true` или `false`.
+
+Например:
+```html
+<!-- Дано: -->
+<button x-bind:disabled="myVar">Нажми на меня</button>
+
+<!-- Когда myVar == true: -->
+<button disabled="disabled">Нажми на меня</button>
+
+<!-- Когда myVar == false: -->
+<button>Нажми на меня</button>
+```
+
+Это добавит или удалит атрибут `disabled`, в зависимости от того, равна `myVar` true или false.
+
+Логические атрибуты поддерживаются в соответствии с [HTML спецификацией](https://html.spec.whatwg.org/multipage/indices.html#attributes-3:boolean-attribute), такие как, например, `disabled`, `readonly`, `required`, `checked`, `hidden`, `selected`, `open` и другие.
+
+---
+
+### `x-on`
+
+> Сокращенный синтаксис "@": `@click="..."`
+
+**Пример:** `<button x-on:click="foo = 'bar'"></button>`
+
+**Синтаксис:** `<button x-on:[событие]="[выражение]"></button>`
+
+`x-on` цепляет прослушиватель события на элемент, на котором был объявлен. Когда событие срабатывает, выполняется переданное JS-выражение.
+
+Если в этом выражении меняются какие-либо данные, другие элементы, "привязанные" к этим данным, будут обновлены.
+
+**Модификатор `keydown`**
+
+**Пример:** `<input type="text" x-on:keydown.escape="open = false">`
+
+Можно обозначить конкретные клавиши для прослушивания, присоединяя их через точку к директиве `x-on:keydown`. Модификаторы – это значения `Event.key`, записанные в kebab-стиле.
+
+Например: `enter`, `escape`, `arrow-up`, `arrow-down`
+
+> Замечание: Можно также прослушивать комбинации с системными клавишами, например: `x-on:keydown.cmd.enter="foo"`
+
+**Модификатор `.away`**
+
+**Пример:** `<div x-on:click.away="showModal = false"></div>`
+
+При добавлении модификатора `.away` обработчик события сработает, только когда событие произошло на другом источнике, а не на этом элементе или его потомках.
+
+Это полезно для скрытия дропдаунов или модальных окон, когда пользователь кликает в другом месте.
+
+**Модификатор `.prevent`**
+
+**Пример:** `<input type="checkbox" x-on:click.prevent>`
+
+При добавлении `.prevent` обработчик вызовет `preventDefault` на сработавшем событии. В примере выше это приведет к тому, что чекбокс не будет отмечен при нажатии на него.
+
+**Модификатор `.stop`**
+
+**Пример:** `<div x-on:click="foo = 'bar'"><button x-on:click.stop></button></div>`
+
+При добавлении `.stop` обработчик вызовет `stopPropagation` на сработавшем событии. В примере выше это приведет к тому, что событие "click" не всплывет от кнопки внешнему `<div>`. Другими словами, когда пользователь нажимает на кнопку, `foo` не устанавливается в `'bar'`.
+
+**Модификатор `.self`**
+
+**Пример:** `<div x-on:click.self="foo = 'bar'"><button></button></div>`
+
+При добавлении `.self` обработчик события сработает, только если `$event.target` – это сам элемент. В примере выше это приведет к тому, что событие "click", всплыв от кнопки внешнему `<div>`, **не** вызовет обработчик.
+
+**Модификатор `.window`**
+
+**Пример:** `<div x-on:resize.window="isOpen = window.outerWidth > 768 ? false : open"></div>`
+
+При добавлении `.window` прослушиватель события установится не на узел DOM, на котором был вызван, а на глобальный объект window. Это полезно, когда нужно изменить состояние компонента при изменении чего-либо в window, например, при событии "resize". В примере выше, когда ширина окна будет больше 768 пикселей, мы закроем модальное окно/дропдаун, иначе сохраним то же состояние.
+
+>Замечание: Также можно использовать модификатор `.document` для добавления прослушивателей к `document`.
+
+**Модификатор `.once`**
+
+**Пример:** `<button x-on:mouseenter.once="fetchSomething()"></button>`
+
+При добавлении `.once` обработчик события будет вызван лишь единожды. Это полезно для вещей, которые вы хотите сделать только один раз, например, загрузка данных и т.п.
+
+**Модификатор `.debounce`**
+
+**Пример:** `<input x-on:input.debounce="fetchSomething()">`
+
+Модификатор `debounce` позволяет вам избавиться от ложных повторных вызовов обработчика события. Другими словами, обработчик НЕ будет вызван, пока не пройдет определенное количество времени с предыдущего вызова. Когда обработчик будет готов к вызову, будет вызван последний вызов.
+
+Время ожидания по умолчанию 250 миллисекунд.
+
+Вы также можете указать свое время:
+
+```
+<input x-on:input.debounce.750="fetchSomething()">
+<input x-on:input.debounce.750ms="fetchSomething()">
+```
+
+---
+
+### `x-model`
+**Пример:** `<input type="text" x-model="foo">`
+
+**Синтаксис:** `<input type="text" x-model="[хранилище данных]">`
+
+`x-model` добавляет элементу "двустороннюю привязку данных" (two-way data binding). Другими словами, значение поля ввода будет синхронизировано со значением в хранилище данных компонента.
+
+> Замечание: `x-model` достаточно умен, чтобы замечать изменения в текстовых полях ввода, чекбоксах, радио-кнопках, textarea, select, и множественных select. В данных сценариях `x-model` ведет себя аналогично `v-model` [во Vue](https://vuejs.org/v2/guide/forms.html).
+
+**Модификатор `.debounce`**
+
+**Пример:** `<input x-model.debounce="search">`
+
+Модификатор `debounce` позволяет вам избавиться от ложных повторных изменений значения. Другими словами, обработчик НЕ будет вызван, пока не пройдет определенное количество времени с предыдущего вызова. Когда обработчик будет готов к вызову, будет вызван последний вызов.
+
+Время ожидания по умолчанию 250 миллисекунд.
+
+Вы также можете указать свое время:
+
+```
+<input x-model.debounce.750="search">
+<input x-model.debounce.750ms="search">
+```
+
+---
+
+### `x-text`
+**Пример:** `<span x-text="foo"></span>`
+
+**Синтаксис:** `<span x-text="[выражение]"`
+
+`x-text` устанавливает значение `innerText` элемента равным результату переданного JS-выражения. Другими словами, работает аналогично `x-bind`, но не для атрибута, а для `innerText` элемента.
+
+---
+
+### `x-html`
+**Пример:** `<span x-html="foo"></span>`
+
+**Синтаксис:** `<span x-html="[выражение]"`
+
+`x-html` устанавливает значение `innerHTML` элемента равным результату переданного JS-выражения. Другими словами, работает аналогично `x-bind`, но не для атрибута, а для `innerHTML` элемента.
+
+> :warning: **Используйте только надежные источники контента и никогда не используйте контент, предоставленный пользователем.** :warning:
+>
+> Динамически отрендеренный HTML от третьих сторон может легко привести к [XSS](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) уязвимостям.
+
+---
+
+### `x-ref`
+**Пример:** `<div x-ref="foo"></div><button x-on:click="$refs.foo.innerText = 'bar'"></button>`
+
+**Синтаксис:** `<div x-ref="[имя рефа]"></div><button x-on:click="$refs.[имя рефа].innerText = 'bar'"></button>`
+
+`x-ref` предоставляет удобный способ получения DOM-элементов ваших компонентов. При установлении атрибута `x-ref` на элемент, вы делаете его доступным всем обработчикам событий в объекте `$refs`.
+
+Это удобная альтернатива установке id и использованию повсюду `document.querySelector`.
+
+> Замечание: при необходимости вы также можете привязывать x-ref динамические значения: `<span :x-ref="item.id"></span>`.
+
+---
+
+### `x-if`
+**Пример:** `<template x-if="true"><div>Какой-то элемент</div></template>`
+
+**Синтаксис:** `<template x-if="[выражение]"><div>Какой-то элемент</div></template>`
+
+В случаях, когда `x-show` недостаточно (`x-show` устанавливает элементу `display: none`, если выражение ложно), можно использовать `x-if`, чтобы полностью удалить элемент из DOM.
+
+Alpine не использует Virtual DOM, поэтому важно, чтобы `x-if` использовался в теге `<template></template>`.
+
+> Замечание: Внутри тега `<template></template>` с `x-if` должен быть лишь один корневой элемент.
+
+---
+
+### `x-for`
+**Пример:**
+```html
+<template x-for="item in items" :key="item">
+    <div x-text="item"></div>
+</template>
+```
+
+> Замечание: привязка `:key` опциональна, хотя КРАЙНЕ рекомендуется.
+
+`x-for` используется для создания новых DOM-узлов для каждого элемента в массиве. `x-for` похоже на `v-for` во Vue, с одним отличием: `x-for` может использовался только в теге `<template></template>`.
+
+Если вы хотите получить доступ к индексу текущей итерации, используйте следующий синтаксис:
+
+```html
+<template x-for="(item, index) in items" :key="index">
+    <!-- Если необходимо, вы также можете ссылаться на "index" внутри итерации. -->
+    <div x-text="index"></div>
+</template>
+```
+
+> Замечание: Внутри тега `<template></template>` с `x-for` должен быть лишь один корневой элемент.
+
+#### Вложенные `x-for`
+Вы можете вкладывать `x-for` друг в друга, но вы ДОЛЖНЫ оборачивать каждый цикл в какой-нибудь элемент. Например:
+
+```html
+<template x-for="item in items">
+    <div>
+        <template x-for="subItem in item.subItems">
+            <div x-text="subItem"></div>
+        </template>
+    </div>
+</template>
+```
+
+---
+
+### `x-transition`
+**Пример:**
+```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>
+```
+
+> Пример выше использует классы из [Tailwind CSS](https://tailwindcss.com)
+
+Alpine предлагает 6 разных transition-директив для добавления классов к различным стадиям перехода элемента от состояния "скрытого" к "видимому". Все эти директивы работают как с `x-show`, так и с `x-if`.
+
+Они ведут себя абсолютно так же, как transition-директивы во Vue, не считая того, что у них другие, более понятные названия:
+
+| Директива | Описание |
+| --- | --- |
+| `:enter` | Применяется в ходе всей фазы появления. |
+| `:enter-start` | Добавляется до введения элемента, удаляется на следующий фрейм после с введения элемента. |
+| `:enter-end` | Добавляется на следующий фрейм после с введения элемента (одновременно с удалением `enter-start`), удаляется, когда переход/анимация заканчивается.
+| `:leave` | Применяется в ходе всей фазы исчезновения. |
+| `:leave-start` | Добавляется, как только вызвано исчезновение, удаляется на следующий фрейм. |
+| `:leave-end` | Добавляется на следующий фрейм, как только вызвано исчезновение (одновременно с удалением `leave-start`), удаляется, когда переход/анимация заканчивается.
+
+---
+
+### `x-cloak`
+**Пример:** `<div x-data="{}" x-cloak></div>`
+
+`x-cloak` атрибуты удаляются с элементов, когда Alpine проинициализирован. Это полезно для скрытия DOM до его инициализации. Для этого необходимо добавить следующие глобальные стили:
+
+```html
+<style>
+    [x-cloak] { display: none; }
+</style>
+```
+
+### Magic Properties
+
+---
+
+### `$el`
+**Пример:**
+```html
+<div x-data>
+    <button @click="$el.innerHTML = 'foo'">Замени меня на "foo"</button>
+</div>
+```
+
+`$el` – магическое свойство, которое используется для получения корневого компонента DOM-узла.
+
+### `$refs`
+**Пример:**
+```html
+<span x-ref="foo"></span>
+
+<button x-on:click="$refs.foo.innerText = 'bar'"></button>
+```
+
+`$refs` – это магическое свойство, которое используется для получения DOM-элементов внутри компонента, помеченных `x-ref`. Это полезно, когда вам нужно вручную манипулировать элементами DOM.
+
+---
+
+### `$event`
+**Пример:**
+```html
+<input x-on:input="alert($event.target.value)">
+```
+
+`$event` – это магическое свойство, которое можно использовать в прослушивателе событий для получения нативного объекта "Event".
+
+---
+
+### `$dispatch`
+**Пример:**
+```html
+<div @custom-event="console.log($event.detail.foo)">
+    <button @click="$dispatch('custom-event', { foo: 'bar' })">
+    <!-- После нажатия кнопки выводит в консоль "bar" -->
+</div>
+```
+
+`$dispatch` – это сокращение для создания `CustomEvent` и его вызова (диспатча) с помощью `.dispatchEvent()`. Существует множество сценариев использования передачи данных между компонентами с помощью пользовательских событий. [Пройдите по этой ссылке](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events), чтобы узнать больше о системе, лежащей в основе `CustomEvent` в браузерах.
+
+Любые данные, переданные как второй параметр в `$dispatch('some-event', { some: 'data' })`, становятся доступны через свойство "detail" события: `$event.detail.some`. Добавление событию пользовательских данных через свойство `.detail` – стандартная практика для `CustomEvent` в браузерах. [Подробнее здесь](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail).
+
+Вы также можете использовать `$dispatch()` для вызова обновления данных в привязках `x-model`. Например:
+
+```html
+<div x-data="{ foo: 'bar' }">
+    <span x-model="foo">
+        <button @click="$dispatch('input', 'baz')">
+        <!-- После нажатия кнопки, `x-model` перехватит всплывающее события "input", и обновит foo на "baz". -->
+    </span>
+</div>
+```
+
+---
+
+### `$nextTick`
+**Пример:**
+```html
+<div x-data="{ fruit: 'яблоко' }">
+    <button
+        x-on:click="
+            fruit = 'груша';
+            $nextTick(() => { console.log($event.target.innerText) });
+        "
+        x-text="fruit"
+    ></button>
+</div>
+```
+
+`$nextTick` – это магическое свойство, которое выполняет переданное выражение только ПОСЛЕ того, как Alpine реактивно обновит DOM. Это полезно в тех случаях, когда вы хотите взаимодействовать с состоянием DOM, ПОСЛЕ того, как оно отразит сделанное вами обновление данных.
+
+---
+
+### `$watch`
+**Пример:**
+```html
+<div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))">
+    <button @click="open = ! open">Toggle Open</button>
+</div>
+```
+
+Магический метод `$watch` позволяет следить за выбранным свойством компонента. В примере выше при нажатии на кнопку: 1) значение `open` изменится; 2) выполнится переданный в `$watch` колбэк; 3) в консоль выведется новое значение.
+
+## Безопасность
+Если вы нашли уязвимость, пожалуйста, отправьте письмо на [calebporzio@gmail.com]()
+
+Alpine полагается на собственную реализацию, которая использует объект `Function` для оценки своих директив. Несмотря на то, что он безопаснее, чем `eval()`, его использование запрещено в некоторых средах, таких как Google Chrome App, т.е. использующих Политику защиты контента (CSP).
+
+Если вы используете Alpine на веб-сайте, имеющем дело с конфиденциальными данными и требующим [CSP](https://csp.withgoogle.com/docs/strict-csp.html), вы должны включить `unsafe-eval` в свою политику. Правильно настроенная политика поможет защитить ваших пользователей при использовании их личных или финансовых данных.
+
+Поскольку политика применяется ко всем скриптам на вашей странице, важно, чтобы другие внешние библиотеки, которые используются на сайте, были тщательно проверены, чтобы убедиться, что они заслуживают доверия, и не будут создавать XSS-уязвимость с помощью функции `eval()` или манипулировать DOM для внедрения вредоносного кода на вашу страницу.
+
+## Планы на третью версию
+* Перейти с `x-ref` на `ref` для соответствия с Vue?
+* Добавить `Alpine.directive()`
+* Добавить `Alpine.component('foo', {...})` (с магическим методом `__init()`)
+* Вызывать Alpine-события для "loaded", "transition-start", и т.д. ([#299](https://github.com/alpinejs/alpine/pull/299)) ?
+* Удалить синтаксис объекта (и массива) у `x-bind:class="{ 'foo': true }"` ([#236](https://github.com/alpinejs/alpine/pull/236), чтобы добавить поддержку синтаксиса объекта для атрибута `style`)
+* Улучшить изменение реактивности `x-for` ([#165](https://github.com/alpinejs/alpine/pull/165))
+* Добавить поддержку "deep watching" ([#294](https://github.com/alpinejs/alpine/pull/294))
+* Добавить сокращение для `$el`
+* Изменить `@click.away` на `@click.outside`?
+
+## Лицензия
+
+Copyright © 2019-2020 Caleb Porzio and контрибьюторы
+
+Лицензировано по лицензии MIT, смотрите [LICENSE.md](LICENSE.md) для подробностей.

+ 674 - 0
README_zh-TW.md

@@ -0,0 +1,674 @@
+# 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 提供了像是 Vue 或 React 這樣的大型框架的回應式或宣告式的特性,但使用成本卻低得多。
+
+你依然可以保留你的 DOM ,並且在你認為合適的時候添加行為。
+
+你可以把 Alpine.js 想像成 JavaScript 的 [Tailwind](https://tailwindcss.com/) 。
+
+> 備註:這個工具的語法幾乎借鑑了 [Vue](https://vuejs.org/) (以及一些 [Angular](https://angularjs.org/) 的延伸)。我永遠感謝他們為 Web 帶來的大禮。
+
+## 部屬
+
+**使用 CDN 來源:** 在你的 `<head>` 標籤的末端添加以下腳本。
+```html
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
+```
+
+僅僅如此,它會自動初始化。
+
+如果是正式環境,建議在連結中加入一個特定的版本號碼,避免新版本的升級題你的程式帶來執行上的意外的錯誤。
+舉個例子,你想固定在 `2.3.5` 這個版本:
+```html
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.3.5/dist/alpine.min.js" defer></script>
+```
+
+**使用 NPM 來源:** 使用 NPM 程式包安裝
+```js
+npm i alpinejs
+```
+
+在你的腳本中引入它。
+```js
+import 'alpinejs'
+```
+
+**對於 IE11 的支援** 請使用以下腳本替代。
+```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>
+```
+
+上述的寫法是 [module/nomodule 模式](https://philipwalton.com/articles/deploying-es2015-code-in-production-today/) ,它會讓現代化網頁瀏覽器自動載入它所需的程式包。而 IE11 版本的程式包將會在 IE11 上或者是其他的傳統網頁瀏覽器上被自動載入。
+
+## 使用
+
+*下拉式選單/互動式視窗*
+```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>
+```
+
+*頁籤*
+```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>
+```
+
+你甚至可以用它來達成一些並不顯而易見事情:
+*在懸停時預取下拉式選單的 HTML 內容*
+```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>
+```
+
+## 教學
+
+共有 13 個指令可供你使用:
+
+| 指令 | 功能描述 |
+| --- | --- |
+| [`x-data`](#x-data) | 宣告一個新的組件範圍。 |
+| [`x-init`](#x-init) | 當一個組件被初始化時,運作一個運算式。 |
+| [`x-show`](#x-show) | 根據運算式的不同(true 或 false)在元素上切換 `display: none;` 。 |
+| [`x-bind`](#x-bind) | 將屬性的數值設定為 JS 運算式的結果。 |
+| [`x-on`](#x-on) | 替元素附加一個事件監聽器,在觸發時執行 JS 運算式。 |
+| [`x-model`](#x-model) | 替元素附加「雙向資料繫結」,使輸入元素與組件資料保持同步。 |
+| [`x-text`](#x-text) | 它的工作原理類似於 `x-bind` ,但會更新元素的 `innerText` 。 |
+| [`x-html`](#x-html) | 它的工作原理類似於 `x-bind` ,但會更新元素的 `innerHTML` 。 |
+| [`x-ref`](#x-ref) | 便利地從組件中取得原始的 DOM 元素。 |
+| [`x-if`](#x-if) | 從 DOM 中完全刪除一個元素,需要在 `<template>` 標籤上使用。 |
+| [`x-for`](#x-for) | 替陣列中的每個項次創建新的 DOM 節點,需要在 `<template>` 標籤中使用。|
+| [`x-transition`](#x-transition) | 將類別應用於元素過渡的各個階段的指令。 |
+| [`x-cloak`](#x-cloak) | 這個屬性在 Alpine 初始化時將被移除,用於隱藏預初始化的 DOM 。 |
+
+以及 6 種魔術屬性:
+
+| 魔術屬性 | 功能描述 |
+| --- | --- |
+| [`$el`](#el) | 檢索根組件的 DOM 節點。 |
+| [`$refs`](#refs) | 檢索組件內被標有 `x-ref` 的 DOM 元素。 |
+| [`$event`](#event) | 檢索事件監聽器中的原生瀏覽器「Event」物件。 |
+| [`$dispatch`](#dispatch) | 創建一個 `CustomEvent` 並在內部使用 `.dispatchEvent()` 進行調度。 |
+| [`$nextTick`](#nexttick) | 在 Alpine 進行回應式 DOM 更新後執行給定的運算式。 |
+| [`$watch`](#watch) | 當你「監聽」的組件屬性被改變時,會啟動一個你所提供的回呼。 |
+
+## 贊助者
+
+<img width="33%" src="https://refactoringui.nyc3.cdn.digitaloceanspaces.com/tailwind-logo.svg" alt="Tailwind CSS">
+
+**想要讓你的 LOGO 置於這裡嗎? [在 Twitter 上傳送私人訊息](https://twitter.com/calebporzio)**
+
+## VIP 貢獻者
+
+<table>
+  <tr>
+    <td align="center"><a href="http://calebporzio.com"><img src="https://avatars2.githubusercontent.com/u/3670578?v=4" width="100px;" alt="Caleb Porzio"/><br /><sub><b>Caleb Porzio</b></sub></a><br /><sub>(Creator)</sub></td>
+    <td align="center"><a href="https://github.com/HugoDF"><img src="https://avatars2.githubusercontent.com/u/6459679?v=4" width="100px;" alt="Hugo"/><br /><sub><b>Hugo</b></sub></a></td>
+    <td align="center"><a href="https://github.com/ryangjchandler"><img src="https://avatars2.githubusercontent.com/u/41837763?v=4" width="100px;" alt="Ryan Chandler"/><br /><sub><b>Ryan Chandler</b></sub></a></td>
+    <td align="center"><a href="https://github.com/SimoTod"><img src="https://avatars2.githubusercontent.com/u/8427737?v=4" width="100px;" alt="Simone Todaro"/><br /><sub><b>Simone Todaro</b></sub></a></td>
+  </tr>
+</table>
+
+### 指令
+
+---
+
+### `x-data`
+
+**範例:** `<div x-data="{ foo: 'bar' }">...</div>`
+
+**結構:** `<div x-data="[JSON data object]">...</div>`
+
+`x-data` 宣告了一個新的組件作用域,它告訴框架得使用其作用域下的資料物件初始化一個新的組件。
+
+想想看,是不是就和 Vue 組件的 `data` 屬性一樣。
+
+**擷取組件邏輯**
+
+你可以將資料(與行為)擷取到可重用的函數中:
+
+```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>
+```
+
+> **對於 bundler 使用者來說**,得注意 Alpine.js 存取的函數是在全域範圍內的(`window`),你需要明確地將你的函數分配給 `window`,以便透過 `x-data` 來使用它們。例如:`window.dropdown = function () {}`(這是因為在 Webpack 、Rollup 與 Parcel 這些 module 中,你所定義的函數將是預設的 module 範圍,而不是 `window` )。
+
+你也可以使用物件解構來混合多個資料物件:
+
+```html
+<div x-data="{...dropdown(), ...tabs()}">
+```
+
+---
+
+### `x-init`
+**範例:** `<div x-data="{ foo: 'bar' }" x-init="foo = 'baz'"></div>`
+
+**結構:** `<div x-data="..." x-init="[expression]"></div>`
+
+`x-init` 會在組件初始化時運作一個運算式。
+
+如果你希望在 Alpine 對 DOM 進行初始化更新之後,再運作程式碼(類似於 VueJS 中的 `mounted()` 鉤子),你可以從 `x-init` 中回傳一個回呼,然後再運作:
+
+`x-init="() => { // 我們可以在這裡存取到 post-dom-initialization 後的狀態 // }"`
+
+---
+
+### `x-show`
+**範例:** `<div x-show="open"></div>`
+
+**結構:** `<div x-show="[expression]"></div>`
+
+`x-show` 會在元素上切換顯示 `display: none;` 樣式,這取決於運算式為 `true` 或 `false` 。
+
+**x-show.transition**
+
+`x-show.transition` 是一個便利的 API ,在 CSS 轉場上你能愉快地使用`x-show` 。
+
+```html
+<div x-show.transition="open">
+	這些內容的出現與消失都將經過轉場。
+</div>
+```
+
+| 指令 | 功能描述 |
+| --- | --- |
+| `x-show.transition` | 同步淡出與縮放 (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` | 只有「in」轉場。 |
+| `x-show.transition.out` | 只有「out」轉場。 |
+| `x-show.transition.opacity` | 只使用淡出。 |
+| `x-show.transition.scale` | 只使用縮放。 |
+| `x-show.transition.scale.75` | 自定 CSS 縮放`transform: scale(.75)` 。 |
+| `x-show.transition.duration.200ms` | 將「in」轉場設定為 200毫秒。「out」將設定為 in 的一半(100 毫秒)。 |
+| `x-show.transition.origin.top.right` |自訂 CSS 轉場起始點`transform-origin: top right`. |
+| `x-show.transition.in.duration.200ms.out.duration.50ms` | 替「in」與「out」設定不同的持續時間。 |
+
+> 備註:這些轉場的修飾器都是可以相互配合使用,這個範例是可行的(雖然很荒謬,呵呵):`x-show.transition.in.duration.100ms.origin.top.right.opacity.scale.85.out.duration.200ms.origin.bottom.left.opacity.scale.95`
+
+> 備註:`x-show` 將等待任何子轉場完成,如果你想繞過這個行為,請添加 `.immediate` 修飾器:
+ 
+```html
+<div x-show.immediate="open">
+    <div x-show.transition="open">
+</div>
+```
+---
+
+### `x-bind`
+
+> 備註:你可以自由使用較短的「:」 語法:`:type="..."`
+
+**範例:** `<input x-bind:type="inputType">`
+
+**結構:** `<input x-bind:[attribute]="[expression]">`
+
+`x-bind` 將一個屬性的數值設定為 JavaScript 運算式的結果。這個運算式可以存取組件的資料物件的所有鍵,並且每次更新資料時都會同步更新。
+
+> 備註:屬性繫結只有在其依賴的關係被更新時才會更新,這個框架能從明地觀察到資料的變化,並檢測那些繫結能夠關係到它們。
+
+**用於類別屬性的`x-bind`**
+
+`x-bind` 與 `class` 屬性在繫結時的行為會有點不同。
+
+對於類別而言,當你傳遞一個物件,這個物件的其鍵為類別名稱,值則是布林運算式來決定是否應該要應用這些類別名稱。
+
+舉例來說:
+`<div x-bind:class="{ 'hidden': foo }"></div>`
+
+在這個範例中,只有當 `foo` 資料屬性的值為 `true` 時,hidden 類別才會被應用。
+
+**用於布林屬性的 `x-bind`**
+
+`x-bind` 支援與數值屬性相同的布林屬性,使用變數作為條件或任何可以解析為 `true` 或 `false` 的 JavaScript 運算式。
+
+舉例來說:
+```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>
+```
+
+這將在 `myVar` 為 true 或 false 時分別添加或刪除 `disabled` 屬性。
+
+布林屬性將支援符合 [HTML 規範](https://html.spec.whatwg.org/multipage/indices.html#attributes-3:boolean-attribute) 的屬性,例如:`disabled`、 `readonly`、 `required`、 `checked`、 `hidden`、 `selected` 與 `open` 等。
+
+---
+
+### `x-on`
+
+> 備註:你可以自由使用較短的「@」 語法:`@click="..."`
+
+**範例:** `<button x-on:click="foo = 'bar'"></button>`
+
+**結構:** `<button x-on:[event]="[expression]"></button>`
+
+`x-on` 會將一個事件監聽器附加在它所宣告的元素上頭,當這個事件被觸發時,在其值所設定的 JavaScript 運算式將會被執行。
+
+如果任何資料因為這個運算式而被修改,那麼與這個被修改的資料所「繫結」的元素屬性也會跟著更新。
+
+**`keydown` 修飾器**
+**範例:** `<input type="text" x-on:keydown.escape="open = false">`
+
+你可以使用附加到 `x-on:keydown` 指令中的 keydown 修飾器,來指定要監聽的特定鍵值。請注意,這些修飾器的 `Event.key` 數值的命名方式是短橫線連接式。
+
+例如: `enter`, `escape`, `arrow-up`, `arrow-down`
+
+> 備註:你也可已經聽系統修飾器的鍵值組合,像是:`x-on:keydown.cmd.enter="foo"`
+
+**`.away` 修飾器**
+**範例:** `<div x-on:click.away="showModal = false"></div>`
+
+當 `.away` 修飾器存在時,事件處理程序只有在事件的起源為非其自身或其子項時才會被執行。
+
+當使用者點擊下拉式選單與互動式視窗時,這對於隱藏它們是非常有用的。
+
+**`.prevent` 修飾器**
+**範例:** `<input type="checkbox" x-on:click.prevent>`
+
+在事件監聽器中加入 `.prevent` 後,將在觸發事件時呼叫 `preventDefault` 。上述的範例意味著,當使用者點擊核取方塊時,這個核取方塊並不會被選中。
+
+**`.stop` 修飾器**
+**範例:** `<div x-on:click="foo = 'bar'"><button x-on:click.stop></button></div>`
+
+在事件監聽器中加入 `.stop` 後,將在觸發事件時呼叫 `stopPropagation` 。上述的範例意味著,「點擊」事件不會從按鈕上升到外部的 `<div>` 。換句話說,當使用者點擊按鈕時,`foo` 並不會被設定為 `'bar'` 。
+
+**`.self` 修飾器**
+**範例:** `<div x-on:click.self="foo = 'bar'"><button></button></div>`
+
+只有當 `$event.target` 是元素本身時,在事件監聽器中加入 `.self` 才會觸發處理程序。上述的範例意味著,按鈕的點擊事件將 **不會** 運作到外部 `<div>` 的處理程序。 
+
+**`.window` 修飾器**
+**範例:** `<div x-on:resize.window="isOpen = window.outerWidth > 768 ? false : open"></div>`
+
+在事件監聽器中加入 `.window` 後,將會把監聽器加入到全域的 window 物件上,而不是被它所宣告的 DOM 節點上頭。這對於你想在視窗發生變化時修改組件狀態時會非常好用,比如:調整大小的事件。在這個範例中,當視窗寬度大於 768px 時,我們將關閉互動式窗/下拉式選單,否則保持相同的狀態。
+
+>備註:你也可以使用 `.document` 修飾器,將監聽器加入到 `document` 中,而不是加入到 `window` 中。
+
+**`.once` 修飾器**
+**範例:** `<button x-on:mouseenter.once="fetchSomething()"></button>`
+
+在事件監聽器中加入 `.once` 修飾器後,將可以保證監聽器只被處理一次。這對於某些你只想執行一次的事件會非常有用處,比如獲取 HTML partials 之類的。
+
+**`.debounce` 修飾器**
+**範例:** `<input x-on:input.debounce="fetchSomething()">`
+
+`debounce` 修飾器允許你替事件處理程序加上「防彈跳」機制,換而言之,事件處理程序將不會立即被運作,它會自事件觸發後等待一段時間才會開始運作。當處理程序準備好被呼叫時,最後一個處理程序的呼叫將被執行。
+
+預設的 debounce 等待時間是 250 毫秒。
+
+如果你想自訂這個時間,可以接著指定一個你所想要的時間,比如這樣:
+
+```
+<input x-on:input.debounce.750="fetchSomething()">
+<input x-on:input.debounce.750ms="fetchSomething()">
+```
+
+---
+
+### `x-model`
+**範例:** `<input type="text" x-model="foo">`
+
+**結構:** `<input type="text" x-model="[data item]">`
+
+`x-model` 可以替元素增加「雙向資料繫結」。換而言之,輸入元素的數值將會與組件的資料項目的數值保持同步。
+
+> 備註:`x-model` 將會聰明地察覺到 inputs 、 checkboxes 、 radio buttons 、 textareas 、 selects 以及 multiple selects 的變化。在這些情形下,`x-model` 的行為將會跟
+ [Vue](https://cn.vuejs.org/v2/guide/forms.html) 有著一樣的表現。
+ 
+**`.debounce` 修飾器**
+**範例:** `<input x-model.debounce="search">`
+
+`debounce` 修飾器允許你替事件處理程序加上「防彈跳」機制,換而言之,事件處理程序將不會立即被運作,它會自事件觸發後等待一段時間才會開始運作。當處理程序準備好被呼叫時,最後一個處理程序的呼叫將被執行。
+
+預設的 debounce 等待時間是 250 毫秒。
+
+如果你想自訂這個時間,可以接著指定一個你所想要的時間,比如這樣:
+
+```
+<input x-model.debounce.750="search">
+<input x-model.debounce.750ms="search">
+```
+
+---
+
+### `x-text`
+**範例:** `<span x-text="foo"></span>`
+
+**結構:** `<span x-text="[expression]"`
+
+`x-text` 的工作原理與 `x-bind` 類似,只是它不會更新元素的值,而是更新元素的 `innerText` 。
+
+---
+
+### `x-html`
+**範例:** `<span x-html="foo"></span>`
+
+**結構:** `<span x-html="[expression]"`
+
+`x-text` 的工作原理與 `x-bind` 類似,只是它不會更新元素的值,而是更新元素的 `innerHTML` 。
+
+> :warning: **你只能在可以信賴的來源使用這個特性,絕對不能將使用者輸入的內容用於其中** :warning:
+>
+> 使用第三方內容動態生成的 HTML 很容易導致 [XSS](https://developer.mozilla.org/zh-TW/docs/Glossary/Cross-site_scripting) 漏洞的發生。
+
+---
+
+### `x-ref`
+**範例:** `<div x-ref="foo"></div><button x-on:click="$refs.foo.innerText = 'bar'"></button>`
+
+**結構:** `<div x-ref="[ref name]"></div><button x-on:click="$refs.[ref name].innerText = 'bar'"></button>`
+
+`x-ref` 提供了一個便利的方法讓你從你的組件中獲取原始的 DOM 元素。透過在一個元素上設定一個 `x-ref` 屬性,你就可以在一個叫做 `$refs` 的物件中呼叫所有的事件處理程序。
+
+這是個很有用的替代方案,可以取代設定 id 以及到處使用 `document.querySelector` 來選取 id 。
+
+> 備註:如果你需要的話,也可以為 x-ref 綁定動態數值:`<span :x-ref="item.id"></span>` 。
+
+---
+
+### `x-if`
+**範例:** `<template x-if="true"><div>Some Element</div></template>`
+
+**結構:** `<template x-if="[expression]"><div>Some Element</div></template>`
+
+對於 `x-show` 還不足夠的情形(如果元素為 false 的話,`x-show` 將元素設定為 `display: none`),`x-if` 可以用來從 DOM 中完全刪除元素。
+
+重要的是,`x-if` 必須使用在 `<template></template>` 標籤上,因為 Alpine 不使用虛擬 DOM 。因為這種實作方式可以使 Alpine 保持穩定,並使用真正的 DOM 來發揮 Alpine 的奇效。
+
+> 備註:`x-if` 必須使用在 `<template></template>` 標籤內擁有根元素的情形下。
+
+---
+
+### `x-for`
+**範例:**
+```html
+<template x-for="item in items" :key="item">
+    <div x-text="item"></div>
+</template>
+```
+
+> 備註:`:key` 的繫結是可選的,但強烈建議你使用。
+
+`x-for` 適用於你想為陣列中每一個成員創建新的 DOM 節點的情形。這應該與 Vue 的 `v-for` 類似,但有一個例外,就是它需要存在於 `template` 標籤上而不是普通的 DOM 元素。
+
+如果你想存取當下的索引,請使用下列語法:
+
+
+```html
+<template x-for="(item, index) in items" :key="index">
+    <!-- 如果你需要的話,也可以在疊代的內部使用「index」-->
+    <div x-text="index"></div>
+</template>
+```
+
+> 備註:`x-for` 必須使用在 `<template></template>` 標籤內擁有根元素的情形下。
+
+
+#### 巢套 `x-for`
+
+你也可以巢狀地使用 `x-for` 迴圈,但你必須將每個迴圈都包裹在同一個元素中,比如說:
+
+
+```html
+<template x-for="item in items">
+    <div>
+        <template x-for="subItem in item.subItems">
+            <div x-text="subItem"></div>
+        </template>
+    </div>
+</template>
+```
+
+---
+
+### `x-transition`
+**範例:**
+```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>
+```
+
+> 上述範例使用了 [Tailwind CSS](https://tailwindcss.com) 中的類別。
+
+Alpine 提供了 6 種不同的轉場指令,用於將類別應用到元素在「隱藏」與「顯示」狀態之間的不同階段。這些指令可以與 `x-show` 和 `x-if` 同時使用。
+
+這些指令的行為與 VueJs 的轉場指令完全相同,只是它們的名字不同,更加地合理:
+
+| 指令 | 功能描述 |
+| --- | --- |
+| `:enter` | 在整個 entering 階段時應用。 |
+| `:enter-start` | 在元素插入前添加,在元素插入後刪除一幀。 |
+| `:enter-end` | 元素在插入後添加一幀(同時刪除 `enter-start` ),在轉場/動畫結束時刪除。|
+| `:leave` | 在整個 leaving 階段時應用。|
+| `:leave-start` | 觸發 leaving 轉場時立即添加,一幀後刪除。 |
+| `:leave-end` | 觸發 leaving 轉場後添加一幀(同時刪除 `leave-start` ),在轉場/動畫結束時刪除。 |
+
+---
+
+### `x-cloak`
+**Example:** `<div x-data="{}" x-cloak></div>`
+
+`x-cloak` attributes are removed from elements when Alpine initializes. This is useful for hiding pre-initialized DOM. It's typical to add the following global style for this to work:
+
+```html
+<style>
+    [x-cloak] { display: none; }
+</style>
+```
+
+### 魔術屬性
+
+---
+
+### `$el`
+**範例:**
+```html
+<div x-data>
+    <button @click="$el.innerHTML = 'foo'">Replace me with "foo"</button>
+</div>
+```
+
+`$el` 是一個魔術屬性,可以用來檢索根組件的 DOM 節點。
+
+### `$refs`
+**範例:**
+```html
+<span x-ref="foo"></span>
+
+<button x-on:click="$refs.foo.innerText = 'bar'"></button>
+```
+
+`$refs` 是一個很有用的魔術屬性,用於檢索組件內有標注 `x-ref` 的 DOM 元素。
+
+---
+
+### `$event`
+**範例:**
+```html
+<input x-on:input="alert($event.target.value)">
+```
+
+`$event` 是一個魔術屬性,可以在事件監聽器中用它來檢索原生瀏覽器的 Event 物件。
+
+---
+
+### `$dispatch`
+**範例:**
+```html
+<div @custom-event="console.log($event.detail.foo)">
+    <button @click="$dispatch('custom-event', { foo: 'bar' })">
+    <!-- 當點擊後, bar 將會出現在 console.log 上-->
+</div>
+```
+
+`$dispatch` 是一個創建 `CustomEvent` 並且在其內部使用 `.dispatchEvent()` 的便利方法。使用自訂事件在組件之間傳遞資料有很多好的使用案例,[閱讀這裡](https://developer.mozilla.org/zh-TW/docs/Web/Guide/Events/Creating_and_triggering_events) 了解更多關於瀏覽器底層 `CustomEvent` 系統的資訊。
+
+你可能發現了,任何作為第二個參數傳入到 `$dispatch('some-event', { some: 'data' })` 的資料,都會透過新的事件「detail」屬性來提供:`$event.detail.some` 。將自訂的事件資料附加到 `.detail` 屬性是瀏覽器中 `CustomEvent` 的標準做法。[閱讀這裡](https://developer.mozilla.org/zh-TW/docs/Web/API/CustomEvent/detail) 了解更多資訊。
+
+
+你也可以使用 `$dispatch()` 來觸發 `x-model` 所繫結的資料更新,比如說:
+
+```html
+<div x-data="{ foo: 'bar' }">
+    <span x-model="foo">
+        <button @click="$dispatch('input', 'baz')">
+        <!-- 點擊按鈕後,`x-model` 將捕捉到冒出的「輸入」事件,並將 foo 更新為 「baz」 -->
+    </span>
+</div>
+```
+
+---
+
+### `$nextTick`
+**範例:**
+```html
+<div x-data="{ fruit: 'apple' }">
+    <button
+        x-on:click="
+            fruit = 'pear';
+            $nextTick(() => { console.log($event.target.innerText) });
+        "
+        x-text="fruit"
+    ></button>
+</div>
+```
+
+`$nextTick` 是一個魔術屬性,它允許你只在 Alpine 進行回應式 DOM 更新後才執行給定的運算式。這對於你想在 DOM 狀態回應了你的任何資料更新後,再與之互動的情形下非常有用。
+
+---
+
+### `$watch`
+**範例:**
+```html
+<div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))">
+    <button @click="open = ! open">Toggle Open</button>
+</div>
+```
+
+你可以使用 `$watch` 魔術方法來「監視」一個組件的屬性。在上述的範例中,當按鈕被點擊和 `open` 被改變時,你所給予的回呼將會被觸發,並且 `console.log` 將會顯示新的數值。
+
+
+## 安全性
+
+如果你發現了安全性漏洞,請發送電子郵件至 [calebporzio@gmail.com]() 。
+
+Alpine 依賴使用於 `Function` 物件的自訂實作來求值於其指令。儘管它比 `eval()` 安全得多,但在某些環境中,比如:Google Chrome 應用程式,使用了限制性的網頁內容安全政策(CSP),來禁止使用它。
+
+如果你在處理機敏資料並在要求 [CSP](https://csp.withgoogle.com/docs/strict-csp.html) 的網站上使用 Alpine ,則需要在政策中加入`unsafe-eval` 。正確設定可靠的政策有助於保護使用者在使用個人或財產相關的資料時的安全性。
+
+由於政策適用於網頁中的所有腳本,因此對網站中包含的其他外部程式庫必須要仔細地審查,以保證它們都是值得信賴的,並且它們不會因為使用 `eval()` 函數或控制 DOM 在頁面中注入惡意程式碼而造成跨網站指令碼的漏洞。
+ 
+## V3 路線圖
+* 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`?
+
+## 授權條款
+
+Copyright © 2019-2020 Caleb Porzio and contributors
+
+Licensed under the MIT license, see [LICENSE.md](LICENSE.md) for details.
+
+## 翻譯對照表
+
+| 英文 | 此文件採用譯名 |
+| --- | --- |
+| animation | 動畫 |
+| binding | 繫結 |
+| callback | 回呼 |
+| components | 組件 |
+| declarative | 宣告式 |
+| destructuring | 解構 |
+| debounce | 防彈跳 |
+| dropdown | 下拉式選單 |
+| expression | 運算式 |
+| handler | 處理程序 |
+| insert | 插入 |
+| kebab-case | 短橫線連接式 |
+| listener | 監聽器 |
+| modifier | 修飾器 |
+| modals | 互動式視窗 |
+| nesting | 巢套 |
+| package | 軟體包 |
+| reactive | 回應式 |
+| tabs | 頁籤 |
+| transition | 轉場 |
+| two-way data binding | 雙向資料繫結 |

+ 2 - 2
src/component.js

@@ -17,7 +17,7 @@ export default class Component {
         const dataExpression = dataAttr === '' ? '{}' : dataAttr
         const initExpression = this.$el.getAttribute('x-init')
 
-        this.unobservedData = seedDataForCloning ? seedDataForCloning : saferEval(dataExpression, {})
+        this.unobservedData = seedDataForCloning ? seedDataForCloning : saferEval(dataExpression, { $el: this.$el })
 
         /* IE11-ONLY:START */
             // For IE11, add our magic properties to the original data for access.
@@ -340,7 +340,7 @@ export default class Component {
                 if (! (closestParentComponent && closestParentComponent.isSameNode(this.$el))) continue
 
                 if (mutations[i].type === 'attributes' && mutations[i].attributeName === 'x-data') {
-                    const rawData = saferEval(mutations[i].target.getAttribute('x-data'), {})
+                    const rawData = saferEval(mutations[i].target.getAttribute('x-data'), { $el: this.$el })
 
                     Object.keys(rawData).forEach(key => {
                         if (this.$data[key] !== rawData[key]) {

+ 2 - 8
src/directives/for.js

@@ -1,7 +1,7 @@
-import { transitionIn, transitionOut, getXAttrs, saferEval } from '../utils'
+import { transitionIn, transitionOut, getXAttrs, warnIfMalformedTemplate } from '../utils'
 
 export function handleForDirective(component, templateEl, expression, initialUpdate, extraVars) {
-    warnIfNotTemplateTag(templateEl)
+    warnIfMalformedTemplate(templateEl, 'x-for')
 
     let iteratorNames = typeof expression === 'function'
         ? parseForExpression(component.evaluateReturnExpression(templateEl, expression))
@@ -85,10 +85,6 @@ function generateKeyForIteration(component, el, index, iterationScopeVariables)
     return component.evaluateReturnExpression(el, bindKeyAttribute.expression, () => iterationScopeVariables)
 }
 
-function warnIfNotTemplateTag(el) {
-    if (el.tagName.toLowerCase() !== 'template') console.warn('Alpine: [x-for] directive should only be added to <template> tags.')
-}
-
 function evaluateItemsAndReturnEmptyIfXIfIsPresentAndFalseOnElement(component, el, iteratorNames, extraVars) {
     let ifAttribute = getXAttrs(el, component, 'if')[0]
 
@@ -102,8 +98,6 @@ function evaluateItemsAndReturnEmptyIfXIfIsPresentAndFalseOnElement(component, e
 function addElementInLoopAfterCurrentEl(templateEl, currentEl) {
     let clone = document.importNode(templateEl.content, true  )
 
-    if (clone.childElementCount !== 1) console.warn('Alpine: <template> tag with [x-for] encountered with multiple element roots. Make sure <template> only has a single child node.')
-
     currentEl.parentElement.insertBefore(clone, currentEl.nextElementSibling)
 
     return currentEl.nextElementSibling

+ 2 - 2
src/directives/if.js

@@ -1,7 +1,7 @@
-import { transitionIn, transitionOut } from '../utils'
+import { transitionIn, transitionOut, warnIfMalformedTemplate } from '../utils'
 
 export function handleIfDirective(component, el, expressionResult, initialUpdate, extraVars) {
-    if (el.nodeName.toLowerCase() !== 'template') console.warn(`Alpine: [x-if] directive should only be added to <template> tags. See https://github.com/alpinejs/alpine#x-if`)
+    warnIfMalformedTemplate(el, 'x-if')
 
     const elementHasAlreadyBeenAdded = el.nextElementSibling && el.nextElementSibling.__x_inserted_me === true
 

+ 2 - 0
src/polyfills.js

@@ -6,3 +6,5 @@ import "element-remove"
 import "classlist-polyfill"
 import "@webcomponents/template"
 import "custom-event-polyfill"
+
+SVGElement.prototype.contains = SVGElement.prototype.contains || HTMLElement.prototype.contains

+ 8 - 0
src/utils.js

@@ -20,6 +20,14 @@ export function isTesting() {
         || navigator.userAgent.includes("jsdom")
 }
 
+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}`)
+    } else if (el.content.childElementCount !== 1) {
+        console.warn(`Alpine: <template> tag with [${directive}] encountered with multiple element roots. Make sure <template> only has a single child node.`)
+    }
+}
+
 export function kebabCase(subject) {
     return subject.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[_\s]/, '-').toLowerCase()
 }

+ 32 - 0
test/constructor.spec.js

@@ -279,6 +279,38 @@ test('auto-detect x-data property changes at run-time', async () => {
     await wait(() => { expect(document.querySelector('span').innerText).toEqual(1) })
 })
 
+test('can use $el when changing x-data property at run-time', async () => {
+    var runObservers = []
+
+    global.MutationObserver = class {
+        constructor(callback) { runObservers.push(callback) }
+        observe() {}
+    }
+
+    document.body.innerHTML = `
+        <div x-data="{ count: '0' }" data-count="1">
+            <span x-text="count"></span>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('span').innerText).toEqual('0')
+
+    document.querySelector('div').setAttribute('x-data', '{ count: $el.dataset.count }')
+
+    runObservers[0]([
+        {
+            addedNodes: [],
+            type: 'attributes',
+            attributeName: 'x-data',
+            target: document.querySelector('div')
+        }
+    ])
+
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('1') })
+})
+
 test('nested components only get registered once on initialization', async () => {
     global.MutationObserver = class {
         constructor(callback) {}

+ 12 - 0
test/data.spec.js

@@ -48,6 +48,18 @@ test('x-data can use attributes from a reusable function', async () => {
     expect(document.querySelector('span').innerText).toEqual('bar')
 })
 
+test('x-data can use $el', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ text: $el.dataset.text }" data-text="test">
+          <span x-text="text"></span>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('span').innerText).toEqual('test')
+})
+
 test('functions in x-data are reactive', async () => {
     document.body.innerHTML = `
         <div x-data="{ foo: 'bar', getFoo() {return this.foo}}">