jbilcke-hf HF Staff commited on
Commit
7da13d6
Β·
1 Parent(s): 296b17c
Dockerfile CHANGED
@@ -23,5 +23,5 @@ RUN npm install
23
  # Copy the current directory contents into the container at $HOME/app setting the owner to the user
24
  COPY --chown=user . $HOME/app
25
 
26
- EXPOSE 7860
27
  CMD [ "npm", "run", "start" ]
 
23
  # Copy the current directory contents into the container at $HOME/app setting the owner to the user
24
  COPY --chown=user . $HOME/app
25
 
26
+ EXPOSE 3000
27
  CMD [ "npm", "run", "start" ]
README.md CHANGED
@@ -5,7 +5,7 @@ colorFrom: blue
5
  colorTo: yellow
6
  sdk: docker
7
  pinned: false
8
- app_port: 7860
9
  ---
10
 
11
  Generate Hugging Face Spaces using deepseek-ai/DeepSeek-V3-0324
@@ -18,7 +18,7 @@ See this project as "Hugging Face Space templates on steroids".
18
  ## Local prompt examples
19
 
20
  ```
21
- http://localhost:7860/?prompt=A%20simple%20page%20to%20compute%20the%20BMI%20(use%20SI%20units)
22
  ```
23
 
24
  # Installation
@@ -40,5 +40,5 @@ This script is a shortcut executing the following commands:
40
 
41
  ```bash
42
  docker build -t space-factory .
43
- docker run -it -p 7860:7860 space-factory
44
  ```
 
5
  colorTo: yellow
6
  sdk: docker
7
  pinned: false
8
+ app_port: 3000
9
  ---
10
 
11
  Generate Hugging Face Spaces using deepseek-ai/DeepSeek-V3-0324
 
18
  ## Local prompt examples
19
 
20
  ```
21
+ http://localhost:3000/?prompt=A%20simple%20page%20to%20compute%20the%20BMI%20(use%20SI%20units)
22
  ```
23
 
24
  # Installation
 
40
 
41
  ```bash
42
  docker build -t space-factory .
43
+ docker run -it -p 3000:3000 space-factory
44
  ```
docs/alpinejs/Async.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Async
2
+ =====
3
+
4
+ Alpine is built to support asynchronous functions in most places it supports standard ones.
5
+
6
+ For example, let's say you have a simple function called `getLabel()` that you use as the input to an `x-text` directive:
7
+
8
+ function getLabel() { return 'Hello World!'}
9
+ function getLabel() {
10
+ return 'Hello World!'
11
+ }
12
+
13
+ <span x-text="getLabel()"></span>
14
+ <span x-text="getLabel()"></span>
15
+
16
+ Because `getLabel` is synchronous, everything works as expected.
17
+
18
+ Now let's pretend that `getLabel` makes a network request to retrieve the label and can't return one instantaneously (asynchronous). By making `getLabel` an async function, you can call it from Alpine using JavaScript's `await` syntax.
19
+
20
+ async function getLabel() { let response = await fetch('/api/label')Β  return await response.text()}
21
+ async function getLabel() {
22
+ let response = await fetch('/api/label')
23
+
24
+ return await response.text()
25
+ }
26
+
27
+ <span x-text="await getLabel()"></span>
28
+ <span x-text="await getLabel()"></span>
29
+
30
+ Additionally, if you prefer calling methods in Alpine without the trailing parenthesis, you can leave them out and Alpine will detect that the provided function is async and handle it accordingly. For example:
31
+
32
+ <span x-text="getLabel"></span>
33
+ <span x-text="getLabel"></span>
34
+
35
+ [← Extending](/advanced/extending)
36
+
37
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/Lifecycle.md ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Lifecycle
2
+ =========
3
+
4
+ Alpine has a handful of different techniques for hooking into different parts of its lifecycle. Let's go through the most useful ones to familiarize yourself with:
5
+
6
+ [Element initialization](#element-initialization)
7
+ -------------------------------------------------
8
+
9
+ Another extremely useful lifecycle hook in Alpine is the `x-init` directive.
10
+
11
+ `x-init` can be added to any element on a page and will execute any JavaScript you call inside it when Alpine begins initializing that element.
12
+
13
+ <button x-init="console.log('Im initing')">
14
+ <button x-init="console.log('Im initing')">
15
+
16
+ In addition to the directive, Alpine will automatically call any `init()` methods stored on a data object. For example:
17
+
18
+ Alpine.data('dropdown', () => ({ init() { // I get called before the element using this data initializes. }}))
19
+ Alpine.data('dropdown', () => ({
20
+ init() {
21
+ // I get called before the element using this data initializes.
22
+ }
23
+ }))
24
+
25
+ [After a state change](#after-a-state-change)
26
+ ---------------------------------------------
27
+
28
+ Alpine allows you to execute code when a piece of data (state) changes. It offers two different APIs for such a task: `$watch` and `x-effect`.
29
+
30
+ ### [`$watch`](#watch)
31
+
32
+ <div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))">
33
+ <div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))">
34
+
35
+ As you can see above, `$watch` allows you to hook into data changes using a dot-notation key. When that piece of data changes, Alpine will call the passed callback and pass it the new value. along with the old value before the change.
36
+
37
+ [β†’ Read more about $watch](/magics/watch)
38
+
39
+ ### [`x-effect`](#x-effect)
40
+
41
+ `x-effect` uses the same mechanism under the hood as `$watch` but has very different usage.
42
+
43
+ Instead of specifying which data key you wish to watch, `x-effect` will call the provided code and intelligently look for any Alpine data used within it. Now when one of those pieces of data changes, the `x-effect` expression will be re-run.
44
+
45
+ Here's the same bit of code from the `$watch` example rewritten using `x-effect`:
46
+
47
+ <div x-data="{ open: false }" x-effect="console.log(open)">
48
+ <div x-data="{ open: false }" x-effect="console.log(open)">
49
+
50
+ Now, this expression will be called right away, and re-called every time `open` is updated.
51
+
52
+ The two main behavioral differences with this approach are:
53
+
54
+ 1. The provided code will be run right away AND when data changes (`$watch` is "lazy" -- won't run until the first data change)
55
+ 2. No knowledge of the previous value. (The callback provided to `$watch` receives both the new value AND the old one)
56
+
57
+ [β†’ Read more about x-effect](/directives/effect)
58
+
59
+ [Alpine initialization](#alpine-initialization)
60
+ -----------------------------------------------
61
+
62
+ ### [`alpine:init`](#alpine-initializing)
63
+
64
+ Ensuring a bit of code executes after Alpine is loaded, but BEFORE it initializes itself on the page is a necessary task.
65
+
66
+ This hook allows you to register custom data, directives, magics, etc. before Alpine does its thing on a page.
67
+
68
+ You can hook into this point in the lifecycle by listening for an event that Alpine dispatches called: `alpine:init`
69
+
70
+ document.addEventListener('alpine:init', () => { Alpine.data(...)})
71
+ document.addEventListener('alpine:init', () => {
72
+ Alpine.data(...)
73
+ })
74
+
75
+ ### [`alpine:initialized`](#alpine-initialized)
76
+
77
+ Alpine also offers a hook that you can use to execute code AFTER it's done initializing called `alpine:initialized`:
78
+
79
+ document.addEventListener('alpine:initialized', () => { //})
80
+ document.addEventListener('alpine:initialized', () => {
81
+ //
82
+ })
83
+
84
+ [← Events](/essentials/events)
85
+
86
+ [x-data β†’](/directives/data)
87
+
88
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/_Events.md ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Events
2
+ ======
3
+
4
+ Alpine makes it simple to listen for browser events and react to them.
5
+
6
+ [Listening for simple events](#listening-for-simple-events)
7
+ -----------------------------------------------------------
8
+
9
+ By using `x-on`, you can listen for browser events that are dispatched on or within an element.
10
+
11
+ Here's a basic example of listening for a click on a button:
12
+
13
+ <button x-on:click="console.log('clicked')">...</button>
14
+ <button x-on:click="console.log('clicked')">...</button>
15
+
16
+ As an alternative, you can use the event shorthand syntax if you prefer: `@`. Here's the same example as before, but using the shorthand syntax (which we'll be using from now on):
17
+
18
+ <button @click="...">...</button>
19
+ <button @click="...">...</button>
20
+
21
+ In addition to `click`, you can listen for any browser event by name. For example: `@mouseenter`, `@keyup`, etc... are all valid syntax.
22
+
23
+ [Listening for specific keys](#listening-for-specific-keys)
24
+ -----------------------------------------------------------
25
+
26
+ Let's say you wanted to listen for the `enter` key to be pressed inside an `<input>` element. Alpine makes this easy by adding the `.enter` like so:
27
+
28
+ <input @keyup.enter="...">
29
+ <input @keyup.enter="...">
30
+
31
+ You can even combine key modifiers to listen for key combinations like pressing `enter` while holding `shift`:
32
+
33
+ <input @keyup.shift.enter="...">
34
+ <input @keyup.shift.enter="...">
35
+
36
+ [Preventing default](#preventing-default)
37
+ -----------------------------------------
38
+
39
+ When reacting to browser events, it is often necessary to "prevent default" (prevent the default behavior of the browser event).
40
+
41
+ For example, if you want to listen for a form submission but prevent the browser from submitting a form request, you can use `.prevent`:
42
+
43
+ <form @submit.prevent="...">...</form>
44
+ <form @submit.prevent="...">...</form>
45
+
46
+ You can also apply `.stop` to achieve the equivalent of `event.stopPropagation()`.
47
+
48
+ [Accessing the event object](#accessing-the-event-object)
49
+ ---------------------------------------------------------
50
+
51
+ Sometimes you may want to access the native browser event object inside your own code. To make this easy, Alpine automatically injects an `$event` magic variable:
52
+
53
+ <button @click="$event.target.remove()">Remove Me</button>
54
+ <button @click="$event.target.remove()">Remove Me</button>
55
+
56
+ [Dispatching custom events](#dispatching-custom-events)
57
+ -------------------------------------------------------
58
+
59
+ In addition to listening for browser events, you can dispatch them as well. This is extremely useful for communicating with other Alpine components or triggering events in tools outside of Alpine itself.
60
+
61
+ Alpine exposes a magic helper called `$dispatch` for this:
62
+
63
+ <div @foo="console.log('foo was dispatched')"> <button @click="$dispatch('foo')"></button></div>
64
+ <div @foo="console.log('foo was dispatched')">
65
+ <button @click="$dispatch('foo')"></button>
66
+ </div>
67
+
68
+ As you can see, when the button is clicked, Alpine will dispatch a browser event called "foo", and our `@foo` listener on the `<div>` will pick it up and react to it.
69
+
70
+ [Listening for events on window](#listening-for-events-on-window)
71
+ -----------------------------------------------------------------
72
+
73
+ Because of the nature of events in the browser, it is sometimes useful to listen to events on the top-level window object.
74
+
75
+ This allows you to communicate across components completely like the following example:
76
+
77
+ <div x-data> <button @click="$dispatch('foo')"></button></div>Β <div x-data @foo.window="console.log('foo was dispatched')">...</div>
78
+ <div x-data>
79
+ <button @click="$dispatch('foo')"></button>
80
+ </div>
81
+
82
+ <div x-data @foo.window="console.log('foo was dispatched')">...</div>
83
+
84
+ In the above example, if we click the button in the first component, Alpine will dispatch the "foo" event. Because of the way events work in the browser, they "bubble" up through parent elements all the way to the top-level "window".
85
+
86
+ Now, because in our second component we are listening for "foo" on the window (with `.window`), when the button is clicked, this listener will pick it up and log the "foo was dispatched" message.
87
+
88
+ [β†’ Read more about x-on](/directives/on)
89
+
90
+ [← Templating](/essentials/templating)
91
+
92
+ [Lifecycle β†’](/essentials/lifecycle)
93
+
94
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/_Reactivity.md ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Reactivity
2
+ ==========
3
+
4
+ Alpine is "reactive" in the sense that when you change a piece of data, everything that depends on that data "reacts" automatically to that change.
5
+
6
+ Every bit of reactivity that takes place in Alpine, happens because of two very important reactive functions in Alpine's core: `Alpine.reactive()`, and `Alpine.effect()`.
7
+
8
+ > Alpine uses VueJS's reactivity engine under the hood to provide these functions. [β†’ Read more about @vue/reactivity](https://github.com/vuejs/vue-next/tree/master/packages/reactivity)
9
+
10
+ Understanding these two functions will give you super powers as an Alpine developer, but also just as a web developer in general.
11
+
12
+ [Alpine.reactive()](#alpine-reactive)
13
+ -------------------------------------
14
+
15
+ Let's first look at `Alpine.reactive()`. This function accepts a JavaScript object as its parameter and returns a "reactive" version of that object. For example:
16
+
17
+ let data = { count: 1 }Β let reactiveData = Alpine.reactive(data)
18
+ let data = { count: 1 }
19
+
20
+ let reactiveData = Alpine.reactive(data)
21
+
22
+ Under the hood, when `Alpine.reactive` receives `data`, it wraps it inside a custom JavaScript proxy.
23
+
24
+ A proxy is a special kind of object in JavaScript that can intercept "get" and "set" calls to a JavaScript object.
25
+
26
+ [β†’ Read more about JavaScript proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
27
+
28
+ At face value, `reactiveData` should behave exactly like `data`. For example:
29
+
30
+ console.log(data.count) // 1console.log(reactiveData.count) // 1Β reactiveData.count = 2Β console.log(data.count) // 2console.log(reactiveData.count) // 2
31
+ console.log(data.count) // 1
32
+ console.log(reactiveData.count) // 1
33
+
34
+ reactiveData.count = 2
35
+
36
+ console.log(data.count) // 2
37
+ console.log(reactiveData.count) // 2
38
+
39
+ What you see here is that because `reactiveData` is a thin wrapper around `data`, any attempts to get or set a property will behave exactly as if you had interacted with `data` directly.
40
+
41
+ The main difference here is that any time you modify or retrieve (get or set) a value from `reactiveData`, Alpine is aware of it and can execute any other logic that depends on this data.
42
+
43
+ `Alpine.reactive` is only the first half of the story. `Alpine.effect` is the other half, let's dig in.
44
+
45
+ [Alpine.effect()](#alpine-effect)
46
+ ---------------------------------
47
+
48
+ `Alpine.effect` accepts a single callback function. As soon as `Alpine.effect` is called, it will run the provided function, but actively look for any interactions with reactive data. If it detects an interaction (a get or set from the aforementioned reactive proxy) it will keep track of it and make sure to re-run the callback if any of reactive data changes in the future. For example:
49
+
50
+ let data = Alpine.reactive({ count: 1 })Β Alpine.effect(() => { console.log(data.count)})
51
+ let data = Alpine.reactive({ count: 1 })
52
+
53
+ Alpine.effect(() => {
54
+ console.log(data.count)
55
+ })
56
+
57
+ When this code is first run, "1" will be logged to the console. Any time `data.count` changes, it's value will be logged to the console again.
58
+
59
+ This is the mechanism that unlocks all of the reactivity at the core of Alpine.
60
+
61
+ To connect the dots further, let's look at a simple "counter" component example without using Alpine syntax at all, only using `Alpine.reactive` and `Alpine.effect`:
62
+
63
+ <button>Increment</button>Β Count: <span></span>
64
+ <button>Increment</button>
65
+
66
+ Count: <span></span>
67
+
68
+ let button = document.querySelector('button')let span = document.querySelector('span')Β let data = Alpine.reactive({ count: 1 })Β Alpine.effect(() => { span.textContent = data.count})Β button.addEventListener('click', () => { data.count = data.count + 1})
69
+ let button = document.querySelector('button')
70
+ let span = document.querySelector('span')
71
+
72
+ let data = Alpine.reactive({ count: 1 })
73
+
74
+ Alpine.effect(() => {
75
+ span.textContent = data.count
76
+ })
77
+
78
+ button.addEventListener('click', () => {
79
+ data.count = data.count + 1
80
+ })
81
+
82
+ Increment
83
+
84
+ Count: 1
85
+
86
+ As you can see, you can make any data reactive, and you can also wrap any functionality in `Alpine.effect`.
87
+
88
+ This combination unlocks an incredibly powerful programming paradigm for web development. Run wild and free.
89
+
90
+ [← CSP](/advanced/csp)
91
+
92
+ [Extending β†’](/advanced/extending)
93
+
94
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/_Start Here.md ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Start Here
2
+ ==========
3
+
4
+ Create a blank HTML file somewhere on your computer with a name like: `i-love-alpine.html`
5
+
6
+ Using a text editor, fill the file with these contents:
7
+
8
+ <html><head> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script></head><body> <h1 x-data="{ message: 'I ❀️ Alpine' }" x-text="message"></h1></body></html>
9
+ <html>
10
+ <head>
11
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
12
+ </head>
13
+ <body>
14
+ <h1 x-data="{ message: 'I ❀️ Alpine' }" x-text="message"></h1>
15
+ </body>
16
+ </html>
17
+
18
+ Open your file in a web browser, if you see `I ❀️ Alpine`, you're ready to rumble!
19
+
20
+ Now that you're all set up to play around, let's look at three practical examples as a foundation for teaching you the basics of Alpine. By the end of this exercise, you should be more than equipped to start building stuff on your own. Let's goooooo.
21
+
22
+ * [Building a counter](#building-a-counter)
23
+ * [Building a dropdown](#building-a-dropdown)
24
+ * [Building a search Input](#building-a-search-input)
25
+
26
+ [Building a counter](#building-a-counter)
27
+ -----------------------------------------
28
+
29
+ Let's start with a simple "counter" component to demonstrate the basics of state and event listening in Alpine, two core features.
30
+
31
+ Insert the following into the `<body>` tag:
32
+
33
+ <div x-data="{ count: 0 }"> <button x-on:click="count++">Increment</button>Β  <span x-text="count"></span></div>
34
+ <div x-data="{ count: 0 }">
35
+ <button x-on:click="count++">Increment</button>
36
+
37
+ <span x-text="count"></span>
38
+ </div>
39
+
40
+ Increment 0
41
+
42
+ Now, you can see with 3 bits of Alpine sprinkled into this HTML, we've created an interactive "counter" component.
43
+
44
+ Let's walk through what's happening briefly:
45
+
46
+ ### [Declaring data](#declaring-data)
47
+
48
+ <div x-data="{ count: 0 }">
49
+ <div x-data="{ count: 0 }">
50
+
51
+ Everything in Alpine starts with an `x-data` directive. Inside of `x-data`, in plain JavaScript, you declare an object of data that Alpine will track.
52
+
53
+ Every property inside this object will be made available to other directives inside this HTML element. In addition, when one of these properties changes, everything that relies on it will change as well.
54
+
55
+ > `x-data` is required on a parent element for most Alpine directives to work.
56
+
57
+ [β†’ Read more about `x-data`](/directives/data)
58
+
59
+ Let's look at `x-on` and see how it can access and modify the `count` property from above:
60
+
61
+ ### [Listening for events](#listening-for-events)
62
+
63
+ <button x-on:click="count++">Increment</button>
64
+ <button x-on:click="count++">Increment</button>
65
+
66
+ `x-on` is a directive you can use to listen for any event on an element. We're listening for a `click` event in this case, so ours looks like `x-on:click`.
67
+
68
+ You can listen for other events as you'd imagine. For example, listening for a `mouseenter` event would look like this: `x-on:mouseenter`.
69
+
70
+ When a `click` event happens, Alpine will call the associated JavaScript expression, `count++` in our case. As you can see, we have direct access to data declared in the `x-data` expression.
71
+
72
+ > You will often see `@` instead of `x-on:`. This is a shorter, friendlier syntax that many prefer. From now on, this documentation will likely use `@` instead of `x-on:`.
73
+
74
+ [β†’ Read more about `x-on`](/directives/on)
75
+
76
+ ### [Reacting to changes](#reacting-to-changes)
77
+
78
+ <span x-text="count"></span>
79
+ <span x-text="count"></span>
80
+
81
+ `x-text` is an Alpine directive you can use to set the text content of an element to the result of a JavaScript expression.
82
+
83
+ In this case, we're telling Alpine to always make sure that the contents of this `span` tag reflect the value of the `count` property.
84
+
85
+ In case it's not clear, `x-text`, like most directives accepts a plain JavaScript expression as an argument. So for example, you could instead set its contents to: `x-text="count * 2"` and the text content of the `span` will now always be 2 times the value of `count`.
86
+
87
+ [β†’ Read more about `x-text`](/directives/text)
88
+
89
+ [Building a dropdown](#building-a-dropdown)
90
+ -------------------------------------------
91
+
92
+ Now that we've seen some basic functionality, let's keep going and look at an important directive in Alpine: `x-show`, by building a contrived "dropdown" component.
93
+
94
+ Insert the following code into the `<body>` tag:
95
+
96
+ <div x-data="{ open: false }"> <button @click="open = ! open">Toggle</button>Β  <div x-show="open" @click.outside="open = false">Contents...</div></div>
97
+ <div x-data="{ open: false }">
98
+ <button @click="open = ! open">Toggle</button>
99
+
100
+ <div x-show="open" @click.outside="open = false">Contents...</div>
101
+ </div>
102
+
103
+ Toggle
104
+
105
+ Contents...
106
+
107
+ If you load this component, you should see that the "Contents..." are hidden by default. You can toggle showing them on the page by clicking the "Toggle" button.
108
+
109
+ The `x-data` and `x-on` directives should be familiar to you from the previous example, so we'll skip those explanations.
110
+
111
+ ### [Toggling elements](#toggling-elements)
112
+
113
+ <div x-show="open" ...>Contents...</div>
114
+ <div x-show="open" ...>Contents...</div>
115
+
116
+ `x-show` is an extremely powerful directive in Alpine that can be used to show and hide a block of HTML on a page based on the result of a JavaScript expression, in our case: `open`.
117
+
118
+ [β†’ Read more about `x-show`](/directives/show)
119
+
120
+ ### [Listening for a click outside](#listening-for-a-click-outside)
121
+
122
+ <div ... @click.outside="open = false">Contents...</div>
123
+ <div ... @click.outside="open = false">Contents...</div>
124
+
125
+ You'll notice something new in this example: `.outside`. Many directives in Alpine accept "modifiers" that are chained onto the end of the directive and are separated by periods.
126
+
127
+ In this case, `.outside` tells Alpine to instead of listening for a click INSIDE the `<div>`, to listen for the click only if it happens OUTSIDE the `<div>`.
128
+
129
+ This is a convenience helper built into Alpine because this is a common need and implementing it by hand is annoying and complex.
130
+
131
+ [β†’ Read more about `x-on` modifiers](/directives/on#modifiers)
132
+
133
+ [Building a search input](#building-a-search-input)
134
+ ---------------------------------------------------
135
+
136
+ Let's now build a more complex component and introduce a handful of other directives and patterns.
137
+
138
+ Insert the following code into the `<body>` tag:
139
+
140
+ <div x-data="{ search: '',Β  items: ['foo', 'bar', 'baz'],Β  get filteredItems() { return this.items.filter( i => i.startsWith(this.search) ) } }"> <input x-model="search" placeholder="Search...">Β  <ul> <template x-for="item in filteredItems" :key="item"> <li x-text="item"></li> </template> </ul></div>
141
+ <div
142
+ x-data="{
143
+ search: '',
144
+
145
+ items: ['foo', 'bar', 'baz'],
146
+
147
+ get filteredItems() {
148
+ return this.items.filter(
149
+ i => i.startsWith(this.search)
150
+ )
151
+ }
152
+ }"
153
+ >
154
+ <input x-model="search" placeholder="Search...">
155
+
156
+ <ul>
157
+ <template x-for="item in filteredItems" :key="item">
158
+ <li x-text="item"></li>
159
+ </template>
160
+ </ul>
161
+ </div>
162
+
163
+ * foo
164
+ * bar
165
+ * baz
166
+
167
+ By default, all of the "items" (foo, bar, and baz) will be shown on the page, but you can filter them by typing into the text input. As you type, the list of items will change to reflect what you're searching for.
168
+
169
+ Now there's quite a bit happening here, so let's go through this snippet piece by piece.
170
+
171
+ ### [Multi line formatting](#multi-line-formatting)
172
+
173
+ The first thing I'd like to point out is that `x-data` now has a lot more going on in it than before. To make it easier to write and read, we've split it up into multiple lines in our HTML. This is completely optional and we'll talk more in a bit about how to avoid this problem altogether, but for now, we'll keep all of this JavaScript directly in the HTML.
174
+
175
+ ### [Binding to inputs](#binding-to-inputs)
176
+
177
+ <input x-model="search" placeholder="Search...">
178
+ <input x-model="search" placeholder="Search...">
179
+
180
+ You'll notice a new directive we haven't seen yet: `x-model`.
181
+
182
+ `x-model` is used to "bind" the value of an input element with a data property: "search" from `x-data="{ search: '', ... }"` in our case.
183
+
184
+ This means that anytime the value of the input changes, the value of "search" will change to reflect that.
185
+
186
+ `x-model` is capable of much more than this simple example.
187
+
188
+ [β†’ Read more about `x-model`](/directives/model)
189
+
190
+ ### [Computed properties using getters](#computed-properties-using-getters)
191
+
192
+ The next bit I'd like to draw your attention to is the `items` and `filteredItems` properties from the `x-data` directive.
193
+
194
+ { ... items: ['foo', 'bar', 'baz'],Β  get filteredItems() { return this.items.filter( i => i.startsWith(this.search) ) }}
195
+ {
196
+ ...
197
+ items: ['foo', 'bar', 'baz'],
198
+
199
+ get filteredItems() {
200
+ return this.items.filter(
201
+ i => i.startsWith(this.search)
202
+ )
203
+ }
204
+ }
205
+
206
+ The `items` property should be self-explanatory. Here we are setting the value of `items` to a JavaScript array of 3 different items (foo, bar, and baz).
207
+
208
+ The interesting part of this snippet is the `filteredItems` property.
209
+
210
+ Denoted by the `get` prefix for this property, `filteredItems` is a "getter" property in this object. This means we can access `filteredItems` as if it was a normal property in our data object, but when we do, JavaScript will evaluate the provided function under the hood and return the result.
211
+
212
+ It's completely acceptable to forgo the `get` and just make this a method that you can call from the template, but some prefer the nicer syntax of the getter.
213
+
214
+ [β†’ Read more about JavaScript getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)
215
+
216
+ Now let's look inside the `filteredItems` getter and make sure we understand what's going on there:
217
+
218
+ return this.items.filter( i => i.startsWith(this.search))
219
+ return this.items.filter(
220
+ i => i.startsWith(this.search)
221
+ )
222
+
223
+ This is all plain JavaScript. We are first getting the array of items (foo, bar, and baz) and filtering them using the provided callback: `i => i.startsWith(this.search)`.
224
+
225
+ By passing in this callback to `filter`, we are telling JavaScript to only return the items that start with the string: `this.search`, which like we saw with `x-model` will always reflect the value of the input.
226
+
227
+ You may notice that up until now, we haven't had to use `this.` to reference properties. However, because we are working directly inside the `x-data` object, we must reference any properties using `this.[property]` instead of simply `[property]`.
228
+
229
+ Because Alpine is a "reactive" framework. Any time the value of `this.search` changes, parts of the template that use `filteredItems` will automatically be updated.
230
+
231
+ ### [Looping elements](#looping-elements)
232
+
233
+ Now that we understand the data part of our component, let's understand what's happening in the template that allows us to loop through `filteredItems` on the page.
234
+
235
+ <ul> <template x-for="item in filteredItems"> <li x-text="item"></li> </template></ul>
236
+ <ul>
237
+ <template x-for="item in filteredItems">
238
+ <li x-text="item"></li>
239
+ </template>
240
+ </ul>
241
+
242
+ The first thing to notice here is the `x-for` directive. `x-for` expressions take the following form: `[item] in [items]` where \[items\] is any array of data, and \[item\] is the name of the variable that will be assigned to an iteration inside the loop.
243
+
244
+ Also notice that `x-for` is declared on a `<template>` element and not directly on the `<li>`. This is a requirement of using `x-for`. It allows Alpine to leverage the existing behavior of `<template>` tags in the browser to its advantage.
245
+
246
+ Now any element inside the `<template>` tag will be repeated for every item inside `filteredItems` and all expressions evaluated inside the loop will have direct access to the iteration variable (`item` in this case).
247
+
248
+ [β†’ Read more about `x-for`](/directives/for)
249
+
250
+ [Recap](#recap)
251
+ ---------------
252
+
253
+ If you've made it this far, you've been exposed to the following directives in Alpine:
254
+
255
+ * x-data
256
+ * x-on
257
+ * x-text
258
+ * x-show
259
+ * x-model
260
+ * x-for
261
+
262
+ That's a great start, however, there are many more directives to sink your teeth into. The best way to absorb Alpine is to read through this documentation. No need to comb over every word, but if you at least glance through every page you will be MUCH more effective when using Alpine.
263
+
264
+ Happy Coding!
265
+
266
+ [Upgrade From V2 β†’](/upgrade-guide)
267
+
268
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/_State.md ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ State
2
+ =====
3
+
4
+ State (JavaScript data that Alpine watches for changes) is at the core of everything you do in Alpine. You can provide local data to a chunk of HTML, or make it globally available for use anywhere on a page using `x-data` or `Alpine.store()` respectively.
5
+
6
+ [Local state](#local-state-x-data)
7
+ ----------------------------------
8
+
9
+ Alpine allows you to declare an HTML block's state in a single `x-data` attribute without ever leaving your markup.
10
+
11
+ Here's a basic example:
12
+
13
+ <div x-data="{ open: false }"> ...</div>
14
+ <div x-data="{ open: false }">
15
+ ...
16
+ </div>
17
+
18
+ Now any other Alpine syntax on or within this element will be able to access `open`. And like you'd guess, when `open` changes for any reason, everything that depends on it will react automatically.
19
+
20
+ [β†’ Read more about `x-data`](/directives/data)
21
+
22
+ ### [Nesting data](#nesting-data)
23
+
24
+ Data is nestable in Alpine. For example, if you have two elements with Alpine data attached (one inside the other), you can access the parent's data from inside the child element.
25
+
26
+ <div x-data="{ open: false }"> <div x-data="{ label: 'Content:' }"> <span x-text="label"></span> <span x-show="open"></span> </div></div>
27
+ <div x-data="{ open: false }">
28
+ <div x-data="{ label: 'Content:' }">
29
+ <span x-text="label"></span>
30
+ <span x-show="open"></span>
31
+ </div>
32
+ </div>
33
+
34
+ This is similar to scoping in JavaScript itself (code within a function can access variables declared outside that function.)
35
+
36
+ Like you may have guessed, if the child has a data property matching the name of a parent's property, the child property will take precedence.
37
+
38
+ ### [Single-element data](#single-element-data)
39
+
40
+ Although this may seem obvious to some, it's worth mentioning that Alpine data can be used within the same element. For example:
41
+
42
+ <button x-data="{ label: 'Click Here' }" x-text="label"></button>
43
+ <button x-data="{ label: 'Click Here' }" x-text="label"></button>
44
+
45
+ ### [Data-less Alpine](#data-less-alpine)
46
+
47
+ Sometimes you may want to use Alpine functionality, but don't need any reactive data. In these cases, you can opt out of passing an expression to `x-data` entirely. For example:
48
+
49
+ <button x-data @click="alert('I\'ve been clicked!')">Click Me</button>
50
+ <button x-data @click="alert('I\'ve been clicked!')">Click Me</button>
51
+
52
+ ### [Re-usable data](#re-usable-data)
53
+
54
+ When using Alpine, you may find the need to re-use a chunk of data and/or its corresponding template.
55
+
56
+ If you are using a backend framework like Rails or Laravel, Alpine first recommends that you extract the entire block of HTML into a template partial or include.
57
+
58
+ If for some reason that isn't ideal for you or you're not in a back-end templating environment, Alpine allows you to globally register and re-use the data portion of a component using `Alpine.data(...)`.
59
+
60
+ Alpine.data('dropdown', () => ({ open: false,Β  toggle() { this.open = ! this.open }}))
61
+ Alpine.data('dropdown', () => ({
62
+ open: false,
63
+
64
+ toggle() {
65
+ this.open = ! this.open
66
+ }
67
+ }))
68
+
69
+ Now that you've registered the "dropdown" data, you can use it inside your markup in as many places as you like:
70
+
71
+ <div x-data="dropdown"> <button @click="toggle">Expand</button>Β  <span x-show="open">Content...</span></div>Β <div x-data="dropdown"> <button @click="toggle">Expand</button>Β  <span x-show="open">Some Other Content...</span></div>
72
+ <div x-data="dropdown">
73
+ <button @click="toggle">Expand</button>
74
+
75
+ <span x-show="open">Content...</span>
76
+ </div>
77
+
78
+ <div x-data="dropdown">
79
+ <button @click="toggle">Expand</button>
80
+
81
+ <span x-show="open">Some Other Content...</span>
82
+ </div>
83
+
84
+ [β†’ Read more about using `Alpine.data()`](/globals/alpine-data)
85
+
86
+ [Global state](#global-state)
87
+ -----------------------------
88
+
89
+ If you wish to make some data available to every component on the page, you can do so using Alpine's "global store" feature.
90
+
91
+ You can register a store using `Alpine.store(...)`, and reference one with the magic `$store()` method.
92
+
93
+ Let's look at a simple example. First we'll register the store globally:
94
+
95
+ Alpine.store('tabs', { current: 'first',Β  items: ['first', 'second', 'third'],})
96
+ Alpine.store('tabs', {
97
+ current: 'first',
98
+
99
+ items: ['first', 'second', 'third'],
100
+ })
101
+
102
+ Now we can access or modify its data from anywhere on our page:
103
+
104
+ <div x-data> <template x-for="tab in $store.tabs.items"> ... </template></div>Β <div x-data> <button @click="$store.tabs.current = 'first'">First Tab</button> <button @click="$store.tabs.current = 'second'">Second Tab</button> <button @click="$store.tabs.current = 'third'">Third Tab</button></div>
105
+ <div x-data>
106
+ <template x-for="tab in $store.tabs.items">
107
+ ...
108
+ </template>
109
+ </div>
110
+
111
+ <div x-data>
112
+ <button @click="$store.tabs.current = 'first'">First Tab</button>
113
+ <button @click="$store.tabs.current = 'second'">Second Tab</button>
114
+ <button @click="$store.tabs.current = 'third'">Third Tab</button>
115
+ </div>
116
+
117
+ [β†’ Read more about `Alpine.store()`](/globals/alpine-store)
118
+
119
+ [← Installation](/essentials/installation)
120
+
121
+ [Templating β†’](/essentials/templating)
122
+
123
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/_Templating.md ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Templating
2
+ ==========
3
+
4
+ Alpine offers a handful of useful directives for manipulating the DOM on a web page.
5
+
6
+ Let's cover a few of the basic templating directives here, but be sure to look through the available directives in the sidebar for an exhaustive list.
7
+
8
+ [Text content](#text-content)
9
+ -----------------------------
10
+
11
+ Alpine makes it easy to control the text content of an element with the `x-text` directive.
12
+
13
+ <div x-data="{ title: 'Start Here' }"> <h1 x-text="title"></h1></div>
14
+ <div x-data="{ title: 'Start Here' }">
15
+ <h1 x-text="title"></h1>
16
+ </div>
17
+
18
+ **Start Here**
19
+
20
+ Now, Alpine will set the text content of the `<h1>` with the value of `title` ("Start Here"). When `title` changes, so will the contents of `<h1>`.
21
+
22
+ Like all directives in Alpine, you can use any JavaScript expression you like. For example:
23
+
24
+ <span x-text="1 + 2"></span>
25
+ <span x-text="1 + 2"></span>
26
+
27
+ 3
28
+
29
+ The `<span>` will now contain the sum of "1" and "2".
30
+
31
+ [β†’ Read more about `x-text`](/directives/text)
32
+
33
+ [Toggling elements](#toggling-elements)
34
+ ---------------------------------------
35
+
36
+ Toggling elements is a common need in web pages and applications. Dropdowns, modals, dialogues, "show-more"s, etc... are all good examples.
37
+
38
+ Alpine offers the `x-show` and `x-if` directives for toggling elements on a page.
39
+
40
+ ### [`x-show`](#x-show)
41
+
42
+ Here's a simple toggle component using `x-show`.
43
+
44
+ <div x-data="{ open: false }"> <button @click="open = ! open">Expand</button>Β  <div x-show="open"> Content... </div></div>
45
+ <div x-data="{ open: false }">
46
+ <button @click="open = ! open">Expand</button>
47
+
48
+ <div x-show="open">
49
+ Content...
50
+ </div>
51
+ </div>
52
+
53
+ Expand
54
+
55
+ Content...
56
+
57
+ Now the entire `<div>` containing the contents will be shown and hidden based on the value of `open`.
58
+
59
+ Under the hood, Alpine adds the CSS property `display: none;` to the element when it should be hidden.
60
+
61
+ [β†’ Read more about `x-show`](/directives/show)
62
+
63
+ This works well for most cases, but sometimes you may want to completely add and remove the element from the DOM entirely. This is what `x-if` is for.
64
+
65
+ ### [`x-if`](#x-if)
66
+
67
+ Here is the same toggle from before, but this time using `x-if` instead of `x-show`.
68
+
69
+ <div x-data="{ open: false }"> <button @click="open = ! open">Expand</button>Β  <template x-if="open"> <div> Content... </div> </template></div>
70
+ <div x-data="{ open: false }">
71
+ <button @click="open = ! open">Expand</button>
72
+
73
+ <template x-if="open">
74
+ <div>
75
+ Content...
76
+ </div>
77
+ </template>
78
+ </div>
79
+
80
+ Expand
81
+
82
+ Notice that `x-if` must be declared on a `<template>` tag. This is so that Alpine can leverage the existing browser behavior of the `<template>` element and use it as the source of the target `<div>` to be added and removed from the page.
83
+
84
+ When `open` is true, Alpine will append the `<div>` to the `<template>` tag, and remove it when `open` is false.
85
+
86
+ [β†’ Read more about `x-if`](/directives/if)
87
+
88
+ [Toggling with transitions](#toggling-with-transitions)
89
+ -------------------------------------------------------
90
+
91
+ Alpine makes it simple to smoothly transition between "shown" and "hidden" states using the `x-transition` directive.
92
+
93
+ > `x-transition` only works with `x-show`, not with `x-if`.
94
+
95
+ Here is, again, the simple toggle example, but this time with transitions applied:
96
+
97
+ <div x-data="{ open: false }"> <button @click="open = ! open">Expands</button>Β  <div x-show="open" x-transition> Content... </div></div>
98
+ <div x-data="{ open: false }">
99
+ <button @click="open = ! open">Expands</button>
100
+
101
+ <div x-show="open" x-transition>
102
+ Content...
103
+ </div>
104
+ </div>
105
+
106
+ Expands
107
+
108
+ Content...
109
+
110
+ Let's zoom in on the portion of the template dealing with transitions:
111
+
112
+ <div x-show="open" x-transition>
113
+ <div x-show="open" x-transition>
114
+
115
+ `x-transition` by itself will apply sensible default transitions (fade and scale) to the toggle.
116
+
117
+ There are two ways to customize these transitions:
118
+
119
+ * Transition helpers
120
+ * Transition CSS classes.
121
+
122
+ Let's take a look at each of these approaches:
123
+
124
+ ### [Transition helpers](#transition-helpers)
125
+
126
+ Let's say you wanted to make the duration of the transition longer, you can manually specify that using the `.duration` modifier like so:
127
+
128
+ <div x-show="open" x-transition.duration.500ms>
129
+ <div x-show="open" x-transition.duration.500ms>
130
+
131
+ Expands
132
+
133
+ Content...
134
+
135
+ Now the transition will last 500 milliseconds.
136
+
137
+ If you want to specify different values for in and out transitions, you can use `x-transition:enter` and `x-transition:leave`:
138
+
139
+ <div x-show="open" x-transition:enter.duration.500ms x-transition:leave.duration.1000ms>
140
+ <div
141
+ x-show="open"
142
+ x-transition:enter.duration.500ms
143
+ x-transition:leave.duration.1000ms
144
+ >
145
+
146
+ Expands
147
+
148
+ Content...
149
+
150
+ Additionally, you can add either `.opacity` or `.scale` to only transition that property. For example:
151
+
152
+ <div x-show="open" x-transition.opacity>
153
+ <div x-show="open" x-transition.opacity>
154
+
155
+ Expands
156
+
157
+ Content...
158
+
159
+ [β†’ Read more about transition helpers](/directives/transition#the-transition-helper)
160
+
161
+ ### [Transition classes](#transition-classes)
162
+
163
+ If you need more fine-grained control over the transitions in your application, you can apply specific CSS classes at specific phases of the transition using the following syntax (this example uses [Tailwind CSS](https://tailwindcss.com/)):
164
+
165
+ <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>
166
+ <div
167
+ x-show="open"
168
+ x-transition:enter="transition ease-out duration-300"
169
+ x-transition:enter-start="opacity-0 transform scale-90"
170
+ x-transition:enter-end="opacity-100 transform scale-100"
171
+ x-transition:leave="transition ease-in duration-300"
172
+ x-transition:leave-start="opacity-100 transform scale-100"
173
+ x-transition:leave-end="opacity-0 transform scale-90"
174
+ >...</div>
175
+
176
+ Expands
177
+
178
+ Content...
179
+
180
+ [β†’ Read more about transition classes](/directives/transition#applying-css-classes)
181
+
182
+ [Binding attributes](#binding-attributes)
183
+ -----------------------------------------
184
+
185
+ You can add HTML attributes like `class`, `style`, `disabled`, etc... to elements in Alpine using the `x-bind` directive.
186
+
187
+ Here is an example of a dynamically bound `class` attribute:
188
+
189
+ <button x-data="{ red: false }" x-bind:class="red ? 'bg-red' : ''" @click="red = ! red"> Toggle Red</button>
190
+ <button
191
+ x-data="{ red: false }"
192
+ x-bind:class="red ? 'bg-red' : ''"
193
+ @click="red = ! red"
194
+ >
195
+ Toggle Red
196
+ </button>
197
+
198
+ Toggle Red
199
+
200
+ As a shortcut, you can leave out the `x-bind` and use the shorthand `:` syntax directly:
201
+
202
+ <button ... :class="red ? 'bg-red' : ''">
203
+ <button ... :class="red ? 'bg-red' : ''">
204
+
205
+ Toggling classes on and off based on data inside Alpine is a common need. Here's an example of toggling a class using Alpine's `class` binding object syntax: (Note: this syntax is only available for `class` attributes)
206
+
207
+ <div x-data="{ open: true }"> <span :class="{ 'hidden': ! open }">...</span></div>
208
+ <div x-data="{ open: true }">
209
+ <span :class="{ 'hidden': ! open }">...</span>
210
+ </div>
211
+
212
+ Now the `hidden` class will be added to the element if `open` is false, and removed if `open` is true.
213
+
214
+ [Looping elements](#looping-elements)
215
+ -------------------------------------
216
+
217
+ Alpine allows for iterating parts of your template based on JavaScript data using the `x-for` directive. Here is a simple example:
218
+
219
+ <div x-data="{ statuses: ['open', 'closed', 'archived'] }"> <template x-for="status in statuses"> <div x-text="status"></div> </template></div>
220
+ <div x-data="{ statuses: ['open', 'closed', 'archived'] }">
221
+ <template x-for="status in statuses">
222
+ <div x-text="status"></div>
223
+ </template>
224
+ </div>
225
+
226
+ open
227
+
228
+ closed
229
+
230
+ archived
231
+
232
+ Similar to `x-if`, `x-for` must be applied to a `<template>` tag. Internally, Alpine will append the contents of `<template>` tag for every iteration in the loop.
233
+
234
+ As you can see the new `status` variable is available in the scope of the iterated templates.
235
+
236
+ [β†’ Read more about `x-for`](/directives/for)
237
+
238
+ [Inner HTML](#inner-html)
239
+ -------------------------
240
+
241
+ Alpine makes it easy to control the HTML content of an element with the `x-html` directive.
242
+
243
+ <div x-data="{ title: '<h1>Start Here</h1>' }"> <div x-html="title"></div></div>
244
+ <div x-data="{ title: '<h1>Start Here</h1>' }">
245
+ <div x-html="title"></div>
246
+ </div>
247
+
248
+ Start Here
249
+ ==========
250
+
251
+ Now, Alpine will set the text content of the `<div>` with the element `<h1>Start Here</h1>`. When `title` changes, so will the contents of `<h1>`.
252
+
253
+ > ⚠️ Only use on trusted content and never on user-provided content. ⚠️ Dynamically rendering HTML from third parties can easily lead to XSS vulnerabilities.
254
+
255
+ [β†’ Read more about `x-html`](/directives/html)
256
+
257
+ [← State](/essentials/state)
258
+
259
+ [Events β†’](/essentials/events)
260
+
261
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-bind.md ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-bind
2
+ ======
3
+
4
+ `x-bind` allows you to set HTML attributes on elements based on the result of JavaScript expressions.
5
+
6
+ For example, here's a component where we will use `x-bind` to set the placeholder value of an input.
7
+
8
+ <div x-data="{ placeholder: 'Type here...' }"> <input type="text" x-bind:placeholder="placeholder"></div>
9
+ <div x-data="{ placeholder: 'Type here...' }">
10
+ <input type="text" x-bind:placeholder="placeholder">
11
+ </div>
12
+
13
+ [Shorthand syntax](#shorthand-syntax)
14
+ -------------------------------------
15
+
16
+ If `x-bind:` is too verbose for your liking, you can use the shorthand: `:`. For example, here is the same input element as above, but refactored to use the shorthand syntax.
17
+
18
+ <input type="text" :placeholder="placeholder">
19
+ <input type="text" :placeholder="placeholder">
20
+
21
+ > Despite not being included in the above snippet, `x-bind` cannot be used if no parent element has `x-data` defined. [β†’ Read more about `x-data`](/directives/data)
22
+
23
+ [Binding classes](#binding-classes)
24
+ -----------------------------------
25
+
26
+ `x-bind` is most often useful for setting specific classes on an element based on your Alpine state.
27
+
28
+ Here's a simple example of a simple dropdown toggle, but instead of using `x-show`, we'll use a "hidden" class to toggle an element.
29
+
30
+ <div x-data="{ open: false }"> <button x-on:click="open = ! open">Toggle Dropdown</button>Β  <div :class="open ? '' : 'hidden'"> Dropdown Contents... </div></div>
31
+ <div x-data="{ open: false }">
32
+ <button x-on:click="open = ! open">Toggle Dropdown</button>
33
+
34
+ <div :class="open ? '' : 'hidden'">
35
+ Dropdown Contents...
36
+ </div>
37
+ </div>
38
+
39
+ Now, when `open` is `false`, the "hidden" class will be added to the dropdown.
40
+
41
+ ### [Shorthand conditionals](#shorthand-conditionals)
42
+
43
+ In cases like these, if you prefer a less verbose syntax you can use JavaScript's short-circuit evaluation instead of standard conditionals:
44
+
45
+ <div :class="show ? '' : 'hidden'"><!-- Is equivalent to: --><div :class="show || 'hidden'">
46
+ <div :class="show ? '' : 'hidden'">
47
+ <!-- Is equivalent to: -->
48
+ <div :class="show || 'hidden'">
49
+
50
+ The inverse is also available to you. Suppose instead of `open`, we use a variable with the opposite value: `closed`.
51
+
52
+ <div :class="closed ? 'hidden' : ''"><!-- Is equivalent to: --><div :class="closed && 'hidden'">
53
+ <div :class="closed ? 'hidden' : ''">
54
+ <!-- Is equivalent to: -->
55
+ <div :class="closed && 'hidden'">
56
+
57
+ ### [Class object syntax](#class-object-syntax)
58
+
59
+ Alpine offers an additional syntax for toggling classes if you prefer. By passing a JavaScript object where the classes are the keys and booleans are the values, Alpine will know which classes to apply and which to remove. For example:
60
+
61
+ <div :class="{ 'hidden': ! show }">
62
+ <div :class="{ 'hidden': ! show }">
63
+
64
+ This technique offers a unique advantage to other methods. When using object-syntax, Alpine will NOT preserve original classes applied to an element's `class` attribute.
65
+
66
+ For example, if you wanted to apply the "hidden" class to an element before Alpine loads, AND use Alpine to toggle its existence you can only achieve that behavior using object-syntax:
67
+
68
+ <div class="hidden" :class="{ 'hidden': ! show }">
69
+ <div class="hidden" :class="{ 'hidden': ! show }">
70
+
71
+ In case that confused you, let's dig deeper into how Alpine handles `x-bind:class` differently than other attributes.
72
+
73
+ ### [Special behavior](#special-behavior)
74
+
75
+ `x-bind:class` behaves differently than other attributes under the hood.
76
+
77
+ Consider the following case.
78
+
79
+ <div class="opacity-50" :class="hide && 'hidden'">
80
+ <div class="opacity-50" :class="hide && 'hidden'">
81
+
82
+ If "class" were any other attribute, the `:class` binding would overwrite any existing class attribute, causing `opacity-50` to be overwritten by either `hidden` or `''`.
83
+
84
+ However, Alpine treats `class` bindings differently. It's smart enough to preserve existing classes on an element.
85
+
86
+ For example, if `hide` is true, the above example will result in the following DOM element:
87
+
88
+ <div class="opacity-50 hidden">
89
+ <div class="opacity-50 hidden">
90
+
91
+ If `hide` is false, the DOM element will look like:
92
+
93
+ <div class="opacity-50">
94
+ <div class="opacity-50">
95
+
96
+ This behavior should be invisible and intuitive to most users, but it is worth mentioning explicitly for the inquiring developer or any special cases that might crop up.
97
+
98
+ [Binding styles](#binding-styles)
99
+ ---------------------------------
100
+
101
+ Similar to the special syntax for binding classes with JavaScript objects, Alpine also offers an object-based syntax for binding `style` attributes.
102
+
103
+ Just like the class objects, this syntax is entirely optional. Only use it if it affords you some advantage.
104
+
105
+ <div :style="{ color: 'red', display: 'flex' }">Β <!-- Will render: --><div style="color: red; display: flex;" ...>
106
+ <div :style="{ color: 'red', display: 'flex' }">
107
+
108
+ <!-- Will render: -->
109
+ <div style="color: red; display: flex;" ...>
110
+
111
+ Conditional inline styling is possible using expressions just like with x-bind:class. Short circuit operators can be used here as well by using a styles object as the second operand.
112
+
113
+ <div x-bind:style="true && { color: 'red' }">Β <!-- Will render: --><div style="color: red;">
114
+ <div x-bind:style="true && { color: 'red' }">
115
+
116
+ <!-- Will render: -->
117
+ <div style="color: red;">
118
+
119
+ One advantage of this approach is being able to mix it in with existing styles on an element:
120
+
121
+ <div style="padding: 1rem;" :style="{ color: 'red', display: 'flex' }">Β <!-- Will render: --><div style="padding: 1rem; color: red; display: flex;" ...>
122
+ <div style="padding: 1rem;" :style="{ color: 'red', display: 'flex' }">
123
+
124
+ <!-- Will render: -->
125
+ <div style="padding: 1rem; color: red; display: flex;" ...>
126
+
127
+ And like most expressions in Alpine, you can always use the result of a JavaScript expression as the reference:
128
+
129
+ <div x-data="{ styles: { color: 'red', display: 'flex' }}"> <div :style="styles"></div>Β <!-- Will render: --><div ...> <div style="color: red; display: flex;" ...></div>
130
+ <div x-data="{ styles: { color: 'red', display: 'flex' }}">
131
+ <div :style="styles">
132
+ </div>
133
+
134
+ <!-- Will render: -->
135
+ <div ...>
136
+ <div style="color: red; display: flex;" ...>
137
+ </div>
138
+
139
+ [Binding Alpine Directives Directly](#bind-directives)
140
+ ------------------------------------------------------
141
+
142
+ `x-bind` allows you to bind an object of different directives and attributes to an element.
143
+
144
+ The object keys can be anything you would normally write as an attribute name in Alpine. This includes Alpine directives and modifiers, but also plain HTML attributes. The object values are either plain strings, or in the case of dynamic Alpine directives, callbacks to be evaluated by Alpine.
145
+
146
+ <div x-data="dropdown"> <button x-bind="trigger">Open Dropdown</button>Β  <span x-bind="dialogue">Dropdown Contents</span></div>Β <script> document.addEventListener('alpine:init', () => { Alpine.data('dropdown', () => ({ open: false,Β  trigger: { ['x-ref']: 'trigger', ['@click']() { this.open = true }, },Β  dialogue: { ['x-show']() { return this.open }, ['@click.outside']() { this.open = false }, }, })) })</script>
147
+ <div x-data="dropdown">
148
+ <button x-bind="trigger">Open Dropdown</button>
149
+
150
+ <span x-bind="dialogue">Dropdown Contents</span>
151
+ </div>
152
+
153
+ <script>
154
+ document.addEventListener('alpine:init', () => {
155
+ Alpine.data('dropdown', () => ({
156
+ open: false,
157
+
158
+ trigger: {
159
+ ['x-ref']: 'trigger',
160
+ ['@click']() {
161
+ this.open = true
162
+ },
163
+ },
164
+
165
+ dialogue: {
166
+ ['x-show']() {
167
+ return this.open
168
+ },
169
+ ['@click.outside']() {
170
+ this.open = false
171
+ },
172
+ },
173
+ }))
174
+ })
175
+ </script>
176
+
177
+ There are a couple of caveats to this usage of `x-bind`:
178
+
179
+ > When the directive being "bound" or "applied" is `x-for`, you should return a normal expression string from the callback. For example: `['x-for']() { return 'item in items' }`
180
+
181
+ [← x-show](/directives/show)
182
+
183
+ [x-on β†’](/directives/on)
184
+
185
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-data.md ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-data
2
+ ======
3
+
4
+ Everything in Alpine starts with the `x-data` directive.
5
+
6
+ `x-data` defines a chunk of HTML as an Alpine component and provides the reactive data for that component to reference.
7
+
8
+ Here's an example of a contrived dropdown component:
9
+
10
+ <div x-data="{ open: false }"> <button @click="open = ! open">Toggle Content</button>Β  <div x-show="open"> Content... </div></div>
11
+ <div x-data="{ open: false }">
12
+ <button @click="open = ! open">Toggle Content</button>
13
+
14
+ <div x-show="open">
15
+ Content...
16
+ </div>
17
+ </div>
18
+
19
+ Don't worry about the other directives in this example (`@click` and `x-show`), we'll get to those in a bit. For now, let's focus on `x-data`.
20
+
21
+ [Scope](#scope)
22
+ ---------------
23
+
24
+ Properties defined in an `x-data` directive are available to all element children. Even ones inside other, nested `x-data` components.
25
+
26
+ For example:
27
+
28
+ <div x-data="{ foo: 'bar' }"> <span x-text="foo"><!-- Will output: "bar" --></span>Β  <div x-data="{ bar: 'baz' }"> <span x-text="foo"><!-- Will output: "bar" --></span>Β  <div x-data="{ foo: 'bob' }"> <span x-text="foo"><!-- Will output: "bob" --></span> </div> </div></div>
29
+ <div x-data="{ foo: 'bar' }">
30
+ <span x-text="foo"><!-- Will output: "bar" --></span>
31
+
32
+ <div x-data="{ bar: 'baz' }">
33
+ <span x-text="foo"><!-- Will output: "bar" --></span>
34
+
35
+ <div x-data="{ foo: 'bob' }">
36
+ <span x-text="foo"><!-- Will output: "bob" --></span>
37
+ </div>
38
+ </div>
39
+ </div>
40
+
41
+ [Methods](#methods)
42
+ -------------------
43
+
44
+ Because `x-data` is evaluated as a normal JavaScript object, in addition to state, you can store methods and even getters.
45
+
46
+ For example, let's extract the "Toggle Content" behavior into a method on `x-data`.
47
+
48
+ <div x-data="{ open: false, toggle() { this.open = ! this.open } }"> <button @click="toggle()">Toggle Content</button>Β  <div x-show="open"> Content... </div></div>
49
+ <div x-data="{ open: false, toggle() { this.open = ! this.open } }">
50
+ <button @click="toggle()">Toggle Content</button>
51
+
52
+ <div x-show="open">
53
+ Content...
54
+ </div>
55
+ </div>
56
+
57
+ Notice the added `toggle() { this.open = ! this.open }` method on `x-data`. This method can now be called from anywhere inside the component.
58
+
59
+ You'll also notice the usage of `this.` to access state on the object itself. This is because Alpine evaluates this data object like any standard JavaScript object with a `this` context.
60
+
61
+ If you prefer, you can leave the calling parenthesis off of the `toggle` method completely. For example:
62
+
63
+ <!-- Before --><button @click="toggle()">...</button>Β <!-- After --><button @click="toggle">...</button>
64
+ <!-- Before -->
65
+ <button @click="toggle()">...</button>
66
+
67
+ <!-- After -->
68
+ <button @click="toggle">...</button>
69
+
70
+ [Getters](#getters)
71
+ -------------------
72
+
73
+ JavaScript [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) are handy when the sole purpose of a method is to return data based on other state.
74
+
75
+ Think of them like "computed properties" (although, they are not cached like Vue's computed properties).
76
+
77
+ Let's refactor our component to use a getter called `isOpen` instead of accessing `open` directly.
78
+
79
+ <div x-data="{ open: false, get isOpen() { return this.open }, toggle() { this.open = ! this.open },}"> <button @click="toggle()">Toggle Content</button>Β  <div x-show="isOpen"> Content... </div></div>
80
+ <div x-data="{
81
+ open: false,
82
+ get isOpen() { return this.open },
83
+ toggle() { this.open = ! this.open },
84
+ }">
85
+ <button @click="toggle()">Toggle Content</button>
86
+
87
+ <div x-show="isOpen">
88
+ Content...
89
+ </div>
90
+ </div>
91
+
92
+ Notice the "Content" now depends on the `isOpen` getter instead of the `open` property directly.
93
+
94
+ In this case there is no tangible benefit. But in some cases, getters are helpful for providing a more expressive syntax in your components.
95
+
96
+ [Data-less components](#data-less-components)
97
+ ---------------------------------------------
98
+
99
+ Occasionally, you want to create an Alpine component, but you don't need any data.
100
+
101
+ In these cases, you can always pass in an empty object.
102
+
103
+ <div x-data="{}">
104
+ <div x-data="{}">
105
+
106
+ However, if you wish, you can also eliminate the attribute value entirely if it looks better to you.
107
+
108
+ <div x-data>
109
+ <div x-data>
110
+
111
+ [Single-element components](#single-element-components)
112
+ -------------------------------------------------------
113
+
114
+ Sometimes you may only have a single element inside your Alpine component, like the following:
115
+
116
+ <div x-data="{ open: true }"> <button @click="open = false" x-show="open">Hide Me</button></div>
117
+ <div x-data="{ open: true }">
118
+ <button @click="open = false" x-show="open">Hide Me</button>
119
+ </div>
120
+
121
+ In these cases, you can declare `x-data` directly on that single element:
122
+
123
+ <button x-data="{ open: true }" @click="open = false" x-show="open"> Hide Me</button>
124
+ <button x-data="{ open: true }" @click="open = false" x-show="open">
125
+ Hide Me
126
+ </button>
127
+
128
+ [Re-usable Data](#re-usable-data)
129
+ ---------------------------------
130
+
131
+ If you find yourself duplicating the contents of `x-data`, or you find the inline syntax verbose, you can extract the `x-data` object out to a dedicated component using `Alpine.data`.
132
+
133
+ Here's a quick example:
134
+
135
+ <div x-data="dropdown"> <button @click="toggle">Toggle Content</button>Β  <div x-show="open"> Content... </div></div>Β <script> document.addEventListener('alpine:init', () => { Alpine.data('dropdown', () => ({ open: false,Β  toggle() { this.open = ! this.open }, })) })</script>
136
+ <div x-data="dropdown">
137
+ <button @click="toggle">Toggle Content</button>
138
+
139
+ <div x-show="open">
140
+ Content...
141
+ </div>
142
+ </div>
143
+
144
+ <script>
145
+ document.addEventListener('alpine:init', () => {
146
+ Alpine.data('dropdown', () => ({
147
+ open: false,
148
+
149
+ toggle() {
150
+ this.open = ! this.open
151
+ },
152
+ }))
153
+ })
154
+ </script>
155
+
156
+ [β†’ Read more about `Alpine.data(...)`](/globals/alpine-data)
157
+
158
+ [← Lifecycle](/essentials/lifecycle)
159
+
160
+ [x-init β†’](/directives/init)
161
+
162
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-effect.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-effect
2
+ ========
3
+
4
+ `x-effect` is a useful directive for re-evaluating an expression when one of its dependencies change. You can think of it as a watcher where you don't have to specify what property to watch, it will watch all properties used within it.
5
+
6
+ If this definition is confusing for you, that's ok. It's better explained through an example:
7
+
8
+ <div x-data="{ label: 'Hello' }" x-effect="console.log(label)"> <button @click="label += ' World!'">Change Message</button></div>
9
+ <div x-data="{ label: 'Hello' }" x-effect="console.log(label)">
10
+ <button @click="label += ' World!'">Change Message</button>
11
+ </div>
12
+
13
+ When this component is loaded, the `x-effect` expression will be run and "Hello" will be logged into the console.
14
+
15
+ Because Alpine knows about any property references contained within `x-effect`, when the button is clicked and `label` is changed, the effect will be re-triggered and "Hello World!" will be logged to the console.
16
+
17
+ [← x-transition](/directives/transition)
18
+
19
+ [x-ignore β†’](/directives/ignore)
20
+
21
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-for.md ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-for
2
+ =====
3
+
4
+ Alpine's `x-for` directive allows you to create DOM elements by iterating through a list. Here's a simple example of using it to create a list of colors based on an array.
5
+
6
+ <ul x-data="{ colors: ['Red', 'Orange', 'Yellow'] }"> <template x-for="color in colors"> <li x-text="color"></li> </template></ul>
7
+ <ul x-data="{ colors: ['Red', 'Orange', 'Yellow'] }">
8
+ <template x-for="color in colors">
9
+ <li x-text="color"></li>
10
+ </template>
11
+ </ul>
12
+
13
+ * Red
14
+ * Orange
15
+ * Yellow
16
+
17
+ You may also pass objects to `x-for`.
18
+
19
+ <ul x-data="{ car: { make: 'Jeep', model: 'Grand Cherokee', color: 'Black' } }"> <template x-for="(value, index) in car"> <li> <span x-text="index"></span>: <span x-text="value"></span> </li> </template></ul>
20
+ <ul x-data="{ car: { make: 'Jeep', model: 'Grand Cherokee', color: 'Black' } }">
21
+ <template x-for="(value, index) in car">
22
+ <li>
23
+ <span x-text="index"></span>: <span x-text="value"></span>
24
+ </li>
25
+ </template>
26
+ </ul>
27
+
28
+ * make: Jeep
29
+ * model: Grand Cherokee
30
+ * color: Black
31
+
32
+ There are two rules worth noting about `x-for`:
33
+
34
+ > `x-for` MUST be declared on a `<template>` element. That `<template>` element MUST contain only one root element
35
+
36
+ [Keys](#keys)
37
+ -------------
38
+
39
+ It is important to specify unique keys for each `x-for` iteration if you are going to be re-ordering items. Without dynamic keys, Alpine may have a hard time keeping track of what re-orders and will cause odd side-effects.
40
+
41
+ <ul x-data="{ colors: [ { id: 1, label: 'Red' }, { id: 2, label: 'Orange' }, { id: 3, label: 'Yellow' },]}"> <template x-for="color in colors" :key="color.id"> <li x-text="color.label"></li> </template></ul>
42
+ <ul x-data="{ colors: [
43
+ { id: 1, label: 'Red' },
44
+ { id: 2, label: 'Orange' },
45
+ { id: 3, label: 'Yellow' },
46
+ ]}">
47
+ <template x-for="color in colors" :key="color.id">
48
+ <li x-text="color.label"></li>
49
+ </template>
50
+ </ul>
51
+
52
+ Now if the colors are added, removed, re-ordered, or their "id"s change, Alpine will preserve or destroy the iterated `<li>`elements accordingly.
53
+
54
+ [Accessing indexes](#accessing-indexes)
55
+ ---------------------------------------
56
+
57
+ If you need to access the index of each item in the iteration, you can do so using the `([item], [index]) in [items]` syntax like so:
58
+
59
+ <ul x-data="{ colors: ['Red', 'Orange', 'Yellow'] }"> <template x-for="(color, index) in colors"> <li> <span x-text="index + ': '"></span> <span x-text="color"></span> </li> </template></ul>
60
+ <ul x-data="{ colors: ['Red', 'Orange', 'Yellow'] }">
61
+ <template x-for="(color, index) in colors">
62
+ <li>
63
+ <span x-text="index + ': '"></span>
64
+ <span x-text="color"></span>
65
+ </li>
66
+ </template>
67
+ </ul>
68
+
69
+ You can also access the index inside a dynamic `:key` expression.
70
+
71
+ <template x-for="(color, index) in colors" :key="index">
72
+ <template x-for="(color, index) in colors" :key="index">
73
+
74
+ [Iterating over a range](#iterating-over-a-range)
75
+ -------------------------------------------------
76
+
77
+ If you need to simply loop `n` number of times, rather than iterate through an array, Alpine offers a short syntax.
78
+
79
+ <ul> <template x-for="i in 10"> <li x-text="i"></li> </template></ul>
80
+ <ul>
81
+ <template x-for="i in 10">
82
+ <li x-text="i"></li>
83
+ </template>
84
+ </ul>
85
+
86
+ `i` in this case can be named anything you like.
87
+
88
+ > Despite not being included in the above snippet, `x-for` cannot be used if no parent element has `x-data` defined. [β†’ Read more about `x-data`](/directives/data)
89
+
90
+ [Contents of a `<template>`](#contents-of-a-template)
91
+ -----------------------------------------------------
92
+
93
+ As mentioned above, an `<template>` tag must contain only one root element.
94
+
95
+ For example, the following code will not work:
96
+
97
+ <template x-for="color in colors"> <span>The next color is </span><span x-text="color"></template>
98
+ <template x-for="color in colors">
99
+ <span>The next color is </span><span x-text="color">
100
+ </template>
101
+
102
+ but this code will work:
103
+
104
+ <template x-for="color in colors"> <p> <span>The next color is </span><span x-text="color"> </p></template>
105
+ <template x-for="color in colors">
106
+ <p>
107
+ <span>The next color is </span><span x-text="color">
108
+ </p>
109
+ </template>
110
+
111
+ [← x-modelable](/directives/modelable)
112
+
113
+ [x-transition β†’](/directives/transition)
114
+
115
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-html.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-html
2
+ ======
3
+
4
+ `x-html` sets the "innerHTML" property of an element to the result of a given expression.
5
+
6
+ > ⚠️ Only use on trusted content and never on user-provided content. ⚠️ Dynamically rendering HTML from third parties can easily lead to XSS vulnerabilities.
7
+
8
+ Here's a basic example of using `x-html` to display a user's username.
9
+
10
+ <div x-data="{ username: '<strong>calebporzio</strong>' }"> Username: <span x-html="username"></span></div>
11
+ <div x-data="{ username: '<strong>calebporzio</strong>' }">
12
+ Username: <span x-html="username"></span>
13
+ </div>
14
+
15
+ Username: **calebporzio**
16
+
17
+ Now the `<span>` tag's inner HTML will be set to "**calebporzio**".
18
+
19
+ [← x-text](/directives/text)
20
+
21
+ [x-model β†’](/directives/model)
22
+
23
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-if.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-if
2
+ ====
3
+
4
+ `x-if` is used for toggling elements on the page, similarly to `x-show`, however it completely adds and removes the element it's applied to rather than just changing its CSS display property to "none".
5
+
6
+ Because of this difference in behavior, `x-if` should not be applied directly to the element, but instead to a `<template>` tag that encloses the element. This way, Alpine can keep a record of the element once it's removed from the page.
7
+
8
+ <template x-if="open"> <div>Contents...</div></template>
9
+ <template x-if="open">
10
+ <div>Contents...</div>
11
+ </template>
12
+
13
+ > Despite not being included in the above snippet, `x-if` cannot be used if no parent element has `x-data` defined. [β†’ Read more about `x-data`](/directives/data)
14
+
15
+ Caveats
16
+ -------
17
+
18
+ Unlike `x-show`, `x-if`, does NOT support transitioning toggles with `x-transition`.
19
+
20
+ `<template>` tags can only contain one root element.
21
+
22
+ [← x-teleport](/directives/teleport)
23
+
24
+ [x-id β†’](/directives/id)
25
+
26
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-model.md ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-model
2
+ =======
3
+
4
+ `x-model` allows you to bind the value of an input element to Alpine data.
5
+
6
+ Here's a simple example of using `x-model` to bind the value of a text field to a piece of data in Alpine.
7
+
8
+ <div x-data="{ message: '' }"> <input type="text" x-model="message">Β  <span x-text="message"></span></div>
9
+ <div x-data="{ message: '' }">
10
+ <input type="text" x-model="message">
11
+
12
+ <span x-text="message"></span>
13
+ </div>
14
+
15
+ Now as the user types into the text field, the `message` will be reflected in the `<span>` tag.
16
+
17
+ `x-model` is two-way bound, meaning it both "sets" and "gets". In addition to changing data, if the data itself changes, the element will reflect the change.
18
+
19
+ We can use the same example as above but this time, we'll add a button to change the value of the `message` property.
20
+
21
+ <div x-data="{ message: '' }"> <input type="text" x-model="message">Β  <button x-on:click="message = 'changed'">Change Message</button></div>
22
+ <div x-data="{ message: '' }">
23
+ <input type="text" x-model="message">
24
+
25
+ <button x-on:click="message = 'changed'">Change Message</button>
26
+ </div>
27
+
28
+ Change Message
29
+
30
+ Now when the `<button>` is clicked, the input element's value will instantly be updated to "changed".
31
+
32
+ `x-model` works with the following input elements:
33
+
34
+ * `<input type="text">`
35
+ * `<textarea>`
36
+ * `<input type="checkbox">`
37
+ * `<input type="radio">`
38
+ * `<select>`
39
+ * `<input type="range">`
40
+
41
+ [Text inputs](#text-inputs)
42
+ ---------------------------
43
+
44
+ <input type="text" x-model="message">Β <span x-text="message"></span>
45
+ <input type="text" x-model="message">
46
+
47
+ <span x-text="message"></span>
48
+
49
+ > Despite not being included in the above snippet, `x-model` cannot be used if no parent element has `x-data` defined. [β†’ Read more about `x-data`](/directives/data)
50
+
51
+ [Textarea inputs](#textarea-inputs)
52
+ -----------------------------------
53
+
54
+ <textarea x-model="message"></textarea>Β <span x-text="message"></span>
55
+ <textarea x-model="message"></textarea>
56
+
57
+ <span x-text="message"></span>
58
+
59
+ [Checkbox inputs](#checkbox-inputs)
60
+ -----------------------------------
61
+
62
+ ### [Single checkbox with boolean](#single-checkbox-with-boolean)
63
+
64
+ <input type="checkbox" id="checkbox" x-model="show">Β <label for="checkbox" x-text="show"></label>
65
+ <input type="checkbox" id="checkbox" x-model="show">
66
+
67
+ <label for="checkbox" x-text="show"></label>
68
+
69
+ ### [Multiple checkboxes bound to array](#multiple-checkboxes-bound-to-array)
70
+
71
+ <input type="checkbox" value="red" x-model="colors"><input type="checkbox" value="orange" x-model="colors"><input type="checkbox" value="yellow" x-model="colors">Β Colors: <span x-text="colors"></span>
72
+ <input type="checkbox" value="red" x-model="colors">
73
+ <input type="checkbox" value="orange" x-model="colors">
74
+ <input type="checkbox" value="yellow" x-model="colors">
75
+
76
+ Colors: <span x-text="colors"></span>
77
+
78
+
79
+
80
+ Colors:
81
+
82
+ [Radio inputs](#radio-inputs)
83
+ -----------------------------
84
+
85
+ <input type="radio" value="yes" x-model="answer"><input type="radio" value="no" x-model="answer">Β Answer: <span x-text="answer"></span>
86
+ <input type="radio" value="yes" x-model="answer">
87
+ <input type="radio" value="no" x-model="answer">
88
+
89
+ Answer: <span x-text="answer"></span>
90
+
91
+
92
+
93
+ Answer:
94
+
95
+ [Select inputs](#select-inputs)
96
+ -------------------------------
97
+
98
+ ### [Single select](#single-select)
99
+
100
+ <select x-model="color"> <option>Red</option> <option>Orange</option> <option>Yellow</option></select>Β Color: <span x-text="color"></span>
101
+ <select x-model="color">
102
+ <option>Red</option>
103
+ <option>Orange</option>
104
+ <option>Yellow</option>
105
+ </select>
106
+
107
+ Color: <span x-text="color"></span>
108
+
109
+ Red Orange Yellow
110
+
111
+ Color:
112
+
113
+ ### [Single select with placeholder](#single-select-with-placeholder)
114
+
115
+ <select x-model="color"> <option value="" disabled>Select A Color</option> <option>Red</option> <option>Orange</option> <option>Yellow</option></select>Β Color: <span x-text="color"></span>
116
+ <select x-model="color">
117
+ <option value="" disabled>Select A Color</option>
118
+ <option>Red</option>
119
+ <option>Orange</option>
120
+ <option>Yellow</option>
121
+ </select>
122
+
123
+ Color: <span x-text="color"></span>
124
+
125
+ Select A Color Red Orange Yellow
126
+
127
+ Color:
128
+
129
+ ### [Multiple select](#multiple-select)
130
+
131
+ <select x-model="color" multiple> <option>Red</option> <option>Orange</option> <option>Yellow</option></select>Β Colors: <span x-text="color"></span>
132
+ <select x-model="color" multiple>
133
+ <option>Red</option>
134
+ <option>Orange</option>
135
+ <option>Yellow</option>
136
+ </select>
137
+
138
+ Colors: <span x-text="color"></span>
139
+
140
+ Red Orange Yellow
141
+
142
+ Color:
143
+
144
+ ### [Dynamically populated Select Options](#dynamically-populated-select-options)
145
+
146
+ <select x-model="color"> <template x-for="color in ['Red', 'Orange', 'Yellow']"> <option x-text="color"></option> </template></select>Β Color: <span x-text="color"></span>
147
+ <select x-model="color">
148
+ <template x-for="color in ['Red', 'Orange', 'Yellow']">
149
+ <option x-text="color"></option>
150
+ </template>
151
+ </select>
152
+
153
+ Color: <span x-text="color"></span>
154
+
155
+ RedOrangeYellow
156
+
157
+ Color:
158
+
159
+ [Range inputs](#range-inputs)
160
+ -----------------------------
161
+
162
+ <input type="range" x-model="range" min="0" max="1" step="0.1">Β <span x-text="range"></span>
163
+ <input type="range" x-model="range" min="0" max="1" step="0.1">
164
+
165
+ <span x-text="range"></span>
166
+
167
+ 0.5
168
+
169
+ [Modifiers](#modifiers)
170
+ -----------------------
171
+
172
+ ### [`.lazy`](#lazy)
173
+
174
+ On text inputs, by default, `x-model` updates the property on every keystroke. By adding the `.lazy` modifier, you can force an `x-model` input to only update the property when user focuses away from the input element.
175
+
176
+ This is handy for things like real-time form-validation where you might not want to show an input validation error until the user "tabs" away from a field.
177
+
178
+ <input type="text" x-model.lazy="username"><span x-show="username.length > 20">The username is too long.</span>
179
+ <input type="text" x-model.lazy="username">
180
+ <span x-show="username.length > 20">The username is too long.</span>
181
+
182
+ ### [`.number`](#number)
183
+
184
+ By default, any data stored in a property via `x-model` is stored as a string. To force Alpine to store the value as a JavaScript number, add the `.number` modifier.
185
+
186
+ <input type="text" x-model.number="age"><span x-text="typeof age"></span>
187
+ <input type="text" x-model.number="age">
188
+ <span x-text="typeof age"></span>
189
+
190
+ ### [`.boolean`](#boolean)
191
+
192
+ By default, any data stored in a property via `x-model` is stored as a string. To force Alpine to store the value as a JavaScript boolean, add the `.boolean` modifier. Both integers (1/0) and strings (true/false) are valid boolean values.
193
+
194
+ <select x-model.boolean="isActive"> <option value="true">Yes</option> <option value="false">No</option></select><span x-text="typeof isActive"></span>
195
+ <select x-model.boolean="isActive">
196
+ <option value="true">Yes</option>
197
+ <option value="false">No</option>
198
+ </select>
199
+ <span x-text="typeof isActive"></span>
200
+
201
+ ### [`.debounce`](#debounce)
202
+
203
+ By adding `.debounce` to `x-model`, you can easily debounce the updating of bound input.
204
+
205
+ This is useful for things like real-time search inputs that fetch new data from the server every time the search property changes.
206
+
207
+ <input type="text" x-model.debounce="search">
208
+ <input type="text" x-model.debounce="search">
209
+
210
+ The default debounce time is 250 milliseconds, you can easily customize this by adding a time modifier like so.
211
+
212
+ <input type="text" x-model.debounce.500ms="search">
213
+ <input type="text" x-model.debounce.500ms="search">
214
+
215
+ ### [`.throttle`](#throttle)
216
+
217
+ Similar to `.debounce` you can limit the property update triggered by `x-model` to only updating on a specified interval.
218
+
219
+ The default throttle interval is 250 milliseconds, you can easily customize this by adding a time modifier like so.
220
+
221
+ <input type="text" x-model.throttle.500ms="search">
222
+ <input type="text" x-model.throttle.500ms="search">
223
+
224
+ ### [`.fill`](#fill)
225
+
226
+ By default, if an input has a value attribute, it is ignored by Alpine and instead, the value of the input is set to the value of the property bound using `x-model`.
227
+
228
+ But if a bound property is empty, then you can use an input's value attribute to populate the property by adding the `.fill` modifier.
229
+
230
+ [Programmatic access](#programmatic access)
231
+ -------------------------------------------
232
+
233
+ Alpine exposes under-the-hood utilities for getting and setting properties bound with `x-model`. This is useful for complex Alpine utilities that may want to override the default x-model behavior, or instances where you want to allow `x-model` on a non-input element.
234
+
235
+ You can access these utilities through a property called `_x_model` on the `x-model`ed element. `_x_model` has two methods to get and set the bound property:
236
+
237
+ * `el._x_model.get()` (returns the value of the bound property)
238
+ * `el._x_model.set()` (sets the value of the bound property)
239
+
240
+ <div x-data="{ username: 'calebporzio' }"> <div x-ref="div" x-model="username"></div>Β  <button @click="$refs.div._x_model.set('phantomatrix')"> Change username to: 'phantomatrix' </button>Β  <span x-text="$refs.div._x_model.get()"></span></div>
241
+ <div x-data="{ username: 'calebporzio' }">
242
+ <div x-ref="div" x-model="username"></div>
243
+
244
+ <button @click="$refs.div._x_model.set('phantomatrix')">
245
+ Change username to: 'phantomatrix'
246
+ </button>
247
+
248
+ <span x-text="$refs.div._x_model.get()"></span>
249
+ </div>
250
+
251
+ Change username to: 'phantomatrix' calebporzio
252
+
253
+ [← x-html](/directives/html)
254
+
255
+ [x-modelable β†’](/directives/modelable)
256
+
257
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-on.md ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-on
2
+ ====
3
+
4
+ `x-on` allows you to easily run code on dispatched DOM events.
5
+
6
+ Here's an example of simple button that shows an alert when clicked.
7
+
8
+ <button x-on:click="alert('Hello World!')">Say Hi</button>
9
+ <button x-on:click="alert('Hello World!')">Say Hi</button>
10
+
11
+ > `x-on` can only listen for events with lower case names, as HTML attributes are case-insensitive. Writing `x-on:CLICK` will listen for an event named `click`. If you need to listen for a custom event with a camelCase name, you can use the [`.camel` helper](#camel) to work around this limitation. Alternatively, you can use [`x-bind`](/directives/bind#bind-directives) to attach an `x-on` directive to an element in javascript code (where case will be preserved).
12
+
13
+ [Shorthand syntax](#shorthand-syntax)
14
+ -------------------------------------
15
+
16
+ If `x-on:` is too verbose for your tastes, you can use the shorthand syntax: `@`.
17
+
18
+ Here's the same component as above, but using the shorthand syntax instead:
19
+
20
+ <button @click="alert('Hello World!')">Say Hi</button>
21
+ <button @click="alert('Hello World!')">Say Hi</button>
22
+
23
+ > Despite not being included in the above snippet, `x-on` cannot be used if no parent element has `x-data` defined. [β†’ Read more about `x-data`](/directives/data)
24
+
25
+ [The event object](#the-event-object)
26
+ -------------------------------------
27
+
28
+ If you wish to access the native JavaScript event object from your expression, you can use Alpine's magic `$event` property.
29
+
30
+ <button @click="alert($event.target.getAttribute('message'))" message="Hello World">Say Hi</button>
31
+ <button @click="alert($event.target.getAttribute('message'))" message="Hello World">Say Hi</button>
32
+
33
+ In addition, Alpine also passes the event object to any methods referenced without trailing parenthesis. For example:
34
+
35
+ <button @click="handleClick">...</button>Β <script> function handleClick(e) { // Now you can access the event object (e) directly }</script>
36
+ <button @click="handleClick">...</button>
37
+
38
+ <script>
39
+ function handleClick(e) {
40
+ // Now you can access the event object (e) directly
41
+ }
42
+ </script>
43
+
44
+ [Keyboard events](#keyboard-events)
45
+ -----------------------------------
46
+
47
+ Alpine makes it easy to listen for `keydown` and `keyup` events on specific keys.
48
+
49
+ Here's an example of listening for the `Enter` key inside an input element.
50
+
51
+ <input type="text" @keyup.enter="alert('Submitted!')">
52
+ <input type="text" @keyup.enter="alert('Submitted!')">
53
+
54
+ You can also chain these key modifiers to achieve more complex listeners.
55
+
56
+ Here's a listener that runs when the `Shift` key is held and `Enter` is pressed, but not when `Enter` is pressed alone.
57
+
58
+ <input type="text" @keyup.shift.enter="alert('Submitted!')">
59
+ <input type="text" @keyup.shift.enter="alert('Submitted!')">
60
+
61
+ You can directly use any valid key names exposed via [`KeyboardEvent.key`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values) as modifiers by converting them to kebab-case.
62
+
63
+ <input type="text" @keyup.page-down="alert('Submitted!')">
64
+ <input type="text" @keyup.page-down="alert('Submitted!')">
65
+
66
+ For easy reference, here is a list of common keys you may want to listen for.
67
+
68
+ Modifier
69
+
70
+ Keyboard Key
71
+
72
+ `.shift`
73
+
74
+ Shift
75
+
76
+ `.enter`
77
+
78
+ Enter
79
+
80
+ `.space`
81
+
82
+ Space
83
+
84
+ `.ctrl`
85
+
86
+ Ctrl
87
+
88
+ `.cmd`
89
+
90
+ Cmd
91
+
92
+ `.meta`
93
+
94
+ Cmd on Mac, Windows key on Windows
95
+
96
+ `.alt`
97
+
98
+ Alt
99
+
100
+ `.up` `.down` `.left` `.right`
101
+
102
+ Up/Down/Left/Right arrows
103
+
104
+ `.escape`
105
+
106
+ Escape
107
+
108
+ `.tab`
109
+
110
+ Tab
111
+
112
+ `.caps-lock`
113
+
114
+ Caps Lock
115
+
116
+ `.equal`
117
+
118
+ Equal, `=`
119
+
120
+ `.period`
121
+
122
+ Period, `.`
123
+
124
+ `.comma`
125
+
126
+ Comma, `,`
127
+
128
+ `.slash`
129
+
130
+ Forward Slash, `/`
131
+
132
+ [Mouse events](#mouse-events)
133
+ -----------------------------
134
+
135
+ Like the above Keyboard Events, Alpine allows the use of some key modifiers for handling `click` events.
136
+
137
+ Modifier
138
+
139
+ Event Key
140
+
141
+ `.shift`
142
+
143
+ shiftKey
144
+
145
+ `.ctrl`
146
+
147
+ ctrlKey
148
+
149
+ `.cmd`
150
+
151
+ metaKey
152
+
153
+ `.meta`
154
+
155
+ metaKey
156
+
157
+ `.alt`
158
+
159
+ altKey
160
+
161
+ These work on `click`, `auxclick`, `context` and `dblclick` events, and even `mouseover`, `mousemove`, `mouseenter`, `mouseleave`, `mouseout`, `mouseup` and `mousedown`.
162
+
163
+ Here's an example of a button that changes behaviour when the `Shift` key is held down.
164
+
165
+ <button type="button" @click="message = 'selected'" @click.shift="message = 'added to selection'"> @mousemove.shift="message = 'add to selection'" @mouseout="message = 'select'" x-text="message"></button>
166
+ <button type="button"
167
+ @click="message = 'selected'"
168
+ @click.shift="message = 'added to selection'">
169
+ @mousemove.shift="message = 'add to selection'"
170
+ @mouseout="message = 'select'"
171
+ x-text="message"></button>
172
+
173
+ > Note: Normal click events with some modifiers (like `ctrl`) will automatically become `contextmenu` events in most browsers. Similarly, `right-click` events will trigger a `contextmenu` event, but will also trigger an `auxclick` event if the `contextmenu` event is prevented.
174
+
175
+ [Custom events](#custom-events)
176
+ -------------------------------
177
+
178
+ Alpine event listeners are a wrapper for native DOM event listeners. Therefore, they can listen for ANY DOM event, including custom events.
179
+
180
+ Here's an example of a component that dispatches a custom DOM event and listens for it as well.
181
+
182
+ <div x-data @foo="alert('Button Was Clicked!')"> <button @click="$event.target.dispatchEvent(new CustomEvent('foo', { bubbles: true }))">...</button></div>
183
+ <div x-data @foo="alert('Button Was Clicked!')">
184
+ <button @click="$event.target.dispatchEvent(new CustomEvent('foo', { bubbles: true }))">...</button>
185
+ </div>
186
+
187
+ When the button is clicked, the `@foo` listener will be called.
188
+
189
+ Because the `.dispatchEvent` API is verbose, Alpine offers a `$dispatch` helper to simplify things.
190
+
191
+ Here's the same component re-written with the `$dispatch` magic property.
192
+
193
+ <div x-data @foo="alert('Button Was Clicked!')"> <button @click="$dispatch('foo')">...</button></div>
194
+ <div x-data @foo="alert('Button Was Clicked!')">
195
+ <button @click="$dispatch('foo')">...</button>
196
+ </div>
197
+
198
+ [β†’ Read more about `$dispatch`](/magics/dispatch)
199
+
200
+ [Modifiers](#modifiers)
201
+ -----------------------
202
+
203
+ Alpine offers a number of directive modifiers to customize the behavior of your event listeners.
204
+
205
+ ### [.prevent](#prevent)
206
+
207
+ `.prevent` is the equivalent of calling `.preventDefault()` inside a listener on the browser event object.
208
+
209
+ <form @submit.prevent="console.log('submitted')" action="/foo"> <button>Submit</button></form>
210
+ <form @submit.prevent="console.log('submitted')" action="/foo">
211
+ <button>Submit</button>
212
+ </form>
213
+
214
+ In the above example, with the `.prevent`, clicking the button will NOT submit the form to the `/foo` endpoint. Instead, Alpine's listener will handle it and "prevent" the event from being handled any further.
215
+
216
+ ### [.stop](#stop)
217
+
218
+ Similar to `.prevent`, `.stop` is the equivalent of calling `.stopPropagation()` inside a listener on the browser event object.
219
+
220
+ <div @click="console.log('I will not get logged')"> <button @click.stop>Click Me</button></div>
221
+ <div @click="console.log('I will not get logged')">
222
+ <button @click.stop>Click Me</button>
223
+ </div>
224
+
225
+ In the above example, clicking the button WON'T log the message. This is because we are stopping the propagation of the event immediately and not allowing it to "bubble" up to the `<div>` with the `@click` listener on it.
226
+
227
+ ### [.outside](#outside)
228
+
229
+ `.outside` is a convenience helper for listening for a click outside of the element it is attached to. Here's a simple dropdown component example to demonstrate:
230
+
231
+ <div x-data="{ open: false }"> <button @click="open = ! open">Toggle</button>Β  <div x-show="open" @click.outside="open = false"> Contents... </div></div>
232
+ <div x-data="{ open: false }">
233
+ <button @click="open = ! open">Toggle</button>
234
+
235
+ <div x-show="open" @click.outside="open = false">
236
+ Contents...
237
+ </div>
238
+ </div>
239
+
240
+ In the above example, after showing the dropdown contents by clicking the "Toggle" button, you can close the dropdown by clicking anywhere on the page outside the content.
241
+
242
+ This is because `.outside` is listening for clicks that DON'T originate from the element it's registered on.
243
+
244
+ > It's worth noting that the `.outside` expression will only be evaluated when the element it's registered on is visible on the page. Otherwise, there would be nasty race conditions where clicking the "Toggle" button would also fire the `@click.outside` handler when it is not visible.
245
+
246
+ ### [.window](#window)
247
+
248
+ When the `.window` modifier is present, Alpine will register the event listener on the root `window` object on the page instead of the element itself.
249
+
250
+ <div @keyup.escape.window="...">...</div>
251
+ <div @keyup.escape.window="...">...</div>
252
+
253
+ The above snippet will listen for the "escape" key to be pressed ANYWHERE on the page.
254
+
255
+ Adding `.window` to listeners is extremely useful for these sorts of cases where a small part of your markup is concerned with events that take place on the entire page.
256
+
257
+ ### [.document](#document)
258
+
259
+ `.document` works similarly to `.window` only it registers listeners on the `document` global, instead of the `window` global.
260
+
261
+ ### [.once](#once)
262
+
263
+ By adding `.once` to a listener, you are ensuring that the handler is only called ONCE.
264
+
265
+ <button @click.once="console.log('I will only log once')">...</button>
266
+ <button @click.once="console.log('I will only log once')">...</button>
267
+
268
+ ### [.debounce](#debounce)
269
+
270
+ Sometimes it is useful to "debounce" an event handler so that it only is called after a certain period of inactivity (250 milliseconds by default).
271
+
272
+ For example if you have a search field that fires network requests as the user types into it, adding a debounce will prevent the network requests from firing on every single keystroke.
273
+
274
+ <input @input.debounce="fetchResults">
275
+ <input @input.debounce="fetchResults">
276
+
277
+ Now, instead of calling `fetchResults` after every keystroke, `fetchResults` will only be called after 250 milliseconds of no keystrokes.
278
+
279
+ If you wish to lengthen or shorten the debounce time, you can do so by trailing a duration after the `.debounce` modifier like so:
280
+
281
+ <input @input.debounce.500ms="fetchResults">
282
+ <input @input.debounce.500ms="fetchResults">
283
+
284
+ Now, `fetchResults` will only be called after 500 milliseconds of inactivity.
285
+
286
+ ### [.throttle](#throttle)
287
+
288
+ `.throttle` is similar to `.debounce` except it will release a handler call every 250 milliseconds instead of deferring it indefinitely.
289
+
290
+ This is useful for cases where there may be repeated and prolonged event firing and using `.debounce` won't work because you want to still handle the event every so often.
291
+
292
+ For example:
293
+
294
+ <div @scroll.window.throttle="handleScroll">...</div>
295
+ <div @scroll.window.throttle="handleScroll">...</div>
296
+
297
+ The above example is a great use case of throttling. Without `.throttle`, the `handleScroll` method would be fired hundreds of times as the user scrolls down a page. This can really slow down a site. By adding `.throttle`, we are ensuring that `handleScroll` only gets called every 250 milliseconds.
298
+
299
+ > Fun Fact: This exact strategy is used on this very documentation site to update the currently highlighted section in the right sidebar.
300
+
301
+ Just like with `.debounce`, you can add a custom duration to your throttled event:
302
+
303
+ <div @scroll.window.throttle.750ms="handleScroll">...</div>
304
+ <div @scroll.window.throttle.750ms="handleScroll">...</div>
305
+
306
+ Now, `handleScroll` will only be called every 750 milliseconds.
307
+
308
+ ### [.self](#self)
309
+
310
+ By adding `.self` to an event listener, you are ensuring that the event originated on the element it is declared on, and not from a child element.
311
+
312
+ <button @click.self="handleClick"> Click MeΒ  <img src="..."></button>
313
+ <button @click.self="handleClick">
314
+ Click Me
315
+
316
+ <img src="...">
317
+ </button>
318
+
319
+ In the above example, we have an `<img>` tag inside the `<button>` tag. Normally, any click originating within the `<button>` element (like on `<img>` for example), would be picked up by a `@click` listener on the button.
320
+
321
+ However, in this case, because we've added a `.self`, only clicking the button itself will call `handleClick`. Only clicks originating on the `<img>` element will not be handled.
322
+
323
+ ### [.camel](#camel)
324
+
325
+ <div @custom-event.camel="handleCustomEvent"> ...</div>
326
+ <div @custom-event.camel="handleCustomEvent">
327
+ ...
328
+ </div>
329
+
330
+ Sometimes you may want to listen for camelCased events such as `customEvent` in our example. Because camelCasing inside HTML attributes is not supported, adding the `.camel` modifier is necessary for Alpine to camelCase the event name internally.
331
+
332
+ By adding `.camel` in the above example, Alpine is now listening for `customEvent` instead of `custom-event`.
333
+
334
+ ### [.dot](#dot)
335
+
336
+ <div @custom-event.dot="handleCustomEvent"> ...</div>
337
+ <div @custom-event.dot="handleCustomEvent">
338
+ ...
339
+ </div>
340
+
341
+ Similar to the `.camelCase` modifier there may be situations where you want to listen for events that have dots in their name (like `custom.event`). Since dots within the event name are reserved by Alpine you need to write them with dashes and add the `.dot` modifier.
342
+
343
+ In the code example above `custom-event.dot` will correspond to the event name `custom.event`.
344
+
345
+ ### [.passive](#passive)
346
+
347
+ Browsers optimize scrolling on pages to be fast and smooth even when JavaScript is being executed on the page. However, improperly implemented touch and wheel listeners can block this optimization and cause poor site performance.
348
+
349
+ If you are listening for touch events, it's important to add `.passive` to your listeners to not block scroll performance.
350
+
351
+ <div @touchstart.passive="...">...</div>
352
+ <div @touchstart.passive="...">...</div>
353
+
354
+ [β†’ Read more about passive listeners](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners)
355
+
356
+ ### .capture
357
+
358
+ Add this modifier if you want to execute this listener in the event's capturing phase, e.g. before the event bubbles from the target element up the DOM.
359
+
360
+ <div @click.capture="console.log('I will log first')"> <button @click="console.log('I will log second')"></button></div>
361
+ <div @click.capture="console.log('I will log first')">
362
+ <button @click="console.log('I will log second')"></button>
363
+ </div>
364
+
365
+ [β†’ Read more about the capturing and bubbling phase of events](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#usecapture)
366
+
367
+ [← x-bind](/directives/bind)
368
+
369
+ [x-text β†’](/directives/text)
370
+
371
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-ref.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-ref
2
+ =====
3
+
4
+ `x-ref` in combination with `$refs` is a useful utility for easily accessing DOM elements directly. It's most useful as a replacement for APIs like `getElementById` and `querySelector`.
5
+
6
+ <button @click="$refs.text.remove()">Remove Text</button>Β <span x-ref="text">Hello πŸ‘‹</span>
7
+ <button @click="$refs.text.remove()">Remove Text</button>
8
+
9
+ <span x-ref="text">Hello πŸ‘‹</span>
10
+
11
+ Remove Text
12
+
13
+ Hello πŸ‘‹
14
+
15
+ > Despite not being included in the above snippet, `x-ref` cannot be used if no parent element has `x-data` defined. [β†’ Read more about `x-data`](/directives/data)
16
+
17
+ [← x-ignore](/directives/ignore)
18
+
19
+ [x-cloak β†’](/directives/cloak)
20
+
21
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-show.md ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-show
2
+ ======
3
+
4
+ `x-show` is one of the most useful and powerful directives in Alpine. It provides an expressive way to show and hide DOM elements.
5
+
6
+ Here's an example of a simple dropdown component using `x-show`.
7
+
8
+ <div x-data="{ open: false }"> <button x-on:click="open = ! open">Toggle Dropdown</button>Β  <div x-show="open"> Dropdown Contents... </div></div>
9
+ <div x-data="{ open: false }">
10
+ <button x-on:click="open = ! open">Toggle Dropdown</button>
11
+
12
+ <div x-show="open">
13
+ Dropdown Contents...
14
+ </div>
15
+ </div>
16
+
17
+ When the "Toggle Dropdown" button is clicked, the dropdown will show and hide accordingly.
18
+
19
+ > If the "default" state of an `x-show` on page load is "false", you may want to use `x-cloak` on the page to avoid "page flicker" (The effect that happens when the browser renders your content before Alpine is finished initializing and hiding it.) You can learn more about `x-cloak` in its documentation.
20
+
21
+ [With transitions](#with-transitions)
22
+ -------------------------------------
23
+
24
+ If you want to apply smooth transitions to the `x-show` behavior, you can use it in conjunction with `x-transition`. You can learn more about that directive [here](/directives/transition), but here's a quick example of the same component as above, just with transitions applied.
25
+
26
+ <div x-data="{ open: false }"> <button x-on:click="open = ! open">Toggle Dropdown</button>Β  <div x-show="open" x-transition> Dropdown Contents... </div></div>
27
+ <div x-data="{ open: false }">
28
+ <button x-on:click="open = ! open">Toggle Dropdown</button>
29
+
30
+ <div x-show="open" x-transition>
31
+ Dropdown Contents...
32
+ </div>
33
+ </div>
34
+
35
+ [Using the important modifier](#using-the-important-modifier)
36
+ -------------------------------------------------------------
37
+
38
+ Sometimes you need to apply a little more force to actually hide an element. In cases where a CSS selector applies the `display` property with the `!important` flag, it will take precedence over the inline style set by Alpine.
39
+
40
+ In these cases you may use the `.important` modifier to set the inline style to `display: none !important`.
41
+
42
+ <div x-data="{ open: false }"> <button x-on:click="open = ! open">Toggle Dropdown</button>Β  <div x-show.important="open"> Dropdown Contents... </div></div>
43
+ <div x-data="{ open: false }">
44
+ <button x-on:click="open = ! open">Toggle Dropdown</button>
45
+
46
+ <div x-show.important="open">
47
+ Dropdown Contents...
48
+ </div>
49
+ </div>
50
+
51
+ [← x-init](/directives/init)
52
+
53
+ [x-bind β†’](/directives/bind)
54
+
55
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-teleport.md ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-teleport
2
+ ==========
3
+
4
+ The `x-teleport` directive allows you to transport part of your Alpine template to another part of the DOM on the page entirely.
5
+
6
+ This is useful for things like modals (especially nesting them), where it's helpful to break out of the z-index of the current Alpine component.
7
+
8
+ [x-teleport](#x-teleport)
9
+ -------------------------
10
+
11
+ By attaching `x-teleport` to a `<template>` element, you are telling Alpine to "append" that element to the provided selector.
12
+
13
+ > The `x-teleport` selector can be any string you would normally pass into something like `document.querySelector`. It will find the first element that matches, be it a tag name (`body`), class name (`.my-class`), ID (`#my-id`), or any other valid CSS selector.
14
+
15
+ [β†’ Read more about `document.querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
16
+
17
+ Here's a contrived modal example:
18
+
19
+ <body> <div x-data="{ open: false }"> <button @click="open = ! open">Toggle Modal</button>Β  <template x-teleport="body"> <div x-show="open"> Modal contents... </div> </template> </div>Β  <div>Some other content placed AFTER the modal markup.</div>Β  ...Β </body>
20
+ <body>
21
+ <div x-data="{ open: false }">
22
+ <button @click="open = ! open">Toggle Modal</button>
23
+
24
+ <template x-teleport="body">
25
+ <div x-show="open">
26
+ Modal contents...
27
+ </div>
28
+ </template>
29
+ </div>
30
+
31
+ <div>Some other content placed AFTER the modal markup.</div>
32
+
33
+ ...
34
+
35
+ </body>
36
+
37
+ Toggle Modal
38
+
39
+ Some other content placed AFTER the modal markup.
40
+
41
+ Modal contents...
42
+
43
+ Notice how when toggling the modal, the actual modal contents show up AFTER the "Some other content..." element? This is because when Alpine is initializing, it sees `x-teleport="body"` and appends and initializes that element to the provided element selector.
44
+
45
+ [Forwarding events](#forwarding-events)
46
+ ---------------------------------------
47
+
48
+ Alpine tries its best to make the experience of teleporting seamless. Anything you would normally do in a template, you should be able to do inside an `x-teleport` template. Teleported content can access the normal Alpine scope of the component as well as other features like `$refs`, `$root`, etc...
49
+
50
+ However, native DOM events have no concept of teleportation, so if, for example, you trigger a "click" event from inside a teleported element, that event will bubble up the DOM tree as it normally would.
51
+
52
+ To make this experience more seamless, you can "forward" events by simply registering event listeners on the `<template x-teleport...>` element itself like so:
53
+
54
+ <div x-data="{ open: false }"> <button @click="open = ! open">Toggle Modal</button>Β  <template x-teleport="body" @click="open = false"> <div x-show="open"> Modal contents... (click to close) </div> </template></div>
55
+ <div x-data="{ open: false }">
56
+ <button @click="open = ! open">Toggle Modal</button>
57
+
58
+ <template x-teleport="body" @click="open = false">
59
+ <div x-show="open">
60
+ Modal contents...
61
+ (click to close)
62
+ </div>
63
+ </template>
64
+ </div>
65
+
66
+ Toggle Modal
67
+
68
+ Modal contents...
69
+
70
+ (click to close)
71
+
72
+ Notice how we are now able to listen for events dispatched from within the teleported element from outside the `<template>` element itself?
73
+
74
+ Alpine does this by looking for event listeners registered on `<template x-teleport...>` and stops those events from propagating past the live, teleported, DOM element. Then, it creates a copy of that event and re-dispatches it from `<template x-teleport...>`.
75
+
76
+ [Nesting](#nesting)
77
+ -------------------
78
+
79
+ Teleporting is especially helpful if you are trying to nest one modal within another. Alpine makes it simple to do so:
80
+
81
+ <div x-data="{ open: false }"> <button @click="open = ! open">Toggle Modal</button>Β  <template x-teleport="body"> <div x-show="open"> Modal contents...Β  <div x-data="{ open: false }"> <button @click="open = ! open">Toggle Nested Modal</button>Β  <template x-teleport="body"> <div x-show="open"> Nested modal contents... </div> </template> </div> </div> </template></div>
82
+ <div x-data="{ open: false }">
83
+ <button @click="open = ! open">Toggle Modal</button>
84
+
85
+ <template x-teleport="body">
86
+ <div x-show="open">
87
+ Modal contents...
88
+
89
+ <div x-data="{ open: false }">
90
+ <button @click="open = ! open">Toggle Nested Modal</button>
91
+
92
+ <template x-teleport="body">
93
+ <div x-show="open">
94
+ Nested modal contents...
95
+ </div>
96
+ </template>
97
+ </div>
98
+ </div>
99
+ </template>
100
+ </div>
101
+
102
+ Toggle Modal
103
+
104
+ Modal contents...
105
+
106
+ Toggle Nested Modal
107
+
108
+ Nested modal contents...
109
+
110
+ After toggling "on" both modals, they are authored as children, but will be rendered as sibling elements on the page, not within one another.
111
+
112
+ [← x-cloak](/directives/cloak)
113
+
114
+ [x-if β†’](/directives/if)
115
+
116
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-text.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-text
2
+ ======
3
+
4
+ `x-text` sets the text content of an element to the result of a given expression.
5
+
6
+ Here's a basic example of using `x-text` to display a user's username.
7
+
8
+ <div x-data="{ username: 'calebporzio' }"> Username: <strong x-text="username"></strong></div>
9
+ <div x-data="{ username: 'calebporzio' }">
10
+ Username: <strong x-text="username"></strong>
11
+ </div>
12
+
13
+ Username: **calebporzio**
14
+
15
+ Now the `<strong>` tag's inner text content will be set to "calebporzio".
16
+
17
+ [← x-on](/directives/on)
18
+
19
+ [x-html β†’](/directives/html)
20
+
21
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/directive-x-transition.md ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ x-transition
2
+ ============
3
+
4
+ Alpine provides a robust transitions utility out of the box. With a few `x-transition` directives, you can create smooth transitions between when an element is shown or hidden.
5
+
6
+ There are two primary ways to handle transitions in Alpine:
7
+
8
+ * [The Transition Helper](#the-transition-helper)
9
+ * [Applying CSS Classes](#applying-css-classes)
10
+
11
+ [The transition helper](#the-transition-helper)
12
+ -----------------------------------------------
13
+
14
+ The simplest way to achieve a transition using Alpine is by adding `x-transition` to an element with `x-show` on it. For example:
15
+
16
+ <div x-data="{ open: false }"> <button @click="open = ! open">Toggle</button>Β  <div x-show="open" x-transition> Hello πŸ‘‹ </div></div>
17
+ <div x-data="{ open: false }">
18
+ <button @click="open = ! open">Toggle</button>
19
+
20
+ <div x-show="open" x-transition>
21
+ Hello πŸ‘‹
22
+ </div>
23
+ </div>
24
+
25
+ Toggle
26
+
27
+ Hello πŸ‘‹
28
+
29
+ As you can see, by default, `x-transition` applies pleasant transition defaults to fade and scale the revealing element.
30
+
31
+ You can override these defaults with modifiers attached to `x-transition`. Let's take a look at those.
32
+
33
+ ### [Customizing duration](#customizing-duration)
34
+
35
+ Initially, the duration is set to be 150 milliseconds when entering, and 75 milliseconds when leaving.
36
+
37
+ You can configure the duration you want for a transition with the `.duration` modifier:
38
+
39
+ <div ... x-transition.duration.500ms>
40
+ <div ... x-transition.duration.500ms>
41
+
42
+ The above `<div>` will transition for 500 milliseconds when entering, and 500 milliseconds when leaving.
43
+
44
+ If you wish to customize the durations specifically for entering and leaving, you can do that like so:
45
+
46
+ <div ... x-transition:enter.duration.500ms x-transition:leave.duration.400ms>
47
+ <div ...
48
+ x-transition:enter.duration.500ms
49
+ x-transition:leave.duration.400ms
50
+ >
51
+
52
+ > Despite not being included in the above snippet, `x-transition` cannot be used if no parent element has `x-data` defined. [β†’ Read more about `x-data`](/directives/data)
53
+
54
+ ### [Customizing delay](#customizing-delay)
55
+
56
+ You can delay a transition using the `.delay` modifier like so:
57
+
58
+ <div ... x-transition.delay.50ms>
59
+ <div ... x-transition.delay.50ms>
60
+
61
+ The above example will delay the transition and in and out of the element by 50 milliseconds.
62
+
63
+ ### [Customizing opacity](#customizing-opacity)
64
+
65
+ By default, Alpine's `x-transition` applies both a scale and opacity transition to achieve a "fade" effect.
66
+
67
+ If you wish to only apply the opacity transition (no scale), you can accomplish that like so:
68
+
69
+ <div ... x-transition.opacity>
70
+ <div ... x-transition.opacity>
71
+
72
+ ### [Customizing scale](#customizing-scale)
73
+
74
+ Similar to the `.opacity` modifier, you can configure `x-transition` to ONLY scale (and not transition opacity as well) like so:
75
+
76
+ <div ... x-transition.scale>
77
+ <div ... x-transition.scale>
78
+
79
+ The `.scale` modifier also offers the ability to configure its scale values AND its origin values:
80
+
81
+ <div ... x-transition.scale.80>
82
+ <div ... x-transition.scale.80>
83
+
84
+ The above snippet will scale the element up and down by 80%.
85
+
86
+ Again, you may customize these values separately for enter and leaving transitions like so:
87
+
88
+ <div ... x-transition:enter.scale.80 x-transition:leave.scale.90>
89
+ <div ...
90
+ x-transition:enter.scale.80
91
+ x-transition:leave.scale.90
92
+ >
93
+
94
+ To customize the origin of the scale transition, you can use the `.origin` modifier:
95
+
96
+ <div ... x-transition.scale.origin.top>
97
+ <div ... x-transition.scale.origin.top>
98
+
99
+ Now the scale will be applied using the top of the element as the origin, instead of the center by default.
100
+
101
+ Like you may have guessed, the possible values for this customization are: `top`, `bottom`, `left`, and `right`.
102
+
103
+ If you wish, you can also combine two origin values. For example, if you want the origin of the scale to be "top right", you can use: `.origin.top.right` as the modifier.
104
+
105
+ [Applying CSS classes](#applying-css-classes)
106
+ ---------------------------------------------
107
+
108
+ For direct control over exactly what goes into your transitions, you can apply CSS classes at different stages of the transition.
109
+
110
+ > The following examples use [TailwindCSS](https://tailwindcss.com/docs/transition-property) utility classes.
111
+
112
+ <div x-data="{ open: false }"> <button @click="open = ! open">Toggle</button>Β  <div x-show="open" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 scale-90" x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-300" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-90" >Hello πŸ‘‹</div></div>
113
+ <div x-data="{ open: false }">
114
+ <button @click="open = ! open">Toggle</button>
115
+
116
+ <div
117
+ x-show="open"
118
+ x-transition:enter="transition ease-out duration-300"
119
+ x-transition:enter-start="opacity-0 scale-90"
120
+ x-transition:enter-end="opacity-100 scale-100"
121
+ x-transition:leave="transition ease-in duration-300"
122
+ x-transition:leave-start="opacity-100 scale-100"
123
+ x-transition:leave-end="opacity-0 scale-90"
124
+ >Hello πŸ‘‹</div>
125
+ </div>
126
+
127
+ Toggle
128
+
129
+ Hello πŸ‘‹
130
+
131
+ Directive
132
+
133
+ Description
134
+
135
+ `:enter`
136
+
137
+ Applied during the entire entering phase.
138
+
139
+ `:enter-start`
140
+
141
+ Added before element is inserted, removed one frame after element is inserted.
142
+
143
+ `:enter-end`
144
+
145
+ Added one frame after element is inserted (at the same time `enter-start` is removed), removed when transition/animation finishes.
146
+
147
+ `:leave`
148
+
149
+ Applied during the entire leaving phase.
150
+
151
+ `:leave-start`
152
+
153
+ Added immediately when a leaving transition is triggered, removed after one frame.
154
+
155
+ `:leave-end`
156
+
157
+ Added one frame after a leaving transition is triggered (at the same time `leave-start` is removed), removed when the transition/animation finishes.
158
+
159
+ [← x-for](/directives/for)
160
+
161
+ [x-effect β†’](/directives/effect)
162
+
163
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/magic-function-$data.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $data
2
+ =====
3
+
4
+ `$data` is a magic property that gives you access to the current Alpine data scope (generally provided by `x-data`).
5
+
6
+ Most of the time, you can just access Alpine data within expressions directly. for example `x-data="{ message: 'Hello Caleb!' }"` will allow you to do things like `x-text="message"`.
7
+
8
+ However, sometimes it is helpful to have an actual object that encapsulates all scope that you can pass around to other functions:
9
+
10
+ <div x-data="{ greeting: 'Hello' }"> <div x-data="{ name: 'Caleb' }"> <button @click="sayHello($data)">Say Hello</button> </div></div>Β <script> function sayHello({ greeting, name }) { alert(greeting + ' ' + name + '!') }</script>
11
+ <div x-data="{ greeting: 'Hello' }">
12
+ <div x-data="{ name: 'Caleb' }">
13
+ <button @click="sayHello($data)">Say Hello</button>
14
+ </div>
15
+ </div>
16
+
17
+ <script>
18
+ function sayHello({ greeting, name }) {
19
+ alert(greeting + ' ' + name + '!')
20
+ }
21
+ </script>
22
+
23
+ Say Hello
24
+
25
+ function sayHello({ greeting, name }) { alert(greeting + ' ' + name + '!') }
26
+
27
+ Now when the button is pressed, the browser will alert `Hello Caleb!` because it was passed a data object that contained all the Alpine scope of the expression that called it (`@click="..."`).
28
+
29
+ Most applications won't need this magic property, but it can be very helpful for deeper, more complicated Alpine utilities.
30
+
31
+ [← $root](/magics/root)
32
+
33
+ [$id β†’](/magics/id)
34
+
35
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
docs/alpinejs/magic-function-$watch.md ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $watch
2
+ ======
3
+
4
+ You can "watch" a component property using the `$watch` magic method. For example:
5
+
6
+ <div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))"> <button @click="open = ! open">Toggle Open</button></div>
7
+ <div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))">
8
+ <button @click="open = ! open">Toggle Open</button>
9
+ </div>
10
+
11
+ In the above example, when the button is pressed and `open` is changed, the provided callback will fire and `console.log` the new value:
12
+
13
+ You can watch deeply nested properties using "dot" notation
14
+
15
+ <div x-data="{ foo: { bar: 'baz' }}" x-init="$watch('foo.bar', value => console.log(value))"> <button @click="foo.bar = 'bob'">Toggle Open</button></div>
16
+ <div x-data="{ foo: { bar: 'baz' }}" x-init="$watch('foo.bar', value => console.log(value))">
17
+ <button @click="foo.bar = 'bob'">Toggle Open</button>
18
+ </div>
19
+
20
+ When the `<button>` is pressed, `foo.bar` will be set to "bob", and "bob" will be logged to the console.
21
+
22
+ ### [Getting the "old" value](#getting-the-old-value)
23
+
24
+ `$watch` keeps track of the previous value of the property being watched, You can access it using the optional second argument to the callback like so:
25
+
26
+ <div x-data="{ open: false }" x-init="$watch('open', (value, oldValue) => console.log(value, oldValue))"> <button @click="open = ! open">Toggle Open</button></div>
27
+ <div x-data="{ open: false }" x-init="$watch('open', (value, oldValue) => console.log(value, oldValue))">
28
+ <button @click="open = ! open">Toggle Open</button>
29
+ </div>
30
+
31
+ ### [Deep watching](#deep-watching)
32
+
33
+ `$watch` automatically watches from changes at any level but you should keep in mind that, when a change is detected, the watcher will return the value of the observed property, not the value of the subproperty that has changed.
34
+
35
+ <div x-data="{ foo: { bar: 'baz' }}" x-init="$watch('foo', (value, oldValue) => console.log(value, oldValue))"> <button @click="foo.bar = 'bob'">Update</button></div>
36
+ <div x-data="{ foo: { bar: 'baz' }}" x-init="$watch('foo', (value, oldValue) => console.log(value, oldValue))">
37
+ <button @click="foo.bar = 'bob'">Update</button>
38
+ </div>
39
+
40
+ When the `<button>` is pressed, `foo.bar` will be set to "bob", and "{bar: 'bob'} {bar: 'baz'}" will be logged to the console (new and old value).
41
+
42
+ > ⚠️ Changing a property of a "watched" object as a side effect of the `$watch` callback will generate an infinite loop and eventually error.
43
+
44
+ <!-- 🚫 Infinite loop --><div x-data="{ foo: { bar: 'baz', bob: 'lob' }}" x-init="$watch('foo', value => foo.bob = foo.bar)"> <button @click="foo.bar = 'bob'">Update</button></div>
45
+ <!-- 🚫 Infinite loop -->
46
+ <div x-data="{ foo: { bar: 'baz', bob: 'lob' }}" x-init="$watch('foo', value => foo.bob = foo.bar)">
47
+ <button @click="foo.bar = 'bob'">Update</button>
48
+ </div>
49
+
50
+ [← $store](/magics/store)
51
+
52
+ [$dispatch β†’](/magics/dispatch)
53
+
54
+ Code highlighting provided by [Torchlight](https://torchlight.dev)
package-lock.json CHANGED
@@ -9,8 +9,8 @@
9
  "version": "1.0.0",
10
  "license": "Apache License",
11
  "dependencies": {
12
- "@huggingface/hub": "^0.8.3",
13
- "@huggingface/inference": "^2.6.1",
14
  "@types/express": "^4.17.17",
15
  "express": "^4.18.2",
16
  "slugify": "^1.6.6",
@@ -30,36 +30,53 @@
30
  }
31
  },
32
  "node_modules/@huggingface/hub": {
33
- "version": "0.8.7",
34
- "resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-0.8.7.tgz",
35
- "integrity": "sha512-BxfPOM/f+RdS2J5uNBRwCllTfH1FMKvWlJrjmybbqxz1CJLIuunBjB2HlEpGlDv8zW1mBuSJy9+eiFzrpgCiXA==",
36
  "dependencies": {
37
- "hash-wasm": "^4.9.0"
38
  },
39
  "engines": {
40
  "node": ">=18"
41
  }
42
  },
43
  "node_modules/@huggingface/inference": {
44
- "version": "2.6.4",
45
- "resolved": "https://registry.npmjs.org/@huggingface/inference/-/inference-2.6.4.tgz",
46
- "integrity": "sha512-Xna7arltBSBoKaH3diGi3sYvkExgJMd/pF4T6vl2YbmDccbr1G/X5EPZ2048p+YgrJYG1jTYFCtY6Dr3HvJaow==",
 
 
 
 
 
 
 
 
 
 
 
 
47
  "engines": {
48
  "node": ">=18"
49
  }
50
  },
 
 
 
 
 
51
  "node_modules/@jridgewell/resolve-uri": {
52
- "version": "3.1.1",
53
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
54
- "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
55
  "engines": {
56
  "node": ">=6.0.0"
57
  }
58
  },
59
  "node_modules/@jridgewell/sourcemap-codec": {
60
- "version": "1.4.15",
61
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
62
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
63
  },
64
  "node_modules/@jridgewell/trace-mapping": {
65
  "version": "0.3.9",
@@ -71,9 +88,9 @@
71
  }
72
  },
73
  "node_modules/@tsconfig/node10": {
74
- "version": "1.0.9",
75
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
76
- "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA=="
77
  },
78
  "node_modules/@tsconfig/node12": {
79
  "version": "1.0.11",
@@ -119,9 +136,9 @@
119
  }
120
  },
121
  "node_modules/@types/express-serve-static-core": {
122
- "version": "4.17.41",
123
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz",
124
- "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==",
125
  "dependencies": {
126
  "@types/node": "*",
127
  "@types/qs": "*",
@@ -140,17 +157,17 @@
140
  "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
141
  },
142
  "node_modules/@types/node": {
143
- "version": "20.10.3",
144
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz",
145
- "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==",
146
  "dependencies": {
147
- "undici-types": "~5.26.4"
148
  }
149
  },
150
  "node_modules/@types/qs": {
151
- "version": "6.9.10",
152
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz",
153
- "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw=="
154
  },
155
  "node_modules/@types/range-parser": {
156
  "version": "1.2.7",
@@ -167,13 +184,13 @@
167
  }
168
  },
169
  "node_modules/@types/serve-static": {
170
- "version": "1.15.5",
171
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz",
172
- "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==",
173
  "dependencies": {
174
  "@types/http-errors": "*",
175
- "@types/mime": "*",
176
- "@types/node": "*"
177
  }
178
  },
179
  "node_modules/accepts": {
@@ -189,9 +206,9 @@
189
  }
190
  },
191
  "node_modules/acorn": {
192
- "version": "8.11.2",
193
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
194
- "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
195
  "bin": {
196
  "acorn": "bin/acorn"
197
  },
@@ -200,9 +217,12 @@
200
  }
201
  },
202
  "node_modules/acorn-walk": {
203
- "version": "8.3.0",
204
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz",
205
- "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==",
 
 
 
206
  "engines": {
207
  "node": ">=0.4.0"
208
  }
@@ -218,20 +238,20 @@
218
  "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
219
  },
220
  "node_modules/body-parser": {
221
- "version": "1.20.1",
222
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
223
- "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
224
  "dependencies": {
225
  "bytes": "3.1.2",
226
- "content-type": "~1.0.4",
227
  "debug": "2.6.9",
228
  "depd": "2.0.0",
229
  "destroy": "1.2.0",
230
  "http-errors": "2.0.0",
231
  "iconv-lite": "0.4.24",
232
  "on-finished": "2.4.1",
233
- "qs": "6.11.0",
234
- "raw-body": "2.5.1",
235
  "type-is": "~1.6.18",
236
  "unpipe": "1.0.0"
237
  },
@@ -248,14 +268,28 @@
248
  "node": ">= 0.8"
249
  }
250
  },
251
- "node_modules/call-bind": {
252
- "version": "1.0.5",
253
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
254
- "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
255
  "dependencies": {
256
- "function-bind": "^1.1.2",
257
- "get-intrinsic": "^1.2.1",
258
- "set-function-length": "^1.1.1"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  },
260
  "funding": {
261
  "url": "https://github.com/sponsors/ljharb"
@@ -281,9 +315,9 @@
281
  }
282
  },
283
  "node_modules/cookie": {
284
- "version": "0.5.0",
285
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
286
- "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
287
  "engines": {
288
  "node": ">= 0.6"
289
  }
@@ -306,19 +340,6 @@
306
  "ms": "2.0.0"
307
  }
308
  },
309
- "node_modules/define-data-property": {
310
- "version": "1.1.1",
311
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
312
- "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
313
- "dependencies": {
314
- "get-intrinsic": "^1.2.1",
315
- "gopd": "^1.0.1",
316
- "has-property-descriptors": "^1.0.0"
317
- },
318
- "engines": {
319
- "node": ">= 0.4"
320
- }
321
- },
322
  "node_modules/depd": {
323
  "version": "2.0.0",
324
  "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -344,19 +365,59 @@
344
  "node": ">=0.3.1"
345
  }
346
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  "node_modules/ee-first": {
348
  "version": "1.1.1",
349
  "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
350
  "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
351
  },
352
  "node_modules/encodeurl": {
353
- "version": "1.0.2",
354
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
355
- "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
356
  "engines": {
357
  "node": ">= 0.8"
358
  }
359
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  "node_modules/escape-html": {
361
  "version": "1.0.3",
362
  "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -371,36 +432,36 @@
371
  }
372
  },
373
  "node_modules/express": {
374
- "version": "4.18.2",
375
- "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
376
- "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
377
  "dependencies": {
378
  "accepts": "~1.3.8",
379
  "array-flatten": "1.1.1",
380
- "body-parser": "1.20.1",
381
  "content-disposition": "0.5.4",
382
  "content-type": "~1.0.4",
383
- "cookie": "0.5.0",
384
  "cookie-signature": "1.0.6",
385
  "debug": "2.6.9",
386
  "depd": "2.0.0",
387
- "encodeurl": "~1.0.2",
388
  "escape-html": "~1.0.3",
389
  "etag": "~1.8.1",
390
- "finalhandler": "1.2.0",
391
  "fresh": "0.5.2",
392
  "http-errors": "2.0.0",
393
- "merge-descriptors": "1.0.1",
394
  "methods": "~1.1.2",
395
  "on-finished": "2.4.1",
396
  "parseurl": "~1.3.3",
397
- "path-to-regexp": "0.1.7",
398
  "proxy-addr": "~2.0.7",
399
- "qs": "6.11.0",
400
  "range-parser": "~1.2.1",
401
  "safe-buffer": "5.2.1",
402
- "send": "0.18.0",
403
- "serve-static": "1.15.0",
404
  "setprototypeof": "1.2.0",
405
  "statuses": "2.0.1",
406
  "type-is": "~1.6.18",
@@ -409,15 +470,19 @@
409
  },
410
  "engines": {
411
  "node": ">= 0.10.0"
 
 
 
 
412
  }
413
  },
414
  "node_modules/finalhandler": {
415
- "version": "1.2.0",
416
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
417
- "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
418
  "dependencies": {
419
  "debug": "2.6.9",
420
- "encodeurl": "~1.0.2",
421
  "escape-html": "~1.0.3",
422
  "on-finished": "2.4.1",
423
  "parseurl": "~1.3.3",
@@ -453,45 +518,44 @@
453
  }
454
  },
455
  "node_modules/get-intrinsic": {
456
- "version": "1.2.2",
457
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
458
- "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
459
  "dependencies": {
 
 
 
 
460
  "function-bind": "^1.1.2",
461
- "has-proto": "^1.0.1",
462
- "has-symbols": "^1.0.3",
463
- "hasown": "^2.0.0"
 
 
464
  },
465
- "funding": {
466
- "url": "https://github.com/sponsors/ljharb"
467
- }
468
- },
469
- "node_modules/gopd": {
470
- "version": "1.0.1",
471
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
472
- "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
473
- "dependencies": {
474
- "get-intrinsic": "^1.1.3"
475
  },
476
  "funding": {
477
  "url": "https://github.com/sponsors/ljharb"
478
  }
479
  },
480
- "node_modules/has-property-descriptors": {
481
  "version": "1.0.1",
482
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz",
483
- "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==",
484
  "dependencies": {
485
- "get-intrinsic": "^1.2.2"
 
486
  },
487
- "funding": {
488
- "url": "https://github.com/sponsors/ljharb"
489
  }
490
  },
491
- "node_modules/has-proto": {
492
- "version": "1.0.1",
493
- "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
494
- "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
495
  "engines": {
496
  "node": ">= 0.4"
497
  },
@@ -500,9 +564,9 @@
500
  }
501
  },
502
  "node_modules/has-symbols": {
503
- "version": "1.0.3",
504
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
505
- "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
506
  "engines": {
507
  "node": ">= 0.4"
508
  },
@@ -510,15 +574,10 @@
510
  "url": "https://github.com/sponsors/ljharb"
511
  }
512
  },
513
- "node_modules/hash-wasm": {
514
- "version": "4.11.0",
515
- "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.11.0.tgz",
516
- "integrity": "sha512-HVusNXlVqHe0fzIzdQOGolnFN6mX/fqcrSAOcTBXdvzrXVHwTz11vXeKRmkR5gTuwVpvHZEIyKoePDvuAR+XwQ=="
517
- },
518
  "node_modules/hasown": {
519
- "version": "2.0.0",
520
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
521
- "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
522
  "dependencies": {
523
  "function-bind": "^1.1.2"
524
  },
@@ -570,6 +629,14 @@
570
  "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
571
  "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
572
  },
 
 
 
 
 
 
 
 
573
  "node_modules/media-typer": {
574
  "version": "0.3.0",
575
  "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -579,9 +646,12 @@
579
  }
580
  },
581
  "node_modules/merge-descriptors": {
582
- "version": "1.0.1",
583
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
584
- "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
 
 
 
585
  },
586
  "node_modules/methods": {
587
  "version": "1.1.2",
@@ -635,9 +705,12 @@
635
  }
636
  },
637
  "node_modules/object-inspect": {
638
- "version": "1.13.1",
639
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
640
- "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
 
 
 
641
  "funding": {
642
  "url": "https://github.com/sponsors/ljharb"
643
  }
@@ -662,9 +735,9 @@
662
  }
663
  },
664
  "node_modules/path-to-regexp": {
665
- "version": "0.1.7",
666
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
667
- "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
668
  },
669
  "node_modules/proxy-addr": {
670
  "version": "2.0.7",
@@ -679,11 +752,11 @@
679
  }
680
  },
681
  "node_modules/qs": {
682
- "version": "6.11.0",
683
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
684
- "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
685
  "dependencies": {
686
- "side-channel": "^1.0.4"
687
  },
688
  "engines": {
689
  "node": ">=0.6"
@@ -701,9 +774,9 @@
701
  }
702
  },
703
  "node_modules/raw-body": {
704
- "version": "2.5.1",
705
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
706
- "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
707
  "dependencies": {
708
  "bytes": "3.1.2",
709
  "http-errors": "2.0.0",
@@ -739,9 +812,9 @@
739
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
740
  },
741
  "node_modules/send": {
742
- "version": "0.18.0",
743
- "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
744
- "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
745
  "dependencies": {
746
  "debug": "2.6.9",
747
  "depd": "2.0.0",
@@ -761,52 +834,101 @@
761
  "node": ">= 0.8.0"
762
  }
763
  },
 
 
 
 
 
 
 
 
764
  "node_modules/send/node_modules/ms": {
765
  "version": "2.1.3",
766
  "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
767
  "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
768
  },
769
  "node_modules/serve-static": {
770
- "version": "1.15.0",
771
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
772
- "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
773
  "dependencies": {
774
- "encodeurl": "~1.0.2",
775
  "escape-html": "~1.0.3",
776
  "parseurl": "~1.3.3",
777
- "send": "0.18.0"
778
  },
779
  "engines": {
780
  "node": ">= 0.8.0"
781
  }
782
  },
783
- "node_modules/set-function-length": {
784
- "version": "1.1.1",
785
- "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
786
- "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
 
 
 
 
 
787
  "dependencies": {
788
- "define-data-property": "^1.1.1",
789
- "get-intrinsic": "^1.2.1",
790
- "gopd": "^1.0.1",
791
- "has-property-descriptors": "^1.0.0"
 
792
  },
793
  "engines": {
794
  "node": ">= 0.4"
 
 
 
795
  }
796
  },
797
- "node_modules/setprototypeof": {
798
- "version": "1.2.0",
799
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
800
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
 
 
 
 
 
 
 
 
 
 
801
  },
802
- "node_modules/side-channel": {
803
- "version": "1.0.4",
804
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
805
- "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
806
  "dependencies": {
807
- "call-bind": "^1.0.0",
808
- "get-intrinsic": "^1.0.2",
809
- "object-inspect": "^1.9.0"
 
 
 
 
 
810
  },
811
  "funding": {
812
  "url": "https://github.com/sponsors/ljharb"
@@ -837,9 +959,9 @@
837
  }
838
  },
839
  "node_modules/ts-node": {
840
- "version": "10.9.1",
841
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
842
- "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
843
  "dependencies": {
844
  "@cspotcode/source-map-support": "^0.8.0",
845
  "@tsconfig/node10": "^1.0.7",
@@ -891,9 +1013,9 @@
891
  }
892
  },
893
  "node_modules/typescript": {
894
- "version": "5.3.2",
895
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
896
- "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
897
  "peer": true,
898
  "bin": {
899
  "tsc": "bin/tsc",
@@ -904,9 +1026,9 @@
904
  }
905
  },
906
  "node_modules/undici-types": {
907
- "version": "5.26.5",
908
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
909
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
910
  },
911
  "node_modules/unpipe": {
912
  "version": "1.0.0",
 
9
  "version": "1.0.0",
10
  "license": "Apache License",
11
  "dependencies": {
12
+ "@huggingface/hub": "^1.1.2",
13
+ "@huggingface/inference": "^3.6.2",
14
  "@types/express": "^4.17.17",
15
  "express": "^4.18.2",
16
  "slugify": "^1.6.6",
 
30
  }
31
  },
32
  "node_modules/@huggingface/hub": {
33
+ "version": "1.1.2",
34
+ "resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-1.1.2.tgz",
35
+ "integrity": "sha512-Jf4GhvVj9ABDw4Itb3BV1T7f22iewuZva476qTicQ4kOTbosuUuFDhsVH7ZH6rVNgg20Ll9kaNBF5CXjySIT+w==",
36
  "dependencies": {
37
+ "@huggingface/tasks": "^0.18.2"
38
  },
39
  "engines": {
40
  "node": ">=18"
41
  }
42
  },
43
  "node_modules/@huggingface/inference": {
44
+ "version": "3.6.2",
45
+ "resolved": "https://registry.npmjs.org/@huggingface/inference/-/inference-3.6.2.tgz",
46
+ "integrity": "sha512-FkJr8dOP8yFTxGFi+X/ylwG3qoCq/s8WI2A4Jg13RBX3voJWrzadpHLD/99EKZU7r35X4Mm6tKUev7dR2B/cTg==",
47
+ "dependencies": {
48
+ "@huggingface/jinja": "^0.3.3",
49
+ "@huggingface/tasks": "^0.18.4"
50
+ },
51
+ "engines": {
52
+ "node": ">=18"
53
+ }
54
+ },
55
+ "node_modules/@huggingface/jinja": {
56
+ "version": "0.3.3",
57
+ "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.3.3.tgz",
58
+ "integrity": "sha512-vQQr2JyWvVFba3Lj9es4q9vCl1sAc74fdgnEMoX8qHrXtswap9ge9uO3ONDzQB0cQ0PUyaKY2N6HaVbTBvSXvw==",
59
  "engines": {
60
  "node": ">=18"
61
  }
62
  },
63
+ "node_modules/@huggingface/tasks": {
64
+ "version": "0.18.6",
65
+ "resolved": "https://registry.npmjs.org/@huggingface/tasks/-/tasks-0.18.6.tgz",
66
+ "integrity": "sha512-ZxE+KS7po5thl2IJcq9H+teSfkdAaLkO5VhbgBqpVpDVkun+Kmmhkp18/ho4D8gAUVKiNdK5vSS7ZqeD/9b8Sw=="
67
+ },
68
  "node_modules/@jridgewell/resolve-uri": {
69
+ "version": "3.1.2",
70
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
71
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
72
  "engines": {
73
  "node": ">=6.0.0"
74
  }
75
  },
76
  "node_modules/@jridgewell/sourcemap-codec": {
77
+ "version": "1.5.0",
78
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
79
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
80
  },
81
  "node_modules/@jridgewell/trace-mapping": {
82
  "version": "0.3.9",
 
88
  }
89
  },
90
  "node_modules/@tsconfig/node10": {
91
+ "version": "1.0.11",
92
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
93
+ "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw=="
94
  },
95
  "node_modules/@tsconfig/node12": {
96
  "version": "1.0.11",
 
136
  }
137
  },
138
  "node_modules/@types/express-serve-static-core": {
139
+ "version": "4.19.6",
140
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
141
+ "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
142
  "dependencies": {
143
  "@types/node": "*",
144
  "@types/qs": "*",
 
157
  "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
158
  },
159
  "node_modules/@types/node": {
160
+ "version": "22.14.0",
161
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
162
+ "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==",
163
  "dependencies": {
164
+ "undici-types": "~6.21.0"
165
  }
166
  },
167
  "node_modules/@types/qs": {
168
+ "version": "6.9.18",
169
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
170
+ "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA=="
171
  },
172
  "node_modules/@types/range-parser": {
173
  "version": "1.2.7",
 
184
  }
185
  },
186
  "node_modules/@types/serve-static": {
187
+ "version": "1.15.7",
188
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
189
+ "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
190
  "dependencies": {
191
  "@types/http-errors": "*",
192
+ "@types/node": "*",
193
+ "@types/send": "*"
194
  }
195
  },
196
  "node_modules/accepts": {
 
206
  }
207
  },
208
  "node_modules/acorn": {
209
+ "version": "8.14.1",
210
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
211
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
212
  "bin": {
213
  "acorn": "bin/acorn"
214
  },
 
217
  }
218
  },
219
  "node_modules/acorn-walk": {
220
+ "version": "8.3.4",
221
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
222
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
223
+ "dependencies": {
224
+ "acorn": "^8.11.0"
225
+ },
226
  "engines": {
227
  "node": ">=0.4.0"
228
  }
 
238
  "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
239
  },
240
  "node_modules/body-parser": {
241
+ "version": "1.20.3",
242
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
243
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
244
  "dependencies": {
245
  "bytes": "3.1.2",
246
+ "content-type": "~1.0.5",
247
  "debug": "2.6.9",
248
  "depd": "2.0.0",
249
  "destroy": "1.2.0",
250
  "http-errors": "2.0.0",
251
  "iconv-lite": "0.4.24",
252
  "on-finished": "2.4.1",
253
+ "qs": "6.13.0",
254
+ "raw-body": "2.5.2",
255
  "type-is": "~1.6.18",
256
  "unpipe": "1.0.0"
257
  },
 
268
  "node": ">= 0.8"
269
  }
270
  },
271
+ "node_modules/call-bind-apply-helpers": {
272
+ "version": "1.0.2",
273
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
274
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
275
  "dependencies": {
276
+ "es-errors": "^1.3.0",
277
+ "function-bind": "^1.1.2"
278
+ },
279
+ "engines": {
280
+ "node": ">= 0.4"
281
+ }
282
+ },
283
+ "node_modules/call-bound": {
284
+ "version": "1.0.4",
285
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
286
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
287
+ "dependencies": {
288
+ "call-bind-apply-helpers": "^1.0.2",
289
+ "get-intrinsic": "^1.3.0"
290
+ },
291
+ "engines": {
292
+ "node": ">= 0.4"
293
  },
294
  "funding": {
295
  "url": "https://github.com/sponsors/ljharb"
 
315
  }
316
  },
317
  "node_modules/cookie": {
318
+ "version": "0.7.1",
319
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
320
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
321
  "engines": {
322
  "node": ">= 0.6"
323
  }
 
340
  "ms": "2.0.0"
341
  }
342
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  "node_modules/depd": {
344
  "version": "2.0.0",
345
  "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
 
365
  "node": ">=0.3.1"
366
  }
367
  },
368
+ "node_modules/dunder-proto": {
369
+ "version": "1.0.1",
370
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
371
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
372
+ "dependencies": {
373
+ "call-bind-apply-helpers": "^1.0.1",
374
+ "es-errors": "^1.3.0",
375
+ "gopd": "^1.2.0"
376
+ },
377
+ "engines": {
378
+ "node": ">= 0.4"
379
+ }
380
+ },
381
  "node_modules/ee-first": {
382
  "version": "1.1.1",
383
  "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
384
  "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
385
  },
386
  "node_modules/encodeurl": {
387
+ "version": "2.0.0",
388
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
389
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
390
  "engines": {
391
  "node": ">= 0.8"
392
  }
393
  },
394
+ "node_modules/es-define-property": {
395
+ "version": "1.0.1",
396
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
397
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
398
+ "engines": {
399
+ "node": ">= 0.4"
400
+ }
401
+ },
402
+ "node_modules/es-errors": {
403
+ "version": "1.3.0",
404
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
405
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
406
+ "engines": {
407
+ "node": ">= 0.4"
408
+ }
409
+ },
410
+ "node_modules/es-object-atoms": {
411
+ "version": "1.1.1",
412
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
413
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
414
+ "dependencies": {
415
+ "es-errors": "^1.3.0"
416
+ },
417
+ "engines": {
418
+ "node": ">= 0.4"
419
+ }
420
+ },
421
  "node_modules/escape-html": {
422
  "version": "1.0.3",
423
  "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
 
432
  }
433
  },
434
  "node_modules/express": {
435
+ "version": "4.21.2",
436
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
437
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
438
  "dependencies": {
439
  "accepts": "~1.3.8",
440
  "array-flatten": "1.1.1",
441
+ "body-parser": "1.20.3",
442
  "content-disposition": "0.5.4",
443
  "content-type": "~1.0.4",
444
+ "cookie": "0.7.1",
445
  "cookie-signature": "1.0.6",
446
  "debug": "2.6.9",
447
  "depd": "2.0.0",
448
+ "encodeurl": "~2.0.0",
449
  "escape-html": "~1.0.3",
450
  "etag": "~1.8.1",
451
+ "finalhandler": "1.3.1",
452
  "fresh": "0.5.2",
453
  "http-errors": "2.0.0",
454
+ "merge-descriptors": "1.0.3",
455
  "methods": "~1.1.2",
456
  "on-finished": "2.4.1",
457
  "parseurl": "~1.3.3",
458
+ "path-to-regexp": "0.1.12",
459
  "proxy-addr": "~2.0.7",
460
+ "qs": "6.13.0",
461
  "range-parser": "~1.2.1",
462
  "safe-buffer": "5.2.1",
463
+ "send": "0.19.0",
464
+ "serve-static": "1.16.2",
465
  "setprototypeof": "1.2.0",
466
  "statuses": "2.0.1",
467
  "type-is": "~1.6.18",
 
470
  },
471
  "engines": {
472
  "node": ">= 0.10.0"
473
+ },
474
+ "funding": {
475
+ "type": "opencollective",
476
+ "url": "https://opencollective.com/express"
477
  }
478
  },
479
  "node_modules/finalhandler": {
480
+ "version": "1.3.1",
481
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
482
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
483
  "dependencies": {
484
  "debug": "2.6.9",
485
+ "encodeurl": "~2.0.0",
486
  "escape-html": "~1.0.3",
487
  "on-finished": "2.4.1",
488
  "parseurl": "~1.3.3",
 
518
  }
519
  },
520
  "node_modules/get-intrinsic": {
521
+ "version": "1.3.0",
522
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
523
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
524
  "dependencies": {
525
+ "call-bind-apply-helpers": "^1.0.2",
526
+ "es-define-property": "^1.0.1",
527
+ "es-errors": "^1.3.0",
528
+ "es-object-atoms": "^1.1.1",
529
  "function-bind": "^1.1.2",
530
+ "get-proto": "^1.0.1",
531
+ "gopd": "^1.2.0",
532
+ "has-symbols": "^1.1.0",
533
+ "hasown": "^2.0.2",
534
+ "math-intrinsics": "^1.1.0"
535
  },
536
+ "engines": {
537
+ "node": ">= 0.4"
 
 
 
 
 
 
 
 
538
  },
539
  "funding": {
540
  "url": "https://github.com/sponsors/ljharb"
541
  }
542
  },
543
+ "node_modules/get-proto": {
544
  "version": "1.0.1",
545
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
546
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
547
  "dependencies": {
548
+ "dunder-proto": "^1.0.1",
549
+ "es-object-atoms": "^1.0.0"
550
  },
551
+ "engines": {
552
+ "node": ">= 0.4"
553
  }
554
  },
555
+ "node_modules/gopd": {
556
+ "version": "1.2.0",
557
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
558
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
559
  "engines": {
560
  "node": ">= 0.4"
561
  },
 
564
  }
565
  },
566
  "node_modules/has-symbols": {
567
+ "version": "1.1.0",
568
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
569
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
570
  "engines": {
571
  "node": ">= 0.4"
572
  },
 
574
  "url": "https://github.com/sponsors/ljharb"
575
  }
576
  },
 
 
 
 
 
577
  "node_modules/hasown": {
578
+ "version": "2.0.2",
579
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
580
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
581
  "dependencies": {
582
  "function-bind": "^1.1.2"
583
  },
 
629
  "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
630
  "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
631
  },
632
+ "node_modules/math-intrinsics": {
633
+ "version": "1.1.0",
634
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
635
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
636
+ "engines": {
637
+ "node": ">= 0.4"
638
+ }
639
+ },
640
  "node_modules/media-typer": {
641
  "version": "0.3.0",
642
  "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
 
646
  }
647
  },
648
  "node_modules/merge-descriptors": {
649
+ "version": "1.0.3",
650
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
651
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
652
+ "funding": {
653
+ "url": "https://github.com/sponsors/sindresorhus"
654
+ }
655
  },
656
  "node_modules/methods": {
657
  "version": "1.1.2",
 
705
  }
706
  },
707
  "node_modules/object-inspect": {
708
+ "version": "1.13.4",
709
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
710
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
711
+ "engines": {
712
+ "node": ">= 0.4"
713
+ },
714
  "funding": {
715
  "url": "https://github.com/sponsors/ljharb"
716
  }
 
735
  }
736
  },
737
  "node_modules/path-to-regexp": {
738
+ "version": "0.1.12",
739
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
740
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
741
  },
742
  "node_modules/proxy-addr": {
743
  "version": "2.0.7",
 
752
  }
753
  },
754
  "node_modules/qs": {
755
+ "version": "6.13.0",
756
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
757
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
758
  "dependencies": {
759
+ "side-channel": "^1.0.6"
760
  },
761
  "engines": {
762
  "node": ">=0.6"
 
774
  }
775
  },
776
  "node_modules/raw-body": {
777
+ "version": "2.5.2",
778
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
779
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
780
  "dependencies": {
781
  "bytes": "3.1.2",
782
  "http-errors": "2.0.0",
 
812
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
813
  },
814
  "node_modules/send": {
815
+ "version": "0.19.0",
816
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
817
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
818
  "dependencies": {
819
  "debug": "2.6.9",
820
  "depd": "2.0.0",
 
834
  "node": ">= 0.8.0"
835
  }
836
  },
837
+ "node_modules/send/node_modules/encodeurl": {
838
+ "version": "1.0.2",
839
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
840
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
841
+ "engines": {
842
+ "node": ">= 0.8"
843
+ }
844
+ },
845
  "node_modules/send/node_modules/ms": {
846
  "version": "2.1.3",
847
  "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
848
  "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
849
  },
850
  "node_modules/serve-static": {
851
+ "version": "1.16.2",
852
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
853
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
854
  "dependencies": {
855
+ "encodeurl": "~2.0.0",
856
  "escape-html": "~1.0.3",
857
  "parseurl": "~1.3.3",
858
+ "send": "0.19.0"
859
  },
860
  "engines": {
861
  "node": ">= 0.8.0"
862
  }
863
  },
864
+ "node_modules/setprototypeof": {
865
+ "version": "1.2.0",
866
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
867
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
868
+ },
869
+ "node_modules/side-channel": {
870
+ "version": "1.1.0",
871
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
872
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
873
  "dependencies": {
874
+ "es-errors": "^1.3.0",
875
+ "object-inspect": "^1.13.3",
876
+ "side-channel-list": "^1.0.0",
877
+ "side-channel-map": "^1.0.1",
878
+ "side-channel-weakmap": "^1.0.2"
879
  },
880
  "engines": {
881
  "node": ">= 0.4"
882
+ },
883
+ "funding": {
884
+ "url": "https://github.com/sponsors/ljharb"
885
  }
886
  },
887
+ "node_modules/side-channel-list": {
888
+ "version": "1.0.0",
889
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
890
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
891
+ "dependencies": {
892
+ "es-errors": "^1.3.0",
893
+ "object-inspect": "^1.13.3"
894
+ },
895
+ "engines": {
896
+ "node": ">= 0.4"
897
+ },
898
+ "funding": {
899
+ "url": "https://github.com/sponsors/ljharb"
900
+ }
901
  },
902
+ "node_modules/side-channel-map": {
903
+ "version": "1.0.1",
904
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
905
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
906
+ "dependencies": {
907
+ "call-bound": "^1.0.2",
908
+ "es-errors": "^1.3.0",
909
+ "get-intrinsic": "^1.2.5",
910
+ "object-inspect": "^1.13.3"
911
+ },
912
+ "engines": {
913
+ "node": ">= 0.4"
914
+ },
915
+ "funding": {
916
+ "url": "https://github.com/sponsors/ljharb"
917
+ }
918
+ },
919
+ "node_modules/side-channel-weakmap": {
920
+ "version": "1.0.2",
921
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
922
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
923
  "dependencies": {
924
+ "call-bound": "^1.0.2",
925
+ "es-errors": "^1.3.0",
926
+ "get-intrinsic": "^1.2.5",
927
+ "object-inspect": "^1.13.3",
928
+ "side-channel-map": "^1.0.1"
929
+ },
930
+ "engines": {
931
+ "node": ">= 0.4"
932
  },
933
  "funding": {
934
  "url": "https://github.com/sponsors/ljharb"
 
959
  }
960
  },
961
  "node_modules/ts-node": {
962
+ "version": "10.9.2",
963
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
964
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
965
  "dependencies": {
966
  "@cspotcode/source-map-support": "^0.8.0",
967
  "@tsconfig/node10": "^1.0.7",
 
1013
  }
1014
  },
1015
  "node_modules/typescript": {
1016
+ "version": "5.8.2",
1017
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
1018
+ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
1019
  "peer": true,
1020
  "bin": {
1021
  "tsc": "bin/tsc",
 
1026
  }
1027
  },
1028
  "node_modules/undici-types": {
1029
+ "version": "6.21.0",
1030
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
1031
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
1032
  },
1033
  "node_modules/unpipe": {
1034
  "version": "1.0.0",
package.json CHANGED
@@ -8,13 +8,13 @@
8
  "test": "node --loader ts-node/esm src/test.mts",
9
  "docker": "npm run docker:build && npm run docker:run",
10
  "docker:build": "docker build -t space-factory-llama2 .",
11
- "docker:run": "docker run -it -p 7860:7860 space-factory-llama2"
12
  },
13
  "author": "Julian Bilcke <julian.bilcke@huggingface.co>",
14
  "license": "Apache License",
15
  "dependencies": {
16
- "@huggingface/hub": "^0.8.3",
17
- "@huggingface/inference": "^2.6.1",
18
  "@types/express": "^4.17.17",
19
  "express": "^4.18.2",
20
  "slugify": "^1.6.6",
 
8
  "test": "node --loader ts-node/esm src/test.mts",
9
  "docker": "npm run docker:build && npm run docker:run",
10
  "docker:build": "docker build -t space-factory-llama2 .",
11
+ "docker:run": "docker run -it -p 3000:3000 space-factory-llama2"
12
  },
13
  "author": "Julian Bilcke <julian.bilcke@huggingface.co>",
14
  "license": "Apache License",
15
  "dependencies": {
16
+ "@huggingface/hub": "^1.1.2",
17
+ "@huggingface/inference": "^3.6.2",
18
  "@types/express": "^4.17.17",
19
  "express": "^4.18.2",
20
  "slugify": "^1.6.6",
public/index.html CHANGED
@@ -42,7 +42,7 @@
42
  name="promptDraft"
43
  x-model="promptDraft"
44
  rows="10"
45
- placeholder="Describe your web app"
46
  class="input w-full rounded text-stone-800 bg-stone-50 border-2 border-stone-400 font-mono text-md md:text-lg h-24 md:h-48"
47
  ></textarea>
48
  <p class="py-1 md:py-2 text-stone-700 text-italic">
@@ -68,7 +68,7 @@
68
  deepseek-ai/DeepSeek-V3-0324
69
  </a>
70
  </p>
71
- <p>Powered by πŸ€— <a href="https://huggingface.co/inference-api" class="underline" target="_blank">Inference API</a></p>
72
  <p class="text-stone-700" x-show="state === 'loading'">
73
  Waiting for the generation to begin (might take a few minutes)..
74
  </p>
@@ -100,7 +100,16 @@
100
  </div>
101
 
102
  <div
103
- x-show="state === 'stopped' && spaceUrl"
 
 
 
 
 
 
 
 
 
104
  class="flex w-full -mt-20 items-end justify-center pointer-events-none">
105
  <div class="flex flex-row py-3 px-8 text-center bg-green-200 text-green-800 rounded-md shadow-md">
106
  <div class="mr-1">πŸš€</div>
@@ -165,20 +174,27 @@
165
  saveToken(token) {
166
  localStorage.setItem(storageKey, token)
167
  },
 
168
  getIframeSource() {
169
  if (!this.open) {
170
  return '/placeholder.html';
171
  }
172
 
173
- if (this.spaceUrl) {
174
  return this.spaceUrl;
175
  }
176
 
 
 
 
 
177
  return `/app?prompt=${encodeURIComponent(this.prompt)}&token=${encodeURIComponent(this.token)}`;
178
  },
179
  checkForSpaceUrl(html) {
180
  if (!html) return null;
181
- const match = html.match(/<space-url>(.*?)<\/space-url>/);
 
 
182
  return match ? match[1] : null;
183
  },
184
  init() {
@@ -189,6 +205,10 @@
189
  }
190
  const iframeDocument = document?.getElementById("iframe")?.contentWindow?.document;
191
  const html = iframeDocument?.documentElement?.outerHTML;
 
 
 
 
192
  const size = Number(html?.length); // count how many characters we have generated
193
 
194
  if (isNaN(size) || !isFinite(size)) {
@@ -208,10 +228,37 @@
208
 
209
  // Check if the space URL is in the response
210
  const detectedSpaceUrl = this.checkForSpaceUrl(html);
 
 
211
  if (detectedSpaceUrl) {
212
  console.log("Space URL detected:", detectedSpaceUrl);
213
  this.spaceUrl = detectedSpaceUrl;
214
  this.state = "stopped";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  }
216
  }
217
 
 
42
  name="promptDraft"
43
  x-model="promptDraft"
44
  rows="10"
45
+ placeholder="Describe your web app (eg. tictactoe web game)"
46
  class="input w-full rounded text-stone-800 bg-stone-50 border-2 border-stone-400 font-mono text-md md:text-lg h-24 md:h-48"
47
  ></textarea>
48
  <p class="py-1 md:py-2 text-stone-700 text-italic">
 
68
  deepseek-ai/DeepSeek-V3-0324
69
  </a>
70
  </p>
71
+ <p>Est. 2023</p>
72
  <p class="text-stone-700" x-show="state === 'loading'">
73
  Waiting for the generation to begin (might take a few minutes)..
74
  </p>
 
100
  </div>
101
 
102
  <div
103
+ x-show="state === 'stopped' && spaceUrl && !spaceUrlReady"
104
+ class="flex w-full -mt-20 items-end justify-center pointer-events-none">
105
+ <div class="flex flex-row py-3 px-8 text-center bg-yellow-200 text-yellow-800 rounded-md shadow-md">
106
+ <div class="mr-1">⏳</div>
107
+ <div>Space created! Waiting for it to be ready...</div>
108
+ </div>
109
+ </div>
110
+
111
+ <div
112
+ x-show="state === 'stopped' && spaceUrl && spaceUrlReady"
113
  class="flex w-full -mt-20 items-end justify-center pointer-events-none">
114
  <div class="flex flex-row py-3 px-8 text-center bg-green-200 text-green-800 rounded-md shadow-md">
115
  <div class="mr-1">πŸš€</div>
 
174
  saveToken(token) {
175
  localStorage.setItem(storageKey, token)
176
  },
177
+ spaceUrlReady: false,
178
  getIframeSource() {
179
  if (!this.open) {
180
  return '/placeholder.html';
181
  }
182
 
183
+ if (this.spaceUrl && this.spaceUrlReady) {
184
  return this.spaceUrl;
185
  }
186
 
187
+ if (this.spaceUrl && !this.spaceUrlReady) {
188
+ return '/placeholder.html';
189
+ }
190
+
191
  return `/app?prompt=${encodeURIComponent(this.prompt)}&token=${encodeURIComponent(this.token)}`;
192
  },
193
  checkForSpaceUrl(html) {
194
  if (!html) return null;
195
+ // Match both regular and HTML-escaped space-url tags
196
+ const match = html.match(/(?:<space-url>|&lt;space-url&gt;)(?:\s*)?(https?:\/\/[^\s<&]+)(?:\s*)?(?:<\/space-url>|&lt;\/space-url&gt;)/i);
197
+ // console.log("Space URL regex check:", match);
198
  return match ? match[1] : null;
199
  },
200
  init() {
 
205
  }
206
  const iframeDocument = document?.getElementById("iframe")?.contentWindow?.document;
207
  const html = iframeDocument?.documentElement?.outerHTML;
208
+ // Log a sample of the HTML content to debug space URL detection
209
+ if (html && html.includes('space-url')) {
210
+ console.log("Found space-url in HTML content:", html.substring(html.indexOf('space-url') - 50, html.indexOf('space-url') + 150));
211
+ }
212
  const size = Number(html?.length); // count how many characters we have generated
213
 
214
  if (isNaN(size) || !isFinite(size)) {
 
228
 
229
  // Check if the space URL is in the response
230
  const detectedSpaceUrl = this.checkForSpaceUrl(html);
231
+ // console.log("detectedSpaceUrl:", detectedSpaceUrl);
232
+
233
  if (detectedSpaceUrl) {
234
  console.log("Space URL detected:", detectedSpaceUrl);
235
  this.spaceUrl = detectedSpaceUrl;
236
  this.state = "stopped";
237
+
238
+ // Add 5-second delay before loading the space URL
239
+ console.log("Waiting 5 seconds before loading the space...");
240
+ setTimeout(() => {
241
+ console.log("Space URL is now ready to load");
242
+ this.spaceUrlReady = true;
243
+ }, 5000);
244
+ }
245
+ }
246
+
247
+ // Try to detect the space URL even if the size hasn't changed
248
+ // as it might be added at the end without changing overall size much
249
+ if (!this.spaceUrl && html) {
250
+ const retryDetectedSpaceUrl = this.checkForSpaceUrl(html);
251
+ if (retryDetectedSpaceUrl) {
252
+ console.log("Space URL detected on retry:", retryDetectedSpaceUrl);
253
+ this.spaceUrl = retryDetectedSpaceUrl;
254
+ this.state = "stopped";
255
+
256
+ // Add 5-second delay before loading the space URL
257
+ console.log("Waiting 5 seconds before loading the space...");
258
+ setTimeout(() => {
259
+ console.log("Space URL is now ready to load");
260
+ this.spaceUrlReady = true;
261
+ }, 5000);
262
  }
263
  }
264
 
src/createSpace.mts CHANGED
@@ -1,6 +1,6 @@
1
  import { v4 as uuidv4 } from "uuid"
2
  import { createRepo, uploadFiles, whoAmI } from "@huggingface/hub"
3
- import type { RepoDesignation, Credentials } from "@huggingface/hub"
4
  import slugify from "slugify"
5
 
6
  import { RepoFile } from "./types.mts"
@@ -30,17 +30,28 @@ export const createSpace = async (files: RepoFile[], token: string) => {
30
  const repo: RepoDesignation = { type: "space", name: repoName }
31
  console.log(`Creating space at ${repoName}${title ? ` (${title})` : ''}`)
32
 
33
- await createRepo({
34
- repo,
35
- credentials,
36
- license: "mit",
37
- sdk:
38
- files.some(file => file.path.includes("Dockerfile"))
39
- ? "docker"
40
- : files.some(file => file.path.includes("app.py"))
41
- ? "streamlit"
42
- : "static" // "streamlit" | "gradio" | "docker" | "static";
43
- });
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  console.log("uploading files..")
46
  await uploadFiles({
 
1
  import { v4 as uuidv4 } from "uuid"
2
  import { createRepo, uploadFiles, whoAmI } from "@huggingface/hub"
3
+ import type { RepoDesignation, Credentials, HubApiError } from "@huggingface/hub"
4
  import slugify from "slugify"
5
 
6
  import { RepoFile } from "./types.mts"
 
30
  const repo: RepoDesignation = { type: "space", name: repoName }
31
  console.log(`Creating space at ${repoName}${title ? ` (${title})` : ''}`)
32
 
33
+ try {
34
+ await createRepo({
35
+ repo,
36
+ credentials,
37
+ license: "mit",
38
+ sdk:
39
+ files.some(file => file.path.includes("Dockerfile"))
40
+ ? "docker"
41
+ : files.some(file => file.path.includes("app.py"))
42
+ ? "streamlit"
43
+ : "static" // "streamlit" | "gradio" | "docker" | "static";
44
+ });
45
+ } catch (error) {
46
+ // If the space already exists (409 Conflict), skip creation and just upload files
47
+ const apiError = error as HubApiError;
48
+ if (apiError.statusCode === 409) {
49
+ console.log(`Space ${repoName} already exists, skipping creation and updating files...`);
50
+ } else {
51
+ // For other errors, rethrow
52
+ throw error;
53
+ }
54
+ }
55
 
56
  console.log("uploading files..")
57
  await uploadFiles({
src/index.mts CHANGED
@@ -3,7 +3,7 @@ import { createSpace } from './createSpace.mts'
3
  import { generateFiles } from './generateFiles.mts'
4
 
5
  const app = express()
6
- const port = 7860
7
 
8
  const minPromptSize = 16 // if you change this, you will need to also change in public/index.html
9
  const timeoutInSec = 15 * 60
@@ -118,7 +118,8 @@ app.get('/app', async (req, res) => {
118
  // Send the space URL back to the frontend
119
  if (spaceInfo && spaceInfo.username && spaceInfo.slug) {
120
  const spaceUrl = `https://${spaceInfo.username}-${spaceInfo.slug}.hf.space`
121
- res.write(`\n\n<space-url>${spaceUrl}</space-url>`)
 
122
  console.log(`Created space: ${spaceUrl}`)
123
  }
124
  }
 
3
  import { generateFiles } from './generateFiles.mts'
4
 
5
  const app = express()
6
+ const port = 3000
7
 
8
  const minPromptSize = 16 // if you change this, you will need to also change in public/index.html
9
  const timeoutInSec = 15 * 60
 
118
  // Send the space URL back to the frontend
119
  if (spaceInfo && spaceInfo.username && spaceInfo.slug) {
120
  const spaceUrl = `https://${spaceInfo.username}-${spaceInfo.slug}.hf.space`
121
+ // Add clear markers and spacing to make the space URL easier to detect
122
+ res.write(`\n\n<!-- SPACE URL MARKER -->\n<space-url>${spaceUrl}</space-url>\n<!-- END SPACE URL MARKER -->\n\n`)
123
  console.log(`Created space: ${spaceUrl}`)
124
  }
125
  }