Compare commits
3 Commits
45813344a0
...
f8d40c4e41
| Author | SHA1 | Date |
|---|---|---|
|
|
f8d40c4e41 | |
|
|
8a4d04db58 | |
|
|
d051d46d1f |
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 70a9d8178bcc16ac76341fc2d975ce0229763315
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 GoHugo.io
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
[](https://app.netlify.com/sites/hugo-mod-bootstrap-scss/deploys)
|
||||
[Test Site](https://hugo-mod-bootstrap-scss.netlify.app/)
|
||||
|
||||
This is a [Hugo module](https://gohugo.io/hugo-modules/) that packages the [Bootstrap v5](https://getbootstrap.com/) SCSS and JavaScript source ready to be used in Hugo.
|
||||
|
||||
For Bootstrap v4, see [the v4 branch](https://github.com/gohugoio/hugo-mod-bootstrap-scss/tree/v4).
|
||||
|
||||
You need the Hugo extended version and [Go](https://golang.org/dl/) to use this component.
|
||||
|
||||
## Use
|
||||
|
||||
Add the component to your Hugo site's config:
|
||||
|
||||
```toml
|
||||
[module]
|
||||
[[module.imports]]
|
||||
path = "github.com/gohugoio/hugo-mod-bootstrap-scss/v5"
|
||||
```
|
||||
|
||||
### SCSS
|
||||
|
||||
The Bootstrap SCSS will be mounted in `assets/scss/bootstrap`, so you can then import either all:
|
||||
|
||||
```scss
|
||||
@import "bootstrap/bootstrap";
|
||||
```
|
||||
|
||||
Or only what you need:
|
||||
|
||||
```scss
|
||||
// Configuration
|
||||
@import "bootstrap/functions";
|
||||
@import "bootstrap/variables";
|
||||
@import "bootstrap/mixins";
|
||||
@import "bootstrap/utilities";
|
||||
|
||||
// Layout & components
|
||||
@import "bootstrap/root";
|
||||
@import "bootstrap/reboot";
|
||||
@import "bootstrap/type";
|
||||
@import "bootstrap/images";
|
||||
@import "bootstrap/containers";
|
||||
@import "bootstrap/grid";
|
||||
@import "bootstrap/tables";
|
||||
@import "bootstrap/forms";
|
||||
@import "bootstrap/buttons";
|
||||
@import "bootstrap/transitions";
|
||||
@import "bootstrap/dropdown";
|
||||
@import "bootstrap/button-group";
|
||||
@import "bootstrap/nav";
|
||||
@import "bootstrap/navbar";
|
||||
@import "bootstrap/card";
|
||||
@import "bootstrap/accordion";
|
||||
@import "bootstrap/breadcrumb";
|
||||
@import "bootstrap/pagination";
|
||||
@import "bootstrap/badge";
|
||||
@import "bootstrap/alert";
|
||||
@import "bootstrap/progress";
|
||||
@import "bootstrap/list-group";
|
||||
@import "bootstrap/close";
|
||||
@import "bootstrap/toasts";
|
||||
@import "bootstrap/modal";
|
||||
@import "bootstrap/tooltip";
|
||||
@import "bootstrap/popover";
|
||||
@import "bootstrap/carousel";
|
||||
@import "bootstrap/spinners";
|
||||
@import "bootstrap/offcanvas";
|
||||
|
||||
// Helpers
|
||||
@import "bootstrap/helpers";
|
||||
|
||||
// Utilities
|
||||
@import "bootstrap/utilities/api";
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
|
||||
See the [Example Site](./exampleSite).
|
||||
|
||||
## Versions
|
||||
|
||||
This repository will be versioned following https://github.com/bep/semverpair
|
||||
|
||||
## How to Upgrade Bootstrap
|
||||
|
||||
1. Checkout the relevant branch (`main`=latest=Bootstrap 5, `v4`=Bootstrap 4)
|
||||
1. Create a PR branch
|
||||
1. Run `hugo mod get -u github.com/twbs/bootstrap`
|
||||
1. Verify that `go.mod` is updated with correct version (run `hugo mod graph`).
|
||||
1. Do `cd exampleSite` and run `hugo server` and make sure it works (and that `github.com/twbs/bootstrap` version is as expected in the table).
|
||||
1. Create a Pull Request and verify that it builds and that the Netlify preview works.
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
// stylelint-disable scss/dimension-no-non-numeric-values
|
||||
|
||||
// SCSS RFS mixin
|
||||
//
|
||||
// Automated responsive values for font sizes, paddings, margins and much more
|
||||
//
|
||||
// Licensed under MIT (https://github.com/twbs/rfs/blob/main/LICENSE)
|
||||
|
||||
// Configuration
|
||||
|
||||
// Base value
|
||||
$rfs-base-value: 1.25rem !default;
|
||||
$rfs-unit: rem !default;
|
||||
|
||||
@if $rfs-unit != rem and $rfs-unit != px {
|
||||
@error "`#{$rfs-unit}` is not a valid unit for $rfs-unit. Use `px` or `rem`.";
|
||||
}
|
||||
|
||||
// Breakpoint at where values start decreasing if screen width is smaller
|
||||
$rfs-breakpoint: 1200px !default;
|
||||
$rfs-breakpoint-unit: px !default;
|
||||
|
||||
@if $rfs-breakpoint-unit != px and $rfs-breakpoint-unit != em and $rfs-breakpoint-unit != rem {
|
||||
@error "`#{$rfs-breakpoint-unit}` is not a valid unit for $rfs-breakpoint-unit. Use `px`, `em` or `rem`.";
|
||||
}
|
||||
|
||||
// Resize values based on screen height and width
|
||||
$rfs-two-dimensional: false !default;
|
||||
|
||||
// Factor of decrease
|
||||
$rfs-factor: 10 !default;
|
||||
|
||||
@if type-of($rfs-factor) != number or $rfs-factor <= 1 {
|
||||
@error "`#{$rfs-factor}` is not a valid $rfs-factor, it must be greater than 1.";
|
||||
}
|
||||
|
||||
// Mode. Possibilities: "min-media-query", "max-media-query"
|
||||
$rfs-mode: min-media-query !default;
|
||||
|
||||
// Generate enable or disable classes. Possibilities: false, "enable" or "disable"
|
||||
$rfs-class: false !default;
|
||||
|
||||
// 1 rem = $rfs-rem-value px
|
||||
$rfs-rem-value: 16 !default;
|
||||
|
||||
// Safari iframe resize bug: https://github.com/twbs/rfs/issues/14
|
||||
$rfs-safari-iframe-resize-bug-fix: false !default;
|
||||
|
||||
// Disable RFS by setting $enable-rfs to false
|
||||
$enable-rfs: true !default;
|
||||
|
||||
// Cache $rfs-base-value unit
|
||||
$rfs-base-value-unit: unit($rfs-base-value);
|
||||
|
||||
@function divide($dividend, $divisor, $precision: 10) {
|
||||
$sign: if($dividend > 0 and $divisor > 0 or $dividend < 0 and $divisor < 0, 1, -1);
|
||||
$dividend: abs($dividend);
|
||||
$divisor: abs($divisor);
|
||||
@if $dividend == 0 {
|
||||
@return 0;
|
||||
}
|
||||
@if $divisor == 0 {
|
||||
@error "Cannot divide by 0";
|
||||
}
|
||||
$remainder: $dividend;
|
||||
$result: 0;
|
||||
$factor: 10;
|
||||
@while ($remainder > 0 and $precision >= 0) {
|
||||
$quotient: 0;
|
||||
@while ($remainder >= $divisor) {
|
||||
$remainder: $remainder - $divisor;
|
||||
$quotient: $quotient + 1;
|
||||
}
|
||||
$result: $result * 10 + $quotient;
|
||||
$factor: $factor * .1;
|
||||
$remainder: $remainder * 10;
|
||||
$precision: $precision - 1;
|
||||
@if ($precision < 0 and $remainder >= $divisor * 5) {
|
||||
$result: $result + 1;
|
||||
}
|
||||
}
|
||||
$result: $result * $factor * $sign;
|
||||
$dividend-unit: unit($dividend);
|
||||
$divisor-unit: unit($divisor);
|
||||
$unit-map: (
|
||||
"px": 1px,
|
||||
"rem": 1rem,
|
||||
"em": 1em,
|
||||
"%": 1%
|
||||
);
|
||||
@if ($dividend-unit != $divisor-unit and map-has-key($unit-map, $dividend-unit)) {
|
||||
$result: $result * map-get($unit-map, $dividend-unit);
|
||||
}
|
||||
@return $result;
|
||||
}
|
||||
|
||||
// Remove px-unit from $rfs-base-value for calculations
|
||||
@if $rfs-base-value-unit == px {
|
||||
$rfs-base-value: divide($rfs-base-value, $rfs-base-value * 0 + 1);
|
||||
}
|
||||
@else if $rfs-base-value-unit == rem {
|
||||
$rfs-base-value: divide($rfs-base-value, divide($rfs-base-value * 0 + 1, $rfs-rem-value));
|
||||
}
|
||||
|
||||
// Cache $rfs-breakpoint unit to prevent multiple calls
|
||||
$rfs-breakpoint-unit-cache: unit($rfs-breakpoint);
|
||||
|
||||
// Remove unit from $rfs-breakpoint for calculations
|
||||
@if $rfs-breakpoint-unit-cache == px {
|
||||
$rfs-breakpoint: divide($rfs-breakpoint, $rfs-breakpoint * 0 + 1);
|
||||
}
|
||||
@else if $rfs-breakpoint-unit-cache == rem or $rfs-breakpoint-unit-cache == "em" {
|
||||
$rfs-breakpoint: divide($rfs-breakpoint, divide($rfs-breakpoint * 0 + 1, $rfs-rem-value));
|
||||
}
|
||||
|
||||
// Calculate the media query value
|
||||
$rfs-mq-value: if($rfs-breakpoint-unit == px, #{$rfs-breakpoint}px, #{divide($rfs-breakpoint, $rfs-rem-value)}#{$rfs-breakpoint-unit});
|
||||
$rfs-mq-property-width: if($rfs-mode == max-media-query, max-width, min-width);
|
||||
$rfs-mq-property-height: if($rfs-mode == max-media-query, max-height, min-height);
|
||||
|
||||
// Internal mixin used to determine which media query needs to be used
|
||||
@mixin _rfs-media-query {
|
||||
@if $rfs-two-dimensional {
|
||||
@if $rfs-mode == max-media-query {
|
||||
@media (#{$rfs-mq-property-width}: #{$rfs-mq-value}), (#{$rfs-mq-property-height}: #{$rfs-mq-value}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@else {
|
||||
@media (#{$rfs-mq-property-width}: #{$rfs-mq-value}) and (#{$rfs-mq-property-height}: #{$rfs-mq-value}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
@else {
|
||||
@media (#{$rfs-mq-property-width}: #{$rfs-mq-value}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal mixin that adds disable classes to the selector if needed.
|
||||
@mixin _rfs-rule {
|
||||
@if $rfs-class == disable and $rfs-mode == max-media-query {
|
||||
// Adding an extra class increases specificity, which prevents the media query to override the property
|
||||
&,
|
||||
.disable-rfs &,
|
||||
&.disable-rfs {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@else if $rfs-class == enable and $rfs-mode == min-media-query {
|
||||
.enable-rfs &,
|
||||
&.enable-rfs {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal mixin that adds enable classes to the selector if needed.
|
||||
@mixin _rfs-media-query-rule {
|
||||
|
||||
@if $rfs-class == enable {
|
||||
@if $rfs-mode == min-media-query {
|
||||
@content;
|
||||
}
|
||||
|
||||
@include _rfs-media-query () {
|
||||
.enable-rfs &,
|
||||
&.enable-rfs {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
@else {
|
||||
@if $rfs-class == disable and $rfs-mode == min-media-query {
|
||||
.disable-rfs &,
|
||||
&.disable-rfs {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@include _rfs-media-query () {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get the formatted non-responsive value
|
||||
@function rfs-value($values) {
|
||||
// Convert to list
|
||||
$values: if(type-of($values) != list, ($values,), $values);
|
||||
|
||||
$val: "";
|
||||
|
||||
// Loop over each value and calculate value
|
||||
@each $value in $values {
|
||||
@if $value == 0 {
|
||||
$val: $val + " 0";
|
||||
}
|
||||
@else {
|
||||
// Cache $value unit
|
||||
$unit: if(type-of($value) == "number", unit($value), false);
|
||||
|
||||
@if $unit == px {
|
||||
// Convert to rem if needed
|
||||
$val: $val + " " + if($rfs-unit == rem, #{divide($value, $value * 0 + $rfs-rem-value)}rem, $value);
|
||||
}
|
||||
@else if $unit == rem {
|
||||
// Convert to px if needed
|
||||
$val: $val + " " + if($rfs-unit == px, #{divide($value, $value * 0 + 1) * $rfs-rem-value}px, $value);
|
||||
} @else {
|
||||
// If $value isn't a number (like inherit) or $value has a unit (not px or rem, like 1.5em) or $ is 0, just print the value
|
||||
$val: $val + " " + $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove first space
|
||||
@return unquote(str-slice($val, 2));
|
||||
}
|
||||
|
||||
// Helper function to get the responsive value calculated by RFS
|
||||
@function rfs-fluid-value($values) {
|
||||
// Convert to list
|
||||
$values: if(type-of($values) != list, ($values,), $values);
|
||||
|
||||
$val: "";
|
||||
|
||||
// Loop over each value and calculate value
|
||||
@each $value in $values {
|
||||
@if $value == 0 {
|
||||
$val: $val + " 0";
|
||||
} @else {
|
||||
// Cache $value unit
|
||||
$unit: if(type-of($value) == "number", unit($value), false);
|
||||
|
||||
// If $value isn't a number (like inherit) or $value has a unit (not px or rem, like 1.5em) or $ is 0, just print the value
|
||||
@if not $unit or $unit != px and $unit != rem {
|
||||
$val: $val + " " + $value;
|
||||
} @else {
|
||||
// Remove unit from $value for calculations
|
||||
$value: divide($value, $value * 0 + if($unit == px, 1, divide(1, $rfs-rem-value)));
|
||||
|
||||
// Only add the media query if the value is greater than the minimum value
|
||||
@if abs($value) <= $rfs-base-value or not $enable-rfs {
|
||||
$val: $val + " " + if($rfs-unit == rem, #{divide($value, $rfs-rem-value)}rem, #{$value}px);
|
||||
}
|
||||
@else {
|
||||
// Calculate the minimum value
|
||||
$value-min: $rfs-base-value + divide(abs($value) - $rfs-base-value, $rfs-factor);
|
||||
|
||||
// Calculate difference between $value and the minimum value
|
||||
$value-diff: abs($value) - $value-min;
|
||||
|
||||
// Base value formatting
|
||||
$min-width: if($rfs-unit == rem, #{divide($value-min, $rfs-rem-value)}rem, #{$value-min}px);
|
||||
|
||||
// Use negative value if needed
|
||||
$min-width: if($value < 0, -$min-width, $min-width);
|
||||
|
||||
// Use `vmin` if two-dimensional is enabled
|
||||
$variable-unit: if($rfs-two-dimensional, vmin, vw);
|
||||
|
||||
// Calculate the variable width between 0 and $rfs-breakpoint
|
||||
$variable-width: #{divide($value-diff * 100, $rfs-breakpoint)}#{$variable-unit};
|
||||
|
||||
// Return the calculated value
|
||||
$val: $val + " calc(" + $min-width + if($value < 0, " - ", " + ") + $variable-width + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove first space
|
||||
@return unquote(str-slice($val, 2));
|
||||
}
|
||||
|
||||
// RFS mixin
|
||||
@mixin rfs($values, $property: font-size) {
|
||||
@if $values != null {
|
||||
$val: rfs-value($values);
|
||||
$fluid-val: rfs-fluid-value($values);
|
||||
|
||||
// Do not print the media query if responsive & non-responsive values are the same
|
||||
@if $val == $fluid-val {
|
||||
#{$property}: $val;
|
||||
}
|
||||
@else {
|
||||
@include _rfs-rule () {
|
||||
#{$property}: if($rfs-mode == max-media-query, $val, $fluid-val);
|
||||
|
||||
// Include safari iframe resize fix if needed
|
||||
min-width: if($rfs-safari-iframe-resize-bug-fix, (0 * 1vw), null);
|
||||
}
|
||||
|
||||
@include _rfs-media-query-rule () {
|
||||
#{$property}: if($rfs-mode == max-media-query, $fluid-val, $val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shorthand helper mixins
|
||||
@mixin font-size($value) {
|
||||
@include rfs($value);
|
||||
}
|
||||
|
||||
@mixin padding($value) {
|
||||
@include rfs($value, padding);
|
||||
}
|
||||
|
||||
@mixin padding-top($value) {
|
||||
@include rfs($value, padding-top);
|
||||
}
|
||||
|
||||
@mixin padding-right($value) {
|
||||
@include rfs($value, padding-right);
|
||||
}
|
||||
|
||||
@mixin padding-bottom($value) {
|
||||
@include rfs($value, padding-bottom);
|
||||
}
|
||||
|
||||
@mixin padding-left($value) {
|
||||
@include rfs($value, padding-left);
|
||||
}
|
||||
|
||||
@mixin margin($value) {
|
||||
@include rfs($value, margin);
|
||||
}
|
||||
|
||||
@mixin margin-top($value) {
|
||||
@include rfs($value, margin-top);
|
||||
}
|
||||
|
||||
@mixin margin-right($value) {
|
||||
@include rfs($value, margin-right);
|
||||
}
|
||||
|
||||
@mixin margin-bottom($value) {
|
||||
@include rfs($value, margin-bottom);
|
||||
}
|
||||
|
||||
@mixin margin-left($value) {
|
||||
@include rfs($value, margin-left);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
[module]
|
||||
[module.hugoVersion]
|
||||
extended = true
|
||||
[[module.mounts]]
|
||||
# Workaround for https://github.com/gohugoio/hugo/issues/6945
|
||||
source = "assets/scss/bootstrap/_vendor"
|
||||
target = "assets/scss/bootstrap/vendor"
|
||||
[[module.mounts]]
|
||||
source = "assets"
|
||||
target = "assets"
|
||||
[[module.imports]]
|
||||
ignoreConfig = true
|
||||
path = "github.com/twbs/bootstrap"
|
||||
[[module.imports.mounts]]
|
||||
source = "scss"
|
||||
target = "assets/scss/bootstrap"
|
||||
[[module.imports.mounts]]
|
||||
source = "js"
|
||||
target = "assets/js/bootstrap"
|
||||
[[module.imports]]
|
||||
path = "github.com/gohugoio/hugo-mod-jslibs-dist/popperjs"
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Import the Bootstrap components we want to use.
|
||||
// See https://github.com/twbs/bootstrap/blob/main/js/index.umd.js
|
||||
import Toast from 'js/bootstrap/src/toast';
|
||||
import Popover from 'js/bootstrap/src/popover';
|
||||
import Button from 'js/bootstrap/src/button.js';
|
||||
import Carousel from 'js/bootstrap/src/carousel.js';
|
||||
|
||||
(function () {
|
||||
let toastElList = [].slice.call(document.querySelectorAll('.toast'));
|
||||
let toastList = toastElList.map(function (toastEl) {
|
||||
return new Toast(toastEl);
|
||||
});
|
||||
|
||||
toastList.forEach(function (toast) {
|
||||
toast.show();
|
||||
});
|
||||
|
||||
let popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
|
||||
popoverTriggerList.map(function (popoverTriggerEl) {
|
||||
return new Popover(popoverTriggerEl);
|
||||
});
|
||||
})();
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
@import "bootstrap/bootstrap";
|
||||
|
||||
.bd-placeholder-img-lg {
|
||||
@include font-size(5.5rem);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ["page", "section", "taxonomy", "term"]
|
||||
|
||||
[outputs]
|
||||
home = ["HTML"]
|
||||
|
||||
[module]
|
||||
[module.hugoVersion]
|
||||
# We use hugo.Deps to list dependencies, which was added in Hugo 0.92.0
|
||||
min = "0.92.0"
|
||||
|
||||
[[module.imports]]
|
||||
path="github.com/gohugoio/hugo-mod-bootstrap-scss/v5"
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/gohugoio/hugo-mod-bootstrap-scss/exampleSite/v5
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.90100.90300 // indirect
|
||||
)
|
||||
|
||||
replace github.com/gohugoio/hugo-mod-bootstrap-scss/v5 => ../
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000 h1:GZxx4Hc+yb0/t3/rau1j8XlAxLE4CyXns2fqQbyqWfs=
|
||||
github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000/go.mod h1:mFberT6ZtcchrsDtfvJM7aAH2bDKLdOnruUHl0hlapI=
|
||||
github.com/twbs/bootstrap v5.1.3+incompatible h1:19+1/69025oghttdacCOGvs1wv9D5lZnpfoCvKUsPCs=
|
||||
github.com/twbs/bootstrap v5.1.3+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0=
|
||||
github.com/twbs/bootstrap v5.2.1+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0=
|
||||
github.com/twbs/bootstrap v5.3.0-alpha1+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0=
|
||||
github.com/twbs/bootstrap v5.3.0-alpha3+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0=
|
||||
github.com/twbs/bootstrap v5.3.2+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0=
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>
|
||||
{{ .Title }}
|
||||
</title>
|
||||
{{/* styles */}}
|
||||
<style>
|
||||
html {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
{{/* Load Bootstrap SCSS. */}}
|
||||
{{ $options := dict "enableSourceMap" true }}
|
||||
{{ if hugo.IsProduction }}
|
||||
{{ $options := dict "enableSourceMap" false "outputStyle" "compressed" }}
|
||||
{{ end }}
|
||||
{{ $styles := resources.Get "scss/styles.scss" }}
|
||||
{{ $styles = $styles | css.Sass $options }}
|
||||
{{ if hugo.IsProduction }}
|
||||
{{ $styles = $styles | fingerprint }}
|
||||
{{ end }}
|
||||
<link href="{{ $styles.RelPermalink }}" rel="stylesheet" />
|
||||
|
||||
{{/* Load Bootstrap JS. */}}
|
||||
{{ $js := resources.Get "js/index.js" }}
|
||||
{{ $params := dict }}
|
||||
{{ $sourceMap := cond hugo.IsProduction "" "inline" }}
|
||||
{{ $opts := dict "sourceMap" $sourceMap "minify" hugo.IsProduction "target" "es2018" "params" $params }}
|
||||
{{ $js = $js | js.Build $opts }}
|
||||
{{ if hugo.IsProduction }}
|
||||
{{ $js = $js | fingerprint }}
|
||||
{{ end }}
|
||||
<script
|
||||
src="{{ $js.RelPermalink }}"
|
||||
{{ if hugo.IsProduction }}integrity="{{ $js.Data.Integrity }}"{{ end }}
|
||||
defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5 mb-5">
|
||||
<h1>Bootstrap v5 Hugo Module</h1>
|
||||
<h2 class="mt-4">Dependencies</h2>
|
||||
<p class="mt-4">
|
||||
<strong>Note:</strong> We have a replacement of
|
||||
github.com/gohugoio/hugo-mod-bootstrap-scss/v4 to point to the directory
|
||||
one level up (we do this to get correct PR previews when we update
|
||||
Bootstrap). The version number reflects the version in
|
||||
<code>go.mod</code>.
|
||||
</p>
|
||||
<table class="table table-striped table-responsive mt-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Owner</th>
|
||||
<th scope="col">Path</th>
|
||||
<th scope="col">Version</th>
|
||||
<th scope="col">Time</th>
|
||||
<th scope="col">Vendor</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $index, $element := hugo.Deps }}
|
||||
<tr>
|
||||
<th scope="row">{{ add $index 1 }}</th>
|
||||
<td>{{ with $element.Owner }}{{ .Path }}{{ end }}</td>
|
||||
<td>
|
||||
{{ $element.Path }}
|
||||
{{ with $element.Replace }}
|
||||
=>
|
||||
{{ .Path }}
|
||||
{{ end }}
|
||||
</td>
|
||||
<td>{{ $element.Version }}</td>
|
||||
<td>{{ with $element.Time }}{{ . }}{{ end }}</td>
|
||||
<td>{{ $element.Vendor }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 class="my-4">Toast (JS plugin)</h2>
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">Bootstrap</strong>
|
||||
<small>11 mins ago</small>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="toast"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body">Hello, world! This is a toast message.</div>
|
||||
</div>
|
||||
<h2 class="my-4">Popover (JS plugin)</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-lg btn-danger"
|
||||
data-bs-toggle="popover"
|
||||
title="Popover title"
|
||||
data-bs-content="And here's some amazing content. It's very engaging. Right?">
|
||||
Click to toggle popover
|
||||
</button>
|
||||
<h2 class="my-4">Buttons</h2>
|
||||
<div class="d-grid gap-2 d-md-block">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
data-bs-toggle="button"
|
||||
autocomplete="off">
|
||||
Toggle button
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary active"
|
||||
data-bs-toggle="button"
|
||||
autocomplete="off"
|
||||
aria-pressed="true">
|
||||
Active toggle button
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
disabled
|
||||
data-bs-toggle="button"
|
||||
autocomplete="off">
|
||||
Disabled toggle button
|
||||
</button>
|
||||
</div>
|
||||
<h2 class="my-4">Carousel</h2>
|
||||
<div
|
||||
id="carouselExampleControls"
|
||||
class="carousel slide"
|
||||
data-bs-ride="carousel">
|
||||
<div class="carousel-inner">
|
||||
<div class="carousel-item active">
|
||||
{{ partial "placeholder.html" (dict "width" "800" "height" "400" "class" "bd-placeholder-img-lg d-block w-100" "color" "#555" "background" "#777" "text" "First slide") }}
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
{{ partial "placeholder.html" (dict "width" "800" "height" "400" "class" "bd-placeholder-img-lg d-block w-100" "color" "#555" "background" "#777" "text" "Second slide") }}
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
{{ partial "placeholder.html" (dict "width" "800" "height" "400" "class" "bd-placeholder-img-lg d-block w-100" "color" "#555" "background" "#777" "text" "Third slide") }}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="carousel-control-prev"
|
||||
type="button"
|
||||
data-bs-target="#carouselExampleControls"
|
||||
data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</button>
|
||||
<button
|
||||
class="carousel-control-next"
|
||||
type="button"
|
||||
data-bs-target="#carouselExampleControls"
|
||||
data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{{- $title := .title | default "Placeholder" -}}
|
||||
{{- $class := .class -}}
|
||||
{{- $default_color := "#6c757d" -}}
|
||||
{{- $default_background := "#6c757d" -}}
|
||||
{{- $color := .color | default $default_color -}}
|
||||
{{- $background := .background | default $default_background -}}
|
||||
{{- $width := .width | default "100%" -}}
|
||||
{{- $height := .height | default "180" -}}
|
||||
{{- $text := .text | default (printf "%sx%s" $width $height) -}}
|
||||
{{- $show_title := not (eq $title "false") -}}
|
||||
{{- $show_text := not (eq $text "false") -}}
|
||||
<svg
|
||||
class="{{ with $class }}{{ . }}{{ end }}"
|
||||
width="{{ $width }}"
|
||||
height="{{ $height }}"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{{ if (or $show_title $show_text) }}
|
||||
role="img"
|
||||
aria-label="{{ if $show_title }}
|
||||
{{ $title }}{{ if $show_text }}:{{ end }}
|
||||
{{ end }}{{ if ($show_text) }}{{ $text }}{{ end }}"
|
||||
{{ else }}
|
||||
aria-hidden="true"
|
||||
{{ end }}
|
||||
preserveAspectRatio="xMidYMid slice">
|
||||
{{- if $show_title }}<title>{{ $title }}</title>{{ end -}}
|
||||
<rect width="100%" height="100%" fill="{{ $background }}" />
|
||||
{{- if $show_text }}
|
||||
<text x="40%" y="50%" fill="{{ $color }}" dy=".3em">{{ $text }}</text>
|
||||
{{ end -}}
|
||||
</svg>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
module github.com/gohugoio/hugo-mod-bootstrap-scss/v5
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000 // indirect
|
||||
github.com/twbs/bootstrap v5.3.3+incompatible // indirect
|
||||
)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000 h1:GZxx4Hc+yb0/t3/rau1j8XlAxLE4CyXns2fqQbyqWfs=
|
||||
github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000/go.mod h1:mFberT6ZtcchrsDtfvJM7aAH2bDKLdOnruUHl0hlapI=
|
||||
github.com/twbs/bootstrap v5.3.3+incompatible h1:goFoqinzdHfkeegpFP7pvhbd0g+A3O2hbU3XCjuNrEQ=
|
||||
github.com/twbs/bootstrap v5.3.3+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0=
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[build]
|
||||
publish = "exampleSite/public"
|
||||
command = "hugo --gc -s exampleSite --minify"
|
||||
|
||||
[build.environment]
|
||||
HUGO_VERSION = "0.143.1"
|
||||
|
||||
[context.deploy-preview]
|
||||
command = "hugo -s exampleSite --minify -D -F -b $DEPLOY_PRIME_URL"
|
||||
|
||||
[context.branch-deploy]
|
||||
command = "hugo -s exampleSite --minify --gc -b $DEPLOY_PRIME_URL"
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 18762bc856492ade7e3626a7fca2b214b547669d
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 GoHugo.io
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# hugo-mod-jslibs-dist
|
||||
|
||||
Thin Hugo Module wrappers around some popular JS libs' distribution source code.
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
This module mounts [AlpineJS](https://github.com/alpinejs/alpine)'s `packages/.../dist` folders.
|
||||
|
||||
See [Releases](https://github.com/gohugoio/hugo-mod-jslibs-dist/releases) for version information. We use the [Semver Pair](https://github.com/bep/semverpair) versioning scheme.
|
||||
|
||||
The `packages` folder is mounted in `assets/jslibs/alpinejs/v3`.
|
||||
|
||||
That means that you can just import it into your Hugo config:
|
||||
|
||||
```toml
|
||||
[[module.imports]]
|
||||
path = "github.com/gohugoio/hugo-mod-jslibs-dist/alpinejs/v3"
|
||||
```
|
||||
|
||||
And then use it in your JS files:
|
||||
|
||||
```js
|
||||
import Alpine from 'jslibs/alpinejs/v3/alpinejs/dist/module.esm.js';
|
||||
import intersect from 'jslibs/alpinejs/v3/intersect/dist/module.esm.js';
|
||||
import persist from 'jslibs/alpinejs/v3/persist/dist/module.esm.js';
|
||||
|
||||
// Set up and start Alpine.
|
||||
(function() {
|
||||
// Register AlpineJS plugins.
|
||||
Alpine.plugin(intersect);
|
||||
Alpine.plugin(persist);
|
||||
|
||||
// Start Alpine.
|
||||
Alpine.start();
|
||||
|
||||
})();
|
||||
```
|
||||
|
||||
Note that AlpineJS now requires ES target 2017 or later to work:
|
||||
|
||||
```handlebars
|
||||
{{ $params := dict }}
|
||||
{{ $opts := dict "sourceMap" $sourceMap "minify" (ne hugo.Environment "development") "target" "es2017" "params" $params }}
|
||||
{{ $js := $js | js.Build $opts }}
|
||||
```
|
||||
|
||||
Note that this works great in combination with [Turbo](https://github.com/gohugoio/hugo-mod-jslibs/tree/master/turbo), but you would need to set up something like [these listeners](https://gist.github.com/bep/a9809f0cb119e44e8ddbe37dd1e58b50) to make it work properly.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
[module]
|
||||
[[module.mounts]]
|
||||
source = "packages"
|
||||
target = "assets/jslibs/alpinejs/v3"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/gohugoio/hugo-mod-jslibs-dist/alpinejs/v3
|
||||
|
||||
go 1.17
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import Alpine from './../src/index'
|
||||
|
||||
window.Alpine = Alpine
|
||||
|
||||
queueMicrotask(() => {
|
||||
Alpine.start()
|
||||
})
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import Alpine from './../src/index'
|
||||
|
||||
export default Alpine
|
||||
|
||||
export { Alpine }
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "alpinejs",
|
||||
"version": "3.13.8",
|
||||
"description": "The rugged, minimal JavaScript framework",
|
||||
"homepage": "https://alpinejs.dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/alpinejs/alpine.git",
|
||||
"directory": "packages/alpinejs"
|
||||
},
|
||||
"author": "Caleb Porzio",
|
||||
"license": "MIT",
|
||||
"main": "dist/module.cjs.js",
|
||||
"module": "dist/module.esm.js",
|
||||
"unpkg": "dist/cdn.min.js",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "~3.1.1"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import { setReactivityEngine, disableEffectScheduling, reactive, effect, release, raw, watch } from './reactivity'
|
||||
import { mapAttributes, directive, setPrefix as prefix, prefix as prefixed } from './directives'
|
||||
import { start, addRootSelector, addInitSelector, closestRoot, findClosest, initTree, destroyTree, interceptInit } from './lifecycle'
|
||||
import { onElRemoved, onAttributeRemoved, onAttributesAdded, mutateDom, deferMutations, flushAndStopDeferringMutations, startObservingMutations, stopObservingMutations } from './mutation'
|
||||
import { mergeProxies, closestDataStack, addScopeToNode, scope as $data } from './scope'
|
||||
import { setEvaluator, evaluate, evaluateLater, dontAutoEvaluateFunctions } from './evaluator'
|
||||
import { transition } from './directives/x-transition'
|
||||
import { clone, cloneNode, skipDuringClone, onlyDuringClone, interceptClone } from './clone'
|
||||
import { interceptor } from './interceptor'
|
||||
import { getBinding as bound, extractProp } from './utils/bind'
|
||||
import { debounce } from './utils/debounce'
|
||||
import { throttle } from './utils/throttle'
|
||||
import { setStyles } from './utils/styles'
|
||||
import { entangle } from './entangle'
|
||||
import { nextTick } from './nextTick'
|
||||
import { walk } from './utils/walk'
|
||||
import { plugin } from './plugin'
|
||||
import { magic } from './magics'
|
||||
import { store } from './store'
|
||||
import { bind } from './binds'
|
||||
import { data } from './datas'
|
||||
|
||||
let Alpine = {
|
||||
get reactive() { return reactive },
|
||||
get release() { return release },
|
||||
get effect() { return effect },
|
||||
get raw() { return raw },
|
||||
version: ALPINE_VERSION,
|
||||
flushAndStopDeferringMutations,
|
||||
dontAutoEvaluateFunctions,
|
||||
disableEffectScheduling,
|
||||
startObservingMutations,
|
||||
stopObservingMutations,
|
||||
setReactivityEngine,
|
||||
onAttributeRemoved,
|
||||
onAttributesAdded,
|
||||
closestDataStack,
|
||||
skipDuringClone,
|
||||
onlyDuringClone,
|
||||
addRootSelector,
|
||||
addInitSelector,
|
||||
interceptClone,
|
||||
addScopeToNode,
|
||||
deferMutations,
|
||||
mapAttributes,
|
||||
evaluateLater,
|
||||
interceptInit,
|
||||
setEvaluator,
|
||||
mergeProxies,
|
||||
extractProp,
|
||||
findClosest,
|
||||
onElRemoved,
|
||||
closestRoot,
|
||||
destroyTree,
|
||||
interceptor, // INTERNAL: not public API and is subject to change without major release.
|
||||
transition, // INTERNAL
|
||||
setStyles, // INTERNAL
|
||||
mutateDom,
|
||||
directive,
|
||||
entangle,
|
||||
throttle,
|
||||
debounce,
|
||||
evaluate,
|
||||
initTree,
|
||||
nextTick,
|
||||
prefixed,
|
||||
prefix,
|
||||
plugin,
|
||||
magic,
|
||||
store,
|
||||
start,
|
||||
clone, // INTERNAL
|
||||
cloneNode, // INTERNAL
|
||||
bound,
|
||||
$data,
|
||||
watch,
|
||||
walk,
|
||||
data,
|
||||
bind,
|
||||
}
|
||||
|
||||
export default Alpine
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import { attributesOnly, directives } from "./directives"
|
||||
|
||||
let binds = {}
|
||||
|
||||
export function bind(name, bindings) {
|
||||
let getBindings = typeof bindings !== 'function' ? () => bindings : bindings
|
||||
|
||||
if (name instanceof Element) {
|
||||
return applyBindingsObject(name, getBindings())
|
||||
} else {
|
||||
binds[name] = getBindings
|
||||
}
|
||||
|
||||
return () => {} // Null cleanup...
|
||||
}
|
||||
|
||||
export function injectBindingProviders(obj) {
|
||||
Object.entries(binds).forEach(([name, callback]) => {
|
||||
Object.defineProperty(obj, name, {
|
||||
get() {
|
||||
return (...args) => {
|
||||
return callback(...args)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
export function addVirtualBindings(el, bindings) {
|
||||
let getBindings = typeof bindings !== 'function' ? () => bindings : bindings
|
||||
|
||||
el._x_virtualDirectives = getBindings()
|
||||
}
|
||||
|
||||
export function applyBindingsObject(el, obj, original) {
|
||||
let cleanupRunners = []
|
||||
|
||||
while (cleanupRunners.length) cleanupRunners.pop()()
|
||||
|
||||
let attributes = Object.entries(obj).map(([name, value]) => ({ name, value }))
|
||||
|
||||
let staticAttributes = attributesOnly(attributes)
|
||||
|
||||
// Handle binding normal HTML attributes (non-Alpine directives).
|
||||
attributes = attributes.map(attribute => {
|
||||
if (staticAttributes.find(attr => attr.name === attribute.name)) {
|
||||
return {
|
||||
name: `x-bind:${attribute.name}`,
|
||||
value: `"${attribute.value}"`,
|
||||
}
|
||||
}
|
||||
|
||||
return attribute
|
||||
})
|
||||
|
||||
directives(el, attributes, original).map(handle => {
|
||||
cleanupRunners.push(handle.runCleanups)
|
||||
|
||||
handle()
|
||||
})
|
||||
|
||||
return () => {
|
||||
while (cleanupRunners.length) cleanupRunners.pop()()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import { effect, release, overrideEffect } from "./reactivity"
|
||||
import { initTree, isRoot } from "./lifecycle"
|
||||
import { walk } from "./utils/walk"
|
||||
|
||||
export let isCloning = false
|
||||
|
||||
export function skipDuringClone(callback, fallback = () => {}) {
|
||||
return (...args) => isCloning ? fallback(...args) : callback(...args)
|
||||
}
|
||||
|
||||
export function onlyDuringClone(callback) {
|
||||
return (...args) => isCloning && callback(...args)
|
||||
}
|
||||
|
||||
let interceptors = []
|
||||
|
||||
export function interceptClone(callback) {
|
||||
interceptors.push(callback)
|
||||
}
|
||||
|
||||
export function cloneNode(from, to)
|
||||
{
|
||||
interceptors.forEach(i => i(from, to))
|
||||
|
||||
isCloning = true
|
||||
|
||||
// We don't need reactive effects in the new tree.
|
||||
// Cloning is just used to seed new server HTML with
|
||||
// Alpine before "morphing" it onto live Alpine...
|
||||
dontRegisterReactiveSideEffects(() => {
|
||||
initTree(to, (el, callback) => {
|
||||
// We're hijacking the "walker" so that we
|
||||
// only initialize the element we're cloning...
|
||||
callback(el, () => {})
|
||||
})
|
||||
})
|
||||
|
||||
isCloning = false
|
||||
}
|
||||
|
||||
export let isCloningLegacy = false
|
||||
|
||||
/** deprecated */
|
||||
export function clone(oldEl, newEl) {
|
||||
if (! newEl._x_dataStack) newEl._x_dataStack = oldEl._x_dataStack
|
||||
|
||||
isCloning = true
|
||||
isCloningLegacy = true
|
||||
|
||||
dontRegisterReactiveSideEffects(() => {
|
||||
cloneTree(newEl)
|
||||
})
|
||||
|
||||
isCloning = false
|
||||
isCloningLegacy = false
|
||||
}
|
||||
|
||||
/** deprecated */
|
||||
export function cloneTree(el) {
|
||||
let hasRunThroughFirstEl = false
|
||||
|
||||
let shallowWalker = (el, callback) => {
|
||||
walk(el, (el, skip) => {
|
||||
if (hasRunThroughFirstEl && isRoot(el)) return skip()
|
||||
|
||||
hasRunThroughFirstEl = true
|
||||
|
||||
callback(el, skip)
|
||||
})
|
||||
}
|
||||
|
||||
initTree(el, shallowWalker)
|
||||
}
|
||||
|
||||
function dontRegisterReactiveSideEffects(callback) {
|
||||
let cache = effect
|
||||
|
||||
overrideEffect((callback, el) => {
|
||||
let storedEffect = cache(callback)
|
||||
|
||||
release(storedEffect)
|
||||
|
||||
return () => {}
|
||||
})
|
||||
|
||||
callback()
|
||||
|
||||
overrideEffect(cache)
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
let datas = {}
|
||||
|
||||
export function data(name, callback) {
|
||||
datas[name] = callback
|
||||
}
|
||||
|
||||
export function injectDataProviders(obj, context) {
|
||||
Object.entries(datas).forEach(([name, callback]) => {
|
||||
Object.defineProperty(obj, name, {
|
||||
get() {
|
||||
return (...args) => {
|
||||
return callback.bind(context)(...args)
|
||||
}
|
||||
},
|
||||
|
||||
enumerable: false,
|
||||
})
|
||||
})
|
||||
|
||||
return obj
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
import { onAttributeRemoved, onElRemoved } from './mutation'
|
||||
import { evaluate, evaluateLater } from './evaluator'
|
||||
import { elementBoundEffect } from './reactivity'
|
||||
import Alpine from './alpine'
|
||||
|
||||
let prefixAsString = 'x-'
|
||||
|
||||
export function prefix(subject = '') {
|
||||
return prefixAsString + subject
|
||||
}
|
||||
|
||||
export function setPrefix(newPrefix) {
|
||||
prefixAsString = newPrefix
|
||||
}
|
||||
|
||||
let directiveHandlers = {}
|
||||
|
||||
export function directive(name, callback) {
|
||||
directiveHandlers[name] = callback
|
||||
|
||||
return {
|
||||
before(directive) {
|
||||
if (!directiveHandlers[directive]) {
|
||||
console.warn(String.raw`Cannot find directive \`${directive}\`. \`${name}\` will use the default order of execution`);
|
||||
return;
|
||||
}
|
||||
const pos = directiveOrder.indexOf(directive);
|
||||
directiveOrder.splice(pos >= 0 ? pos : directiveOrder.indexOf('DEFAULT'), 0, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function directives(el, attributes, originalAttributeOverride) {
|
||||
attributes = Array.from(attributes)
|
||||
|
||||
if (el._x_virtualDirectives) {
|
||||
let vAttributes = Object.entries(el._x_virtualDirectives).map(([name, value]) => ({ name, value }))
|
||||
|
||||
let staticAttributes = attributesOnly(vAttributes)
|
||||
|
||||
// Handle binding normal HTML attributes (non-Alpine directives).
|
||||
vAttributes = vAttributes.map(attribute => {
|
||||
if (staticAttributes.find(attr => attr.name === attribute.name)) {
|
||||
return {
|
||||
name: `x-bind:${attribute.name}`,
|
||||
value: `"${attribute.value}"`,
|
||||
}
|
||||
}
|
||||
|
||||
return attribute
|
||||
})
|
||||
|
||||
attributes = attributes.concat(vAttributes)
|
||||
}
|
||||
|
||||
let transformedAttributeMap = {}
|
||||
|
||||
let directives = attributes
|
||||
.map(toTransformedAttributes((newName, oldName) => transformedAttributeMap[newName] = oldName))
|
||||
.filter(outNonAlpineAttributes)
|
||||
.map(toParsedDirectives(transformedAttributeMap, originalAttributeOverride))
|
||||
.sort(byPriority)
|
||||
|
||||
return directives.map(directive => {
|
||||
return getDirectiveHandler(el, directive)
|
||||
})
|
||||
}
|
||||
|
||||
export function attributesOnly(attributes) {
|
||||
return Array.from(attributes)
|
||||
.map(toTransformedAttributes())
|
||||
.filter(attr => ! outNonAlpineAttributes(attr))
|
||||
}
|
||||
|
||||
let isDeferringHandlers = false
|
||||
let directiveHandlerStacks = new Map
|
||||
let currentHandlerStackKey = Symbol()
|
||||
|
||||
export function deferHandlingDirectives(callback) {
|
||||
isDeferringHandlers = true
|
||||
|
||||
let key = Symbol()
|
||||
|
||||
currentHandlerStackKey = key
|
||||
|
||||
directiveHandlerStacks.set(key, [])
|
||||
|
||||
let flushHandlers = () => {
|
||||
while (directiveHandlerStacks.get(key).length) directiveHandlerStacks.get(key).shift()()
|
||||
|
||||
directiveHandlerStacks.delete(key)
|
||||
}
|
||||
|
||||
let stopDeferring = () => { isDeferringHandlers = false; flushHandlers() }
|
||||
|
||||
callback(flushHandlers)
|
||||
|
||||
stopDeferring()
|
||||
}
|
||||
|
||||
export function getElementBoundUtilities(el) {
|
||||
let cleanups = []
|
||||
|
||||
let cleanup = callback => cleanups.push(callback)
|
||||
|
||||
let [effect, cleanupEffect] = elementBoundEffect(el)
|
||||
|
||||
cleanups.push(cleanupEffect)
|
||||
|
||||
let utilities = {
|
||||
Alpine,
|
||||
effect,
|
||||
cleanup,
|
||||
evaluateLater: evaluateLater.bind(evaluateLater, el),
|
||||
evaluate: evaluate.bind(evaluate, el),
|
||||
}
|
||||
|
||||
let doCleanup = () => cleanups.forEach(i => i())
|
||||
|
||||
return [utilities, doCleanup]
|
||||
}
|
||||
|
||||
export function getDirectiveHandler(el, directive) {
|
||||
let noop = () => {}
|
||||
|
||||
let handler = directiveHandlers[directive.type] || noop
|
||||
|
||||
let [utilities, cleanup] = getElementBoundUtilities(el)
|
||||
|
||||
onAttributeRemoved(el, directive.original, cleanup)
|
||||
|
||||
let fullHandler = () => {
|
||||
if (el._x_ignore || el._x_ignoreSelf) return
|
||||
|
||||
handler.inline && handler.inline(el, directive, utilities)
|
||||
|
||||
handler = handler.bind(handler, el, directive, utilities)
|
||||
|
||||
isDeferringHandlers ? directiveHandlerStacks.get(currentHandlerStackKey).push(handler) : handler()
|
||||
}
|
||||
|
||||
fullHandler.runCleanups = cleanup
|
||||
|
||||
return fullHandler
|
||||
}
|
||||
|
||||
export let startingWith = (subject, replacement) => ({ name, value }) => {
|
||||
if (name.startsWith(subject)) name = name.replace(subject, replacement)
|
||||
|
||||
return { name, value }
|
||||
}
|
||||
|
||||
export let into = i => i
|
||||
|
||||
function toTransformedAttributes(callback = () => {}) {
|
||||
return ({ name, value }) => {
|
||||
let { name: newName, value: newValue } = attributeTransformers.reduce((carry, transform) => {
|
||||
return transform(carry)
|
||||
}, { name, value })
|
||||
|
||||
if (newName !== name) callback(newName, name)
|
||||
|
||||
return { name: newName, value: newValue }
|
||||
}
|
||||
}
|
||||
|
||||
let attributeTransformers = []
|
||||
|
||||
export function mapAttributes(callback) {
|
||||
attributeTransformers.push(callback)
|
||||
}
|
||||
|
||||
function outNonAlpineAttributes({ name }) {
|
||||
return alpineAttributeRegex().test(name)
|
||||
}
|
||||
|
||||
let alpineAttributeRegex = () => (new RegExp(`^${prefixAsString}([^:^.]+)\\b`))
|
||||
|
||||
function toParsedDirectives(transformedAttributeMap, originalAttributeOverride) {
|
||||
return ({ name, value }) => {
|
||||
let typeMatch = name.match(alpineAttributeRegex())
|
||||
let valueMatch = name.match(/:([a-zA-Z0-9\-_:]+)/)
|
||||
let modifiers = name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []
|
||||
let original = originalAttributeOverride || transformedAttributeMap[name] || name
|
||||
|
||||
return {
|
||||
type: typeMatch ? typeMatch[1] : null,
|
||||
value: valueMatch ? valueMatch[1] : null,
|
||||
modifiers: modifiers.map(i => i.replace('.', '')),
|
||||
expression: value,
|
||||
original,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT = 'DEFAULT'
|
||||
|
||||
let directiveOrder = [
|
||||
'ignore',
|
||||
'ref',
|
||||
'data',
|
||||
'id',
|
||||
'anchor',
|
||||
'bind',
|
||||
'init',
|
||||
'for',
|
||||
'model',
|
||||
'modelable',
|
||||
'transition',
|
||||
'show',
|
||||
'if',
|
||||
DEFAULT,
|
||||
'teleport',
|
||||
]
|
||||
|
||||
function byPriority(a, b) {
|
||||
let typeA = directiveOrder.indexOf(a.type) === -1 ? DEFAULT : a.type
|
||||
let typeB = directiveOrder.indexOf(b.type) === -1 ? DEFAULT : b.type
|
||||
|
||||
return directiveOrder.indexOf(typeA) - directiveOrder.indexOf(typeB)
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { directive } from '../directives'
|
||||
import { warn } from '../utils/warn'
|
||||
|
||||
import './x-transition'
|
||||
import './x-modelable'
|
||||
import './x-teleport'
|
||||
import './x-ignore'
|
||||
import './x-effect'
|
||||
import './x-model'
|
||||
import './x-cloak'
|
||||
import './x-init'
|
||||
import './x-text'
|
||||
import './x-html'
|
||||
import './x-bind'
|
||||
import './x-data'
|
||||
import './x-show'
|
||||
import './x-for'
|
||||
import './x-ref'
|
||||
import './x-if'
|
||||
import './x-id'
|
||||
import './x-on'
|
||||
|
||||
// Register warnings for people using plugin syntaxes and not loading the plugin itself:
|
||||
warnMissingPluginDirective('Collapse', 'collapse', 'collapse')
|
||||
warnMissingPluginDirective('Intersect', 'intersect', 'intersect')
|
||||
warnMissingPluginDirective('Focus', 'trap', 'focus')
|
||||
warnMissingPluginDirective('Mask', 'mask', 'mask')
|
||||
|
||||
function warnMissingPluginDirective(name, directiveName, slug) {
|
||||
directive(directiveName, (el) => warn(`You can't use [x-${directiveName}] without first installing the "${name}" plugin here: https://alpinejs.dev/plugins/${slug}`, el))
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { directive, into, mapAttributes, prefix, startingWith } from '../directives'
|
||||
import { evaluateLater } from '../evaluator'
|
||||
import { mutateDom } from '../mutation'
|
||||
import bind from '../utils/bind'
|
||||
import { applyBindingsObject, injectBindingProviders } from '../binds'
|
||||
|
||||
mapAttributes(startingWith(':', into(prefix('bind:'))))
|
||||
|
||||
let handler = (el, { value, modifiers, expression, original }, { effect, cleanup }) => {
|
||||
if (! value) {
|
||||
let bindingProviders = {}
|
||||
injectBindingProviders(bindingProviders)
|
||||
|
||||
let getBindings = evaluateLater(el, expression)
|
||||
|
||||
getBindings(bindings => {
|
||||
applyBindingsObject(el, bindings, original)
|
||||
}, { scope: bindingProviders } )
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (value === 'key') return storeKeyForXFor(el, expression)
|
||||
|
||||
if (el._x_inlineBindings && el._x_inlineBindings[value] && el._x_inlineBindings[value].extract) {
|
||||
return
|
||||
}
|
||||
|
||||
let evaluate = evaluateLater(el, expression)
|
||||
|
||||
effect(() => evaluate(result => {
|
||||
// If nested object key is undefined, set the default value to empty string.
|
||||
if (result === undefined && typeof expression === 'string' && expression.match(/\./)) {
|
||||
result = ''
|
||||
}
|
||||
|
||||
mutateDom(() => bind(el, value, result, modifiers))
|
||||
}))
|
||||
|
||||
cleanup(() => {
|
||||
el._x_undoAddedClasses && el._x_undoAddedClasses()
|
||||
el._x_undoAddedStyles && el._x_undoAddedStyles()
|
||||
})
|
||||
}
|
||||
|
||||
// @todo: see if I can take advantage of the object created here inside the
|
||||
// non-inline handler above so we're not duplicating work twice...
|
||||
handler.inline = (el, { value, modifiers, expression }) => {
|
||||
if (! value) return;
|
||||
|
||||
if (! el._x_inlineBindings) el._x_inlineBindings = {}
|
||||
|
||||
el._x_inlineBindings[value] = { expression, extract: false }
|
||||
}
|
||||
|
||||
directive('bind', handler)
|
||||
|
||||
function storeKeyForXFor(el, expression) {
|
||||
el._x_keyExpression = expression
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { directive, prefix } from '../directives'
|
||||
import { mutateDom } from '../mutation'
|
||||
|
||||
directive('cloak', el => queueMicrotask(() => mutateDom(() => el.removeAttribute(prefix('cloak')))))
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import { directive, prefix } from '../directives'
|
||||
import { initInterceptors } from '../interceptor'
|
||||
import { injectDataProviders } from '../datas'
|
||||
import { addRootSelector } from '../lifecycle'
|
||||
import { interceptClone, isCloning, isCloningLegacy } from '../clone'
|
||||
import { addScopeToNode } from '../scope'
|
||||
import { injectMagics, magic } from '../magics'
|
||||
import { reactive } from '../reactivity'
|
||||
import { evaluate } from '../evaluator'
|
||||
|
||||
addRootSelector(() => `[${prefix('data')}]`)
|
||||
|
||||
directive('data', ((el, { expression }, { cleanup }) => {
|
||||
if (shouldSkipRegisteringDataDuringClone(el)) return
|
||||
|
||||
expression = expression === '' ? '{}' : expression
|
||||
|
||||
let magicContext = {}
|
||||
let cleanup1 = injectMagics(magicContext, el).cleanup
|
||||
|
||||
let dataProviderContext = {}
|
||||
injectDataProviders(dataProviderContext, magicContext)
|
||||
|
||||
let data = evaluate(el, expression, { scope: dataProviderContext })
|
||||
|
||||
if (data === undefined || data === true) data = {}
|
||||
|
||||
let cleanup2 = injectMagics(data, el).cleanup
|
||||
|
||||
let reactiveData = reactive(data)
|
||||
|
||||
initInterceptors(reactiveData)
|
||||
|
||||
let undo = addScopeToNode(el, reactiveData)
|
||||
|
||||
reactiveData['init'] && evaluate(el, reactiveData['init'])
|
||||
|
||||
cleanup(() => {
|
||||
reactiveData['destroy'] && evaluate(el, reactiveData['destroy'])
|
||||
|
||||
undo()
|
||||
|
||||
// MemLeak1: Issue #2140
|
||||
cleanup1()
|
||||
cleanup2()
|
||||
})
|
||||
}))
|
||||
|
||||
interceptClone((from, to) => {
|
||||
// Transfer over existing runtime Alpine state from
|
||||
// the existing dom tree over to the new one...
|
||||
if (from._x_dataStack) {
|
||||
to._x_dataStack = from._x_dataStack
|
||||
|
||||
// Set a flag to signify the new tree is using
|
||||
// pre-seeded state (used so x-data knows when
|
||||
// and when not to initialize state)...
|
||||
to.setAttribute('data-has-alpine-state', true)
|
||||
}
|
||||
})
|
||||
|
||||
// If we are cloning a tree, we only want to evaluate x-data if another
|
||||
// x-data context DOESN'T exist on the component.
|
||||
// The reason a data context WOULD exist is that we graft root x-data state over
|
||||
// from the live tree before hydrating the clone tree.
|
||||
function shouldSkipRegisteringDataDuringClone(el) {
|
||||
if (! isCloning) return false
|
||||
if (isCloningLegacy) return true
|
||||
|
||||
return el.hasAttribute('data-has-alpine-state')
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { skipDuringClone } from '../clone'
|
||||
import { directive } from '../directives'
|
||||
import { evaluate, evaluateLater } from '../evaluator'
|
||||
|
||||
directive('effect', skipDuringClone((el, { expression }, { effect }) => {
|
||||
effect(evaluateLater(el, expression))
|
||||
}))
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
import { addScopeToNode } from '../scope'
|
||||
import { evaluateLater } from '../evaluator'
|
||||
import { directive } from '../directives'
|
||||
import { reactive } from '../reactivity'
|
||||
import { initTree } from '../lifecycle'
|
||||
import { mutateDom } from '../mutation'
|
||||
import { warn } from '../utils/warn'
|
||||
import { dequeueJob } from '../scheduler'
|
||||
import { skipDuringClone } from '../clone'
|
||||
|
||||
directive('for', (el, { expression }, { effect, cleanup }) => {
|
||||
let iteratorNames = parseForExpression(expression)
|
||||
|
||||
let evaluateItems = evaluateLater(el, iteratorNames.items)
|
||||
let evaluateKey = evaluateLater(el,
|
||||
// the x-bind:key expression is stored for our use instead of evaluated.
|
||||
el._x_keyExpression || 'index'
|
||||
)
|
||||
|
||||
el._x_prevKeys = []
|
||||
el._x_lookup = {}
|
||||
|
||||
effect(() => loop(el, iteratorNames, evaluateItems, evaluateKey))
|
||||
|
||||
cleanup(() => {
|
||||
Object.values(el._x_lookup).forEach(el => el.remove())
|
||||
|
||||
delete el._x_prevKeys
|
||||
delete el._x_lookup
|
||||
})
|
||||
})
|
||||
|
||||
let shouldFastRender = true
|
||||
|
||||
function loop(el, iteratorNames, evaluateItems, evaluateKey) {
|
||||
let isObject = i => typeof i === 'object' && ! Array.isArray(i)
|
||||
let templateEl = el
|
||||
|
||||
evaluateItems(items => {
|
||||
// Prepare yourself. There's a lot going on here. Take heart,
|
||||
// every bit of complexity in this function was added for
|
||||
// the purpose of making Alpine fast with large datas.
|
||||
|
||||
// Support number literals. Ex: x-for="i in 100"
|
||||
if (isNumeric(items) && items >= 0) {
|
||||
items = Array.from(Array(items).keys(), i => i + 1)
|
||||
}
|
||||
|
||||
if (items === undefined) items = []
|
||||
|
||||
let lookup = el._x_lookup
|
||||
let prevKeys = el._x_prevKeys
|
||||
let scopes = []
|
||||
let keys = []
|
||||
|
||||
// In order to preserve DOM elements (move instead of replace)
|
||||
// we need to generate all the keys for every iteration up
|
||||
// front. These will be our source of truth for diffing.
|
||||
if (isObject(items)) {
|
||||
items = Object.entries(items).map(([key, value]) => {
|
||||
let scope = getIterationScopeVariables(iteratorNames, value, key, items)
|
||||
|
||||
evaluateKey(value => {
|
||||
if (keys.includes(value)) warn('Duplicate key on x-for', el)
|
||||
|
||||
keys.push(value)
|
||||
}, { scope: { index: key, ...scope} })
|
||||
|
||||
scopes.push(scope)
|
||||
})
|
||||
} else {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let scope = getIterationScopeVariables(iteratorNames, items[i], i, items)
|
||||
|
||||
evaluateKey(value => {
|
||||
if (keys.includes(value)) warn('Duplicate key on x-for', el)
|
||||
|
||||
keys.push(value)
|
||||
}, { scope: { index: i, ...scope} })
|
||||
|
||||
scopes.push(scope)
|
||||
}
|
||||
}
|
||||
|
||||
// Rather than making DOM manipulations inside one large loop, we'll
|
||||
// instead track which mutations need to be made in the following
|
||||
// arrays. After we're finished, we can batch them at the end.
|
||||
let adds = []
|
||||
let moves = []
|
||||
let removes = []
|
||||
let sames = []
|
||||
|
||||
// First, we track elements that will need to be removed.
|
||||
for (let i = 0; i < prevKeys.length; i++) {
|
||||
let key = prevKeys[i]
|
||||
|
||||
if (keys.indexOf(key) === -1) removes.push(key)
|
||||
}
|
||||
|
||||
// Notice we're mutating prevKeys as we go. This makes it
|
||||
// so that we can efficiently make incremental comparisons.
|
||||
prevKeys = prevKeys.filter(key => ! removes.includes(key))
|
||||
|
||||
let lastKey = 'template'
|
||||
|
||||
// This is the important part of the diffing algo. Identifying
|
||||
// which keys (future DOM elements) are new, which ones have
|
||||
// or haven't moved (noting where they moved to / from).
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let key = keys[i]
|
||||
|
||||
let prevIndex = prevKeys.indexOf(key)
|
||||
|
||||
if (prevIndex === -1) {
|
||||
// New key found.
|
||||
prevKeys.splice(i, 0, key)
|
||||
|
||||
adds.push([lastKey, i])
|
||||
} else if (prevIndex !== i) {
|
||||
// A key has moved.
|
||||
let keyInSpot = prevKeys.splice(i, 1)[0]
|
||||
let keyForSpot = prevKeys.splice(prevIndex - 1, 1)[0]
|
||||
|
||||
prevKeys.splice(i, 0, keyForSpot)
|
||||
prevKeys.splice(prevIndex, 0, keyInSpot)
|
||||
|
||||
moves.push([keyInSpot, keyForSpot])
|
||||
} else {
|
||||
// This key hasn't moved, but we'll still keep track
|
||||
// so that we can refresh it later on.
|
||||
sames.push(key)
|
||||
}
|
||||
|
||||
lastKey = key
|
||||
}
|
||||
|
||||
// Now that we've done the diffing work, we can apply the mutations
|
||||
// in batches for both separating types work and optimizing
|
||||
// for browser performance.
|
||||
|
||||
// We'll remove all the nodes that need to be removed,
|
||||
// letting the mutation observer pick them up and
|
||||
// clean up any side effects they had.
|
||||
for (let i = 0; i < removes.length; i++) {
|
||||
let key = removes[i]
|
||||
|
||||
// Remove any queued effects that might run after the DOM node has been removed.
|
||||
if (!! lookup[key]._x_effects) {
|
||||
lookup[key]._x_effects.forEach(dequeueJob)
|
||||
}
|
||||
|
||||
lookup[key].remove()
|
||||
|
||||
lookup[key] = null
|
||||
delete lookup[key]
|
||||
}
|
||||
|
||||
// Here we'll move elements around, skipping
|
||||
// mutation observer triggers by using "mutateDom".
|
||||
for (let i = 0; i < moves.length; i++) {
|
||||
let [keyInSpot, keyForSpot] = moves[i]
|
||||
|
||||
let elInSpot = lookup[keyInSpot]
|
||||
let elForSpot = lookup[keyForSpot]
|
||||
|
||||
let marker = document.createElement('div')
|
||||
|
||||
mutateDom(() => {
|
||||
if (! elForSpot) warn(`x-for ":key" is undefined or invalid`, templateEl, keyForSpot, lookup)
|
||||
|
||||
elForSpot.after(marker)
|
||||
elInSpot.after(elForSpot)
|
||||
elForSpot._x_currentIfEl && elForSpot.after(elForSpot._x_currentIfEl)
|
||||
marker.before(elInSpot)
|
||||
elInSpot._x_currentIfEl && elInSpot.after(elInSpot._x_currentIfEl)
|
||||
marker.remove()
|
||||
})
|
||||
|
||||
elForSpot._x_refreshXForScope(scopes[keys.indexOf(keyForSpot)])
|
||||
}
|
||||
|
||||
// We can now create and add new elements.
|
||||
for (let i = 0; i < adds.length; i++) {
|
||||
let [lastKey, index] = adds[i]
|
||||
|
||||
let lastEl = (lastKey === 'template') ? templateEl : lookup[lastKey]
|
||||
// If the element is a x-if template evaluated to true,
|
||||
// point lastEl to the if-generated node
|
||||
if (lastEl._x_currentIfEl) lastEl = lastEl._x_currentIfEl
|
||||
|
||||
let scope = scopes[index]
|
||||
let key = keys[index]
|
||||
|
||||
let clone = document.importNode(templateEl.content, true).firstElementChild
|
||||
|
||||
let reactiveScope = reactive(scope)
|
||||
|
||||
addScopeToNode(clone, reactiveScope, templateEl)
|
||||
|
||||
clone._x_refreshXForScope = (newScope) => {
|
||||
Object.entries(newScope).forEach(([key, value]) => {
|
||||
reactiveScope[key] = value
|
||||
})
|
||||
}
|
||||
|
||||
mutateDom(() => {
|
||||
lastEl.after(clone)
|
||||
|
||||
// These nodes will be "inited" as morph walks the tree...
|
||||
skipDuringClone(() => initTree(clone))()
|
||||
})
|
||||
|
||||
if (typeof key === 'object') {
|
||||
warn('x-for key cannot be an object, it must be a string or an integer', templateEl)
|
||||
}
|
||||
|
||||
lookup[key] = clone
|
||||
}
|
||||
|
||||
// If an element hasn't changed, we still want to "refresh" the
|
||||
// data it depends on in case the data has changed in an
|
||||
// "unobservable" way.
|
||||
for (let i = 0; i < sames.length; i++) {
|
||||
lookup[sames[i]]._x_refreshXForScope(scopes[keys.indexOf(sames[i])])
|
||||
}
|
||||
|
||||
// Now we'll log the keys (and the order they're in) for comparing
|
||||
// against next time.
|
||||
templateEl._x_prevKeys = keys
|
||||
})
|
||||
}
|
||||
|
||||
// This was taken from VueJS 2.* core. Thanks Vue!
|
||||
function parseForExpression(expression) {
|
||||
let forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
||||
let stripParensRE = /^\s*\(|\)\s*$/g
|
||||
let forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
||||
let inMatch = expression.match(forAliasRE)
|
||||
|
||||
if (! inMatch) return
|
||||
|
||||
let res = {}
|
||||
res.items = inMatch[2].trim()
|
||||
let item = inMatch[1].replace(stripParensRE, '').trim()
|
||||
let iteratorMatch = item.match(forIteratorRE)
|
||||
|
||||
if (iteratorMatch) {
|
||||
res.item = item.replace(forIteratorRE, '').trim()
|
||||
res.index = iteratorMatch[1].trim()
|
||||
|
||||
if (iteratorMatch[2]) {
|
||||
res.collection = iteratorMatch[2].trim()
|
||||
}
|
||||
} else {
|
||||
res.item = item
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
function getIterationScopeVariables(iteratorNames, item, index, items) {
|
||||
// We must create a new object, so each iteration has a new scope
|
||||
let scopeVariables = {}
|
||||
|
||||
// Support array destructuring ([foo, bar]).
|
||||
if (/^\[.*\]$/.test(iteratorNames.item) && Array.isArray(item)) {
|
||||
let names = iteratorNames.item.replace('[', '').replace(']', '').split(',').map(i => i.trim())
|
||||
|
||||
names.forEach((name, i) => {
|
||||
scopeVariables[name] = item[i]
|
||||
})
|
||||
// Support object destructuring ({ foo: 'oof', bar: 'rab' }).
|
||||
} else if (/^\{.*\}$/.test(iteratorNames.item) && ! Array.isArray(item) && typeof item === 'object') {
|
||||
let names = iteratorNames.item.replace('{', '').replace('}', '').split(',').map(i => i.trim())
|
||||
|
||||
names.forEach(name => {
|
||||
scopeVariables[name] = item[name]
|
||||
})
|
||||
} else {
|
||||
scopeVariables[iteratorNames.item] = item
|
||||
}
|
||||
|
||||
if (iteratorNames.index) scopeVariables[iteratorNames.index] = index
|
||||
|
||||
if (iteratorNames.collection) scopeVariables[iteratorNames.collection] = items
|
||||
|
||||
return scopeVariables
|
||||
}
|
||||
|
||||
function isNumeric(subject){
|
||||
return ! Array.isArray(subject) && ! isNaN(subject)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { directive } from '../directives'
|
||||
import { initTree } from '../lifecycle'
|
||||
import { mutateDom } from '../mutation'
|
||||
|
||||
directive('html', (el, { expression }, { effect, evaluateLater }) => {
|
||||
let evaluate = evaluateLater(expression)
|
||||
|
||||
effect(() => {
|
||||
evaluate(value => {
|
||||
mutateDom(() => {
|
||||
el.innerHTML = value
|
||||
|
||||
el._x_ignoreSelf = true
|
||||
initTree(el)
|
||||
delete el._x_ignoreSelf
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { interceptClone } from "../clone"
|
||||
import { directive } from "../directives"
|
||||
import { setIdRoot } from '../ids'
|
||||
|
||||
directive('id', (el, { expression }, { evaluate }) => {
|
||||
let names = evaluate(expression)
|
||||
|
||||
names.forEach(name => setIdRoot(el, name))
|
||||
})
|
||||
|
||||
interceptClone((from, to) => {
|
||||
// Transfer over existing ID registrations from
|
||||
// the existing dom tree over to the new one
|
||||
// so that there aren't ID mismatches...
|
||||
if (from._x_ids) {
|
||||
to._x_ids = from._x_ids
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { evaluateLater } from '../evaluator'
|
||||
import { addScopeToNode } from '../scope'
|
||||
import { directive } from '../directives'
|
||||
import { initTree } from '../lifecycle'
|
||||
import { mutateDom } from '../mutation'
|
||||
import { walk } from "../utils/walk"
|
||||
import { dequeueJob } from '../scheduler'
|
||||
import { warn } from "../utils/warn"
|
||||
import { skipDuringClone } from '../clone'
|
||||
|
||||
directive('if', (el, { expression }, { effect, cleanup }) => {
|
||||
if (el.tagName.toLowerCase() !== 'template') warn('x-if can only be used on a <template> tag', el)
|
||||
|
||||
let evaluate = evaluateLater(el, expression)
|
||||
|
||||
let show = () => {
|
||||
if (el._x_currentIfEl) return el._x_currentIfEl
|
||||
|
||||
let clone = el.content.cloneNode(true).firstElementChild
|
||||
|
||||
addScopeToNode(clone, {}, el)
|
||||
|
||||
mutateDom(() => {
|
||||
el.after(clone)
|
||||
|
||||
// These nodes will be "inited" as morph walks the tree...
|
||||
skipDuringClone(() => initTree(clone))()
|
||||
})
|
||||
|
||||
el._x_currentIfEl = clone
|
||||
|
||||
el._x_undoIf = () => {
|
||||
walk(clone, (node) => {
|
||||
if (!!node._x_effects) {
|
||||
node._x_effects.forEach(dequeueJob)
|
||||
}
|
||||
})
|
||||
|
||||
clone.remove();
|
||||
|
||||
delete el._x_currentIfEl
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
let hide = () => {
|
||||
if (! el._x_undoIf) return
|
||||
|
||||
el._x_undoIf()
|
||||
|
||||
delete el._x_undoIf
|
||||
}
|
||||
|
||||
effect(() => evaluate(value => {
|
||||
value ? show() : hide()
|
||||
}))
|
||||
|
||||
cleanup(() => el._x_undoIf && el._x_undoIf())
|
||||
})
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { directive } from "../directives"
|
||||
|
||||
let handler = () => {}
|
||||
|
||||
handler.inline = (el, { modifiers }, { cleanup }) => {
|
||||
modifiers.includes('self')
|
||||
? el._x_ignoreSelf = true
|
||||
: el._x_ignore = true
|
||||
|
||||
cleanup(() => {
|
||||
modifiers.includes('self')
|
||||
? delete el._x_ignoreSelf
|
||||
: delete el._x_ignore
|
||||
})
|
||||
}
|
||||
|
||||
directive('ignore', handler)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { directive, prefix } from "../directives";
|
||||
import { addInitSelector } from "../lifecycle";
|
||||
import { skipDuringClone } from "../clone";
|
||||
|
||||
addInitSelector(() => `[${prefix('init')}]`)
|
||||
|
||||
directive('init', skipDuringClone((el, { expression }, { evaluate }) => {
|
||||
if (typeof expression === 'string') {
|
||||
return !! expression.trim() && evaluate(expression, {}, false)
|
||||
}
|
||||
|
||||
return evaluate(expression, {}, false)
|
||||
}))
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
import { evaluateLater } from '../evaluator'
|
||||
import { directive } from '../directives'
|
||||
import { mutateDom } from '../mutation'
|
||||
import { nextTick } from '../nextTick'
|
||||
import bind, { safeParseBoolean } from '../utils/bind'
|
||||
import on from '../utils/on'
|
||||
import { isCloning } from '../clone'
|
||||
|
||||
directive('model', (el, { modifiers, expression }, { effect, cleanup }) => {
|
||||
let scopeTarget = el
|
||||
|
||||
if (modifiers.includes('parent')) {
|
||||
scopeTarget = el.parentNode
|
||||
}
|
||||
|
||||
let evaluateGet = evaluateLater(scopeTarget, expression)
|
||||
let evaluateSet
|
||||
|
||||
if (typeof expression === 'string') {
|
||||
evaluateSet = evaluateLater(scopeTarget, `${expression} = __placeholder`)
|
||||
} else if (typeof expression === 'function' && typeof expression() === 'string') {
|
||||
evaluateSet = evaluateLater(scopeTarget, `${expression()} = __placeholder`)
|
||||
} else {
|
||||
evaluateSet = () => {}
|
||||
}
|
||||
|
||||
let getValue = () => {
|
||||
let result
|
||||
|
||||
evaluateGet(value => result = value)
|
||||
|
||||
return isGetterSetter(result) ? result.get() : result
|
||||
}
|
||||
|
||||
let setValue = value => {
|
||||
let result
|
||||
|
||||
evaluateGet(value => result = value)
|
||||
|
||||
if (isGetterSetter(result)) {
|
||||
result.set(value)
|
||||
} else {
|
||||
evaluateSet(() => {}, {
|
||||
scope: { '__placeholder': value }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof expression === 'string' && el.type === 'radio') {
|
||||
// Radio buttons only work properly when they share a name attribute.
|
||||
// People might assume we take care of that for them, because
|
||||
// they already set a shared "x-model" attribute.
|
||||
mutateDom(() => {
|
||||
if (! el.hasAttribute('name')) el.setAttribute('name', expression)
|
||||
})
|
||||
}
|
||||
|
||||
// If the element we are binding to is a select, a radio, or checkbox
|
||||
// we'll listen for the change event instead of the "input" event.
|
||||
var event = (el.tagName.toLowerCase() === 'select')
|
||||
|| ['checkbox', 'radio'].includes(el.type)
|
||||
|| modifiers.includes('lazy')
|
||||
? 'change' : 'input'
|
||||
|
||||
// We only want to register the event listener when we're not cloning, since the
|
||||
// mutation observer handles initializing the x-model directive already when
|
||||
// the element is inserted into the DOM. Otherwise we register it twice.
|
||||
let removeListener = isCloning ? () => {} : on(el, event, modifiers, (e) => {
|
||||
setValue(getInputValue(el, modifiers, e, getValue()))
|
||||
})
|
||||
|
||||
if (modifiers.includes('fill'))
|
||||
if ([undefined, null, ''].includes(getValue())
|
||||
|| (el.type === 'checkbox' && Array.isArray(getValue()))) {
|
||||
setValue(
|
||||
getInputValue(el, modifiers, { target: el }, getValue())
|
||||
);
|
||||
}
|
||||
// Register the listener removal callback on the element, so that
|
||||
// in addition to the cleanup function, x-modelable may call it.
|
||||
// Also, make this a keyed object if we decide to reintroduce
|
||||
// "named modelables" some time in a future Alpine version.
|
||||
if (! el._x_removeModelListeners) el._x_removeModelListeners = {}
|
||||
el._x_removeModelListeners['default'] = removeListener
|
||||
|
||||
cleanup(() => el._x_removeModelListeners['default']())
|
||||
|
||||
// If the input/select/textarea element is linked to a form
|
||||
// we listen for the reset event on the parent form (the event
|
||||
// does not trigger on the single inputs) and update
|
||||
// on nextTick so the page doesn't end up out of sync
|
||||
if (el.form) {
|
||||
let removeResetListener = on(el.form, 'reset', [], (e) => {
|
||||
nextTick(() => el._x_model && el._x_model.set(el.value))
|
||||
})
|
||||
cleanup(() => removeResetListener())
|
||||
}
|
||||
|
||||
// Allow programmatic overriding of x-model.
|
||||
el._x_model = {
|
||||
get() {
|
||||
return getValue()
|
||||
},
|
||||
set(value) {
|
||||
setValue(value)
|
||||
},
|
||||
}
|
||||
|
||||
el._x_forceModelUpdate = (value) => {
|
||||
// If nested model key is undefined, set the default value to empty string.
|
||||
if (value === undefined && typeof expression === 'string' && expression.match(/\./)) value = ''
|
||||
|
||||
// @todo: This is nasty
|
||||
window.fromModel = true
|
||||
mutateDom(() => bind(el, 'value', value))
|
||||
delete window.fromModel
|
||||
}
|
||||
|
||||
effect(() => {
|
||||
// We need to make sure we're always "getting" the value up front,
|
||||
// so that we don't run into a situation where because of the early
|
||||
// the reactive value isn't gotten and therefore disables future reactions.
|
||||
let value = getValue()
|
||||
|
||||
// Don't modify the value of the input if it's focused.
|
||||
if (modifiers.includes('unintrusive') && document.activeElement.isSameNode(el)) return
|
||||
|
||||
el._x_forceModelUpdate(value)
|
||||
})
|
||||
})
|
||||
|
||||
function getInputValue(el, modifiers, event, currentValue) {
|
||||
return mutateDom(() => {
|
||||
// Check for event.detail due to an issue where IE11 handles other events as a CustomEvent.
|
||||
// Safari autofill triggers event as CustomEvent and assigns value to target
|
||||
// so we return event.target.value instead of event.detail
|
||||
if (event instanceof CustomEvent && event.detail !== undefined)
|
||||
return event.detail !== null && event.detail !== undefined ? event.detail : event.target.value
|
||||
else if (el.type === 'checkbox') {
|
||||
// If the data we are binding to is an array, toggle its value inside the array.
|
||||
if (Array.isArray(currentValue)) {
|
||||
let newValue = null;
|
||||
|
||||
if (modifiers.includes('number')) {
|
||||
newValue = safeParseNumber(event.target.value)
|
||||
} else if (modifiers.includes('boolean')) {
|
||||
newValue = safeParseBoolean(event.target.value)
|
||||
} else {
|
||||
newValue = event.target.value
|
||||
}
|
||||
|
||||
return event.target.checked ? currentValue.concat([newValue]) : currentValue.filter(el => ! checkedAttrLooseCompare(el, newValue))
|
||||
} else {
|
||||
return event.target.checked
|
||||
}
|
||||
} else if (el.tagName.toLowerCase() === 'select' && el.multiple) {
|
||||
if (modifiers.includes('number')) {
|
||||
return Array.from(event.target.selectedOptions).map(option => {
|
||||
let rawValue = option.value || option.text
|
||||
return safeParseNumber(rawValue)
|
||||
})
|
||||
} else if (modifiers.includes('boolean')) {
|
||||
return Array.from(event.target.selectedOptions).map(option => {
|
||||
let rawValue = option.value || option.text
|
||||
return safeParseBoolean(rawValue)
|
||||
})
|
||||
}
|
||||
|
||||
return Array.from(event.target.selectedOptions).map(option => {
|
||||
return option.value || option.text
|
||||
})
|
||||
} else {
|
||||
let newValue
|
||||
|
||||
if (el.type === 'radio') {
|
||||
if (event.target.checked) {
|
||||
newValue = event.target.value
|
||||
} else {
|
||||
newValue = currentValue
|
||||
}
|
||||
} else {
|
||||
newValue = event.target.value
|
||||
}
|
||||
|
||||
if (modifiers.includes('number')) {
|
||||
return safeParseNumber(newValue)
|
||||
} else if (modifiers.includes('boolean')) {
|
||||
return safeParseBoolean(newValue)
|
||||
} else if (modifiers.includes('trim')) {
|
||||
return newValue.trim()
|
||||
} else {
|
||||
return newValue
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function safeParseNumber(rawValue) {
|
||||
let number = rawValue ? parseFloat(rawValue) : null
|
||||
|
||||
return isNumeric(number) ? number : rawValue
|
||||
}
|
||||
|
||||
function checkedAttrLooseCompare(valueA, valueB) {
|
||||
return valueA == valueB
|
||||
}
|
||||
|
||||
function isNumeric(subject){
|
||||
return ! Array.isArray(subject) && ! isNaN(subject)
|
||||
}
|
||||
|
||||
function isGetterSetter(value) {
|
||||
return value !== null && typeof value === 'object' && typeof value.get === 'function' && typeof value.set === 'function'
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { directive } from '../directives'
|
||||
import { entangle } from '../entangle';
|
||||
|
||||
directive('modelable', (el, { expression }, { effect, evaluateLater, cleanup }) => {
|
||||
let func = evaluateLater(expression)
|
||||
let innerGet = () => { let result; func(i => result = i); return result; }
|
||||
let evaluateInnerSet = evaluateLater(`${expression} = __placeholder`)
|
||||
let innerSet = val => evaluateInnerSet(() => {}, { scope: { '__placeholder': val }})
|
||||
|
||||
let initialValue = innerGet()
|
||||
|
||||
innerSet(initialValue)
|
||||
|
||||
queueMicrotask(() => {
|
||||
if (! el._x_model) return
|
||||
|
||||
// Remove native event listeners as these are now bound with x-modelable.
|
||||
// The reason for this is that it's often useful to wrap <input> elements
|
||||
// in x-modelable/model, but the input events from the native input
|
||||
// override any functionality added by x-modelable causing confusion.
|
||||
el._x_removeModelListeners['default']()
|
||||
|
||||
let outerGet = el._x_model.get
|
||||
let outerSet = el._x_model.set
|
||||
|
||||
let releaseEntanglement = entangle(
|
||||
{
|
||||
get() { return outerGet() },
|
||||
set(value) { outerSet(value) },
|
||||
},
|
||||
{
|
||||
get() { return innerGet() },
|
||||
set(value) { innerSet(value) },
|
||||
},
|
||||
)
|
||||
|
||||
cleanup(releaseEntanglement)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { directive, into, mapAttributes, prefix, startingWith } from '../directives'
|
||||
import { evaluateLater } from '../evaluator'
|
||||
import { skipDuringClone } from '../clone'
|
||||
import on from '../utils/on'
|
||||
|
||||
mapAttributes(startingWith('@', into(prefix('on:'))))
|
||||
|
||||
directive('on', skipDuringClone((el, { value, modifiers, expression }, { cleanup }) => {
|
||||
let evaluate = expression ? evaluateLater(el, expression) : () => {}
|
||||
|
||||
// Forward event listeners on portals.
|
||||
if (el.tagName.toLowerCase() === 'template') {
|
||||
if (! el._x_forwardEvents) el._x_forwardEvents = []
|
||||
if (! el._x_forwardEvents.includes(value)) el._x_forwardEvents.push(value)
|
||||
}
|
||||
|
||||
let removeListener = on(el, value, modifiers, e => {
|
||||
evaluate(() => {}, { scope: { '$event': e }, params: [e] })
|
||||
})
|
||||
|
||||
cleanup(() => removeListener())
|
||||
}))
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { closestRoot } from '../lifecycle'
|
||||
import { directive } from '../directives'
|
||||
|
||||
function handler () {}
|
||||
|
||||
handler.inline = (el, { expression }, { cleanup }) => {
|
||||
let root = closestRoot(el)
|
||||
|
||||
if (! root._x_refs) root._x_refs = {}
|
||||
|
||||
root._x_refs[expression] = el
|
||||
|
||||
cleanup(() => delete root._x_refs[expression])
|
||||
}
|
||||
|
||||
directive('ref', handler)
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import { evaluateLater } from '../evaluator'
|
||||
import { directive } from '../directives'
|
||||
import { mutateDom } from '../mutation'
|
||||
import { once } from '../utils/once'
|
||||
|
||||
directive('show', (el, { modifiers, expression }, { effect }) => {
|
||||
let evaluate = evaluateLater(el, expression)
|
||||
|
||||
// We're going to set this function on the element directly so that
|
||||
// other plugins like "Collapse" can overwrite them with their own logic.
|
||||
if (! el._x_doHide) el._x_doHide = () => {
|
||||
mutateDom(() => {
|
||||
el.style.setProperty('display', 'none', modifiers.includes('important') ? 'important' : undefined)
|
||||
})
|
||||
}
|
||||
|
||||
if (! el._x_doShow) el._x_doShow = () => {
|
||||
mutateDom(() => {
|
||||
if (el.style.length === 1 && el.style.display === 'none') {
|
||||
el.removeAttribute('style')
|
||||
} else {
|
||||
el.style.removeProperty('display')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let hide = () => {
|
||||
el._x_doHide()
|
||||
el._x_isShown = false
|
||||
}
|
||||
|
||||
let show = () => {
|
||||
el._x_doShow()
|
||||
el._x_isShown = true
|
||||
}
|
||||
|
||||
// We are wrapping this function in a setTimeout here to prevent
|
||||
// a race condition from happening where elements that have a
|
||||
// @click.away always view themselves as shown on the page.
|
||||
let clickAwayCompatibleShow = () => setTimeout(show)
|
||||
|
||||
let toggle = once(
|
||||
value => value ? show() : hide(),
|
||||
value => {
|
||||
if (typeof el._x_toggleAndCascadeWithTransitions === 'function') {
|
||||
el._x_toggleAndCascadeWithTransitions(el, value, show, hide)
|
||||
} else {
|
||||
value ? clickAwayCompatibleShow() : hide()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
let oldValue
|
||||
let firstTime = true
|
||||
|
||||
effect(() => evaluate(value => {
|
||||
// Let's make sure we only call this effect if the value changed.
|
||||
// This prevents "blip" transitions. (1 tick out, then in)
|
||||
if (! firstTime && value === oldValue) return
|
||||
|
||||
if (modifiers.includes('immediate')) value ? clickAwayCompatibleShow() : hide()
|
||||
|
||||
toggle(value)
|
||||
|
||||
oldValue = value
|
||||
firstTime = false
|
||||
}))
|
||||
})
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import { skipDuringClone } from "../clone"
|
||||
import { directive } from "../directives"
|
||||
import { initTree } from "../lifecycle"
|
||||
import { mutateDom } from "../mutation"
|
||||
import { addScopeToNode } from "../scope"
|
||||
import { warn } from "../utils/warn"
|
||||
|
||||
directive('teleport', (el, { modifiers, expression }, { cleanup }) => {
|
||||
if (el.tagName.toLowerCase() !== 'template') warn('x-teleport can only be used on a <template> tag', el)
|
||||
|
||||
let target = getTarget(expression)
|
||||
|
||||
let clone = el.content.cloneNode(true).firstElementChild
|
||||
|
||||
// Add reference to element on <template x-teleport, and visa versa.
|
||||
el._x_teleport = clone
|
||||
clone._x_teleportBack = el
|
||||
|
||||
// Add the key to the DOM so they can be more easily searched for and linked up...
|
||||
el.setAttribute('data-teleport-template', true)
|
||||
clone.setAttribute('data-teleport-target', true)
|
||||
|
||||
// Forward event listeners:
|
||||
if (el._x_forwardEvents) {
|
||||
el._x_forwardEvents.forEach(eventName => {
|
||||
clone.addEventListener(eventName, e => {
|
||||
e.stopPropagation()
|
||||
|
||||
el.dispatchEvent(new e.constructor(e.type, e))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
addScopeToNode(clone, {}, el)
|
||||
|
||||
let placeInDom = (clone, target, modifiers) => {
|
||||
if (modifiers.includes('prepend')) {
|
||||
// insert element before the target
|
||||
target.parentNode.insertBefore(clone, target)
|
||||
} else if (modifiers.includes('append')) {
|
||||
// insert element after the target
|
||||
target.parentNode.insertBefore(clone, target.nextSibling)
|
||||
} else {
|
||||
// origin
|
||||
target.appendChild(clone)
|
||||
}
|
||||
}
|
||||
|
||||
mutateDom(() => {
|
||||
placeInDom(clone, target, modifiers)
|
||||
|
||||
initTree(clone)
|
||||
|
||||
clone._x_ignore = true
|
||||
})
|
||||
|
||||
el._x_teleportPutBack = () => {
|
||||
let target = getTarget(expression)
|
||||
|
||||
mutateDom(() => {
|
||||
placeInDom(el._x_teleport, target, modifiers)
|
||||
})
|
||||
}
|
||||
|
||||
cleanup(() => clone.remove())
|
||||
})
|
||||
|
||||
let teleportContainerDuringClone = document.createElement('div')
|
||||
|
||||
function getTarget(expression) {
|
||||
let target = skipDuringClone(() => {
|
||||
return document.querySelector(expression)
|
||||
}, () => {
|
||||
return teleportContainerDuringClone
|
||||
})()
|
||||
|
||||
if (! target) warn(`Cannot find x-teleport element for selector: "${expression}"`)
|
||||
|
||||
return target
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { directive } from '../directives'
|
||||
import { mutateDom } from '../mutation'
|
||||
|
||||
directive('text', (el, { expression }, { effect, evaluateLater }) => {
|
||||
let evaluate = evaluateLater(expression)
|
||||
|
||||
effect(() => {
|
||||
evaluate(value => {
|
||||
mutateDom(() => {
|
||||
el.textContent = value
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
import { releaseNextTicks, holdNextTicks } from '../nextTick'
|
||||
import { setClasses } from '../utils/classes'
|
||||
import { setStyles } from '../utils/styles'
|
||||
import { directive } from '../directives'
|
||||
import { mutateDom } from '../mutation'
|
||||
import { once } from '../utils/once'
|
||||
|
||||
directive('transition', (el, { value, modifiers, expression }, { evaluate }) => {
|
||||
if (typeof expression === 'function') expression = evaluate(expression)
|
||||
if (expression === false) return
|
||||
if (!expression || typeof expression === 'boolean') {
|
||||
registerTransitionsFromHelper(el, modifiers, value)
|
||||
} else {
|
||||
registerTransitionsFromClassString(el, expression, value)
|
||||
}
|
||||
})
|
||||
|
||||
function registerTransitionsFromClassString(el, classString, stage) {
|
||||
registerTransitionObject(el, setClasses, '')
|
||||
|
||||
let directiveStorageMap = {
|
||||
'enter': (classes) => { el._x_transition.enter.during = classes },
|
||||
'enter-start': (classes) => { el._x_transition.enter.start = classes },
|
||||
'enter-end': (classes) => { el._x_transition.enter.end = classes },
|
||||
'leave': (classes) => { el._x_transition.leave.during = classes },
|
||||
'leave-start': (classes) => { el._x_transition.leave.start = classes },
|
||||
'leave-end': (classes) => { el._x_transition.leave.end = classes },
|
||||
}
|
||||
|
||||
directiveStorageMap[stage](classString)
|
||||
}
|
||||
|
||||
function registerTransitionsFromHelper(el, modifiers, stage) {
|
||||
registerTransitionObject(el, setStyles)
|
||||
|
||||
let doesntSpecify = (! modifiers.includes('in') && ! modifiers.includes('out')) && ! stage
|
||||
let transitioningIn = doesntSpecify || modifiers.includes('in') || ['enter'].includes(stage)
|
||||
let transitioningOut = doesntSpecify || modifiers.includes('out') || ['leave'].includes(stage)
|
||||
|
||||
if (modifiers.includes('in') && ! doesntSpecify) {
|
||||
modifiers = modifiers.filter((i, index) => index < modifiers.indexOf('out'))
|
||||
}
|
||||
|
||||
if (modifiers.includes('out') && ! doesntSpecify) {
|
||||
modifiers = modifiers.filter((i, index) => index > modifiers.indexOf('out'))
|
||||
}
|
||||
|
||||
let wantsAll = ! modifiers.includes('opacity') && ! modifiers.includes('scale')
|
||||
let wantsOpacity = wantsAll || modifiers.includes('opacity')
|
||||
let wantsScale = wantsAll || modifiers.includes('scale')
|
||||
let opacityValue = wantsOpacity ? 0 : 1
|
||||
let scaleValue = wantsScale ? modifierValue(modifiers, 'scale', 95) / 100 : 1
|
||||
let delay = modifierValue(modifiers, 'delay', 0) / 1000
|
||||
let origin = modifierValue(modifiers, 'origin', 'center')
|
||||
let property = 'opacity, transform'
|
||||
let durationIn = modifierValue(modifiers, 'duration', 150) / 1000
|
||||
let durationOut = modifierValue(modifiers, 'duration', 75) / 1000
|
||||
let easing = `cubic-bezier(0.4, 0.0, 0.2, 1)`
|
||||
|
||||
if (transitioningIn) {
|
||||
el._x_transition.enter.during = {
|
||||
transformOrigin: origin,
|
||||
transitionDelay: `${delay}s`,
|
||||
transitionProperty: property,
|
||||
transitionDuration: `${durationIn}s`,
|
||||
transitionTimingFunction: easing,
|
||||
}
|
||||
|
||||
el._x_transition.enter.start = {
|
||||
opacity: opacityValue,
|
||||
transform: `scale(${scaleValue})`,
|
||||
}
|
||||
|
||||
el._x_transition.enter.end = {
|
||||
opacity: 1,
|
||||
transform: `scale(1)`,
|
||||
}
|
||||
}
|
||||
|
||||
if (transitioningOut) {
|
||||
el._x_transition.leave.during = {
|
||||
transformOrigin: origin,
|
||||
transitionDelay: `${delay}s`,
|
||||
transitionProperty: property,
|
||||
transitionDuration: `${durationOut}s`,
|
||||
transitionTimingFunction: easing,
|
||||
}
|
||||
|
||||
el._x_transition.leave.start = {
|
||||
opacity: 1,
|
||||
transform: `scale(1)`,
|
||||
}
|
||||
|
||||
el._x_transition.leave.end = {
|
||||
opacity: opacityValue,
|
||||
transform: `scale(${scaleValue})`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerTransitionObject(el, setFunction, defaultValue = {}) {
|
||||
if (! el._x_transition) el._x_transition = {
|
||||
enter: { during: defaultValue, start: defaultValue, end: defaultValue },
|
||||
|
||||
leave: { during: defaultValue, start: defaultValue, end: defaultValue },
|
||||
|
||||
in(before = () => {}, after = () => {}) {
|
||||
transition(el, setFunction, {
|
||||
during: this.enter.during,
|
||||
start: this.enter.start,
|
||||
end: this.enter.end,
|
||||
}, before, after)
|
||||
},
|
||||
|
||||
out(before = () => {}, after = () => {}) {
|
||||
transition(el, setFunction, {
|
||||
during: this.leave.during,
|
||||
start: this.leave.start,
|
||||
end: this.leave.end,
|
||||
}, before, after)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
window.Element.prototype._x_toggleAndCascadeWithTransitions = function (el, value, show, hide) {
|
||||
// We are running this function after one tick to prevent
|
||||
// a race condition from happening where elements that have a
|
||||
// @click.away always view themselves as shown on the page.
|
||||
// If the tab is active, we prioritise requestAnimationFrame which plays
|
||||
// nicely with nested animations otherwise we use setTimeout to make sure
|
||||
// it keeps running in background. setTimeout has a lower priority in the
|
||||
// event loop so it would skip nested transitions but when the tab is
|
||||
// hidden, it's not relevant.
|
||||
const nextTick = document.visibilityState === 'visible' ? requestAnimationFrame : setTimeout;
|
||||
let clickAwayCompatibleShow = () => nextTick(show);
|
||||
|
||||
if (value) {
|
||||
if (el._x_transition && (el._x_transition.enter || el._x_transition.leave)) {
|
||||
// This fixes a bug where if you are only transitioning OUT and you are also using @click.outside
|
||||
// the element when shown immediately starts transitioning out. There is a test in the manual
|
||||
// transition test file for this: /tests/cypress/manual-transition-test.html
|
||||
(el._x_transition.enter && (Object.entries(el._x_transition.enter.during).length || Object.entries(el._x_transition.enter.start).length || Object.entries(el._x_transition.enter.end).length))
|
||||
? el._x_transition.in(show)
|
||||
: clickAwayCompatibleShow()
|
||||
} else {
|
||||
el._x_transition
|
||||
? el._x_transition.in(show)
|
||||
: clickAwayCompatibleShow()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Livewire depends on el._x_hidePromise.
|
||||
el._x_hidePromise = el._x_transition
|
||||
? new Promise((resolve, reject) => {
|
||||
el._x_transition.out(() => {}, () => resolve(hide))
|
||||
|
||||
el._x_transitioning && el._x_transitioning.beforeCancel(() => reject({ isFromCancelledTransition: true }))
|
||||
})
|
||||
: Promise.resolve(hide)
|
||||
|
||||
queueMicrotask(() => {
|
||||
let closest = closestHide(el)
|
||||
|
||||
if (closest) {
|
||||
if (! closest._x_hideChildren) closest._x_hideChildren = []
|
||||
|
||||
closest._x_hideChildren.push(el)
|
||||
} else {
|
||||
nextTick(() => {
|
||||
let hideAfterChildren = el => {
|
||||
let carry = Promise.all([
|
||||
el._x_hidePromise,
|
||||
...(el._x_hideChildren || []).map(hideAfterChildren),
|
||||
]).then(([i]) => i())
|
||||
|
||||
delete el._x_hidePromise
|
||||
delete el._x_hideChildren
|
||||
|
||||
return carry
|
||||
}
|
||||
|
||||
hideAfterChildren(el).catch((e) => {
|
||||
if (! e.isFromCancelledTransition) throw e
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function closestHide(el) {
|
||||
let parent = el.parentNode
|
||||
|
||||
if (! parent) return
|
||||
|
||||
return parent._x_hidePromise ? parent : closestHide(parent)
|
||||
}
|
||||
|
||||
export function transition(el, setFunction, { during, start, end } = {}, before = () => {}, after = () => {}) {
|
||||
if (el._x_transitioning) el._x_transitioning.cancel()
|
||||
|
||||
if (Object.keys(during).length === 0 && Object.keys(start).length === 0 && Object.keys(end).length === 0) {
|
||||
// Execute right away if there is no transition.
|
||||
before(); after()
|
||||
return
|
||||
}
|
||||
|
||||
let undoStart, undoDuring, undoEnd
|
||||
|
||||
performTransition(el, {
|
||||
start() {
|
||||
undoStart = setFunction(el, start)
|
||||
},
|
||||
during() {
|
||||
undoDuring = setFunction(el, during)
|
||||
},
|
||||
before,
|
||||
end() {
|
||||
undoStart()
|
||||
|
||||
undoEnd = setFunction(el, end)
|
||||
},
|
||||
after,
|
||||
cleanup() {
|
||||
undoDuring()
|
||||
undoEnd()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function performTransition(el, stages) {
|
||||
// All transitions need to be truly "cancellable". Meaning we need to
|
||||
// account for interruptions at ALL stages of the transitions and
|
||||
// immediately run the rest of the transition.
|
||||
let interrupted, reachedBefore, reachedEnd
|
||||
|
||||
let finish = once(() => {
|
||||
mutateDom(() => {
|
||||
interrupted = true
|
||||
|
||||
if (! reachedBefore) stages.before()
|
||||
|
||||
if (! reachedEnd) {
|
||||
stages.end()
|
||||
|
||||
releaseNextTicks()
|
||||
}
|
||||
|
||||
stages.after()
|
||||
|
||||
// Adding an "isConnected" check, in case the callback removed the element from the DOM.
|
||||
if (el.isConnected) stages.cleanup()
|
||||
|
||||
delete el._x_transitioning
|
||||
})
|
||||
})
|
||||
|
||||
el._x_transitioning = {
|
||||
beforeCancels: [],
|
||||
beforeCancel(callback) { this.beforeCancels.push(callback) },
|
||||
cancel: once(function () { while (this.beforeCancels.length) { this.beforeCancels.shift()() }; finish(); }),
|
||||
finish,
|
||||
}
|
||||
|
||||
mutateDom(() => {
|
||||
stages.start()
|
||||
stages.during()
|
||||
})
|
||||
|
||||
holdNextTicks()
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (interrupted) return
|
||||
|
||||
// Note: Safari's transitionDuration property will list out comma separated transition durations
|
||||
// for every single transition property. Let's grab the first one and call it a day.
|
||||
let duration = Number(getComputedStyle(el).transitionDuration.replace(/,.*/, '').replace('s', '')) * 1000
|
||||
let delay = Number(getComputedStyle(el).transitionDelay.replace(/,.*/, '').replace('s', '')) * 1000
|
||||
|
||||
if (duration === 0) duration = Number(getComputedStyle(el).animationDuration.replace('s', '')) * 1000
|
||||
|
||||
mutateDom(() => {
|
||||
stages.before()
|
||||
})
|
||||
|
||||
reachedBefore = true
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (interrupted) return
|
||||
|
||||
mutateDom(() => {
|
||||
stages.end()
|
||||
})
|
||||
|
||||
releaseNextTicks()
|
||||
|
||||
setTimeout(el._x_transitioning.finish, duration + delay)
|
||||
|
||||
reachedEnd = true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function modifierValue(modifiers, key, fallback) {
|
||||
// If the modifier isn't present, use the default.
|
||||
if (modifiers.indexOf(key) === -1) return fallback
|
||||
|
||||
// If it IS present, grab the value after it: x-show.transition.duration.500ms
|
||||
const rawValue = modifiers[modifiers.indexOf(key) + 1]
|
||||
|
||||
if (! rawValue) return fallback
|
||||
|
||||
if (key === 'scale') {
|
||||
// Check if the very next value is NOT a number and return the fallback.
|
||||
// If x-show.transition.scale, we'll use the default scale value.
|
||||
// That is how a user opts out of the opacity transition.
|
||||
if (isNaN(rawValue)) return fallback
|
||||
}
|
||||
|
||||
if (key === 'duration' || key === 'delay') {
|
||||
// Support x-transition.duration.500ms && duration.500
|
||||
let match = rawValue.match(/([0-9]+)ms/)
|
||||
if (match) return match[1]
|
||||
}
|
||||
|
||||
if (key === 'origin') {
|
||||
// Support chaining origin directions: x-show.transition.top.right
|
||||
if (['top', 'right', 'left', 'center', 'bottom'].includes(modifiers[modifiers.indexOf(key) + 2])) {
|
||||
return [rawValue, modifiers[modifiers.indexOf(key) + 2]].join(' ')
|
||||
}
|
||||
}
|
||||
|
||||
return rawValue
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { effect, release } from './reactivity'
|
||||
|
||||
export function entangle({ get: outerGet, set: outerSet }, { get: innerGet, set: innerSet }) {
|
||||
let firstRun = true
|
||||
let outerHash
|
||||
let innerHash
|
||||
|
||||
let reference = effect(() => {
|
||||
let outer = outerGet()
|
||||
let inner = innerGet()
|
||||
|
||||
if (firstRun) {
|
||||
innerSet(cloneIfObject(outer))
|
||||
firstRun = false
|
||||
} else {
|
||||
let outerHashLatest = JSON.stringify(outer)
|
||||
let innerHashLatest = JSON.stringify(inner)
|
||||
|
||||
if (outerHashLatest !== outerHash) { // If outer changed...
|
||||
innerSet(cloneIfObject(outer))
|
||||
} else if (outerHashLatest !== innerHashLatest) { // If inner changed...
|
||||
outerSet(cloneIfObject(inner))
|
||||
} else { // If nothing changed...
|
||||
// Prevent an infinite loop...
|
||||
}
|
||||
}
|
||||
|
||||
outerHash = JSON.stringify(outerGet())
|
||||
innerHash = JSON.stringify(innerGet())
|
||||
})
|
||||
|
||||
return () => {
|
||||
release(reference)
|
||||
}
|
||||
}
|
||||
|
||||
function cloneIfObject(value) {
|
||||
return typeof value === 'object'
|
||||
? JSON.parse(JSON.stringify(value))
|
||||
: value
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
import { closestDataStack, mergeProxies } from './scope'
|
||||
import { injectMagics } from './magics'
|
||||
import { tryCatch, handleError } from './utils/error'
|
||||
import { onAttributeRemoved } from './mutation'
|
||||
|
||||
let shouldAutoEvaluateFunctions = true
|
||||
|
||||
export function dontAutoEvaluateFunctions(callback) {
|
||||
let cache = shouldAutoEvaluateFunctions
|
||||
|
||||
shouldAutoEvaluateFunctions = false
|
||||
|
||||
let result = callback()
|
||||
|
||||
shouldAutoEvaluateFunctions = cache
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function evaluate(el, expression, extras = {}) {
|
||||
let result
|
||||
|
||||
evaluateLater(el, expression)(value => result = value, extras)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function evaluateLater(...args) {
|
||||
return theEvaluatorFunction(...args)
|
||||
}
|
||||
|
||||
let theEvaluatorFunction = normalEvaluator
|
||||
|
||||
export function setEvaluator(newEvaluator) {
|
||||
theEvaluatorFunction = newEvaluator
|
||||
}
|
||||
|
||||
export function normalEvaluator(el, expression) {
|
||||
let overriddenMagics = {}
|
||||
|
||||
let cleanup = injectMagics(overriddenMagics, el).cleanup
|
||||
|
||||
// MemLeak1: Issue #2140 (note: there are other uses of injectMagics)
|
||||
onAttributeRemoved(el, "evaluator", cleanup)
|
||||
|
||||
let dataStack = [overriddenMagics, ...closestDataStack(el)]
|
||||
|
||||
let evaluator = (typeof expression === 'function')
|
||||
? generateEvaluatorFromFunction(dataStack, expression)
|
||||
: generateEvaluatorFromString(dataStack, expression, el)
|
||||
|
||||
return tryCatch.bind(null, el, expression, evaluator)
|
||||
}
|
||||
|
||||
export function generateEvaluatorFromFunction(dataStack, func) {
|
||||
return (receiver = () => {}, { scope = {}, params = [] } = {}) => {
|
||||
let result = func.apply(mergeProxies([scope, ...dataStack]), params)
|
||||
|
||||
runIfTypeOfFunction(receiver, result)
|
||||
}
|
||||
}
|
||||
|
||||
let evaluatorMemo = {}
|
||||
|
||||
function generateFunctionFromString(expression, el) {
|
||||
if (evaluatorMemo[expression]) {
|
||||
return evaluatorMemo[expression]
|
||||
}
|
||||
|
||||
let AsyncFunction = Object.getPrototypeOf(async function(){}).constructor
|
||||
|
||||
// Some expressions that are useful in Alpine are not valid as the right side of an expression.
|
||||
// Here we'll detect if the expression isn't valid for an assignment and wrap it in a self-
|
||||
// calling function so that we don't throw an error AND a "return" statement can b e used.
|
||||
let rightSideSafeExpression = 0
|
||||
// Support expressions starting with "if" statements like: "if (...) doSomething()"
|
||||
|| /^[\n\s]*if.*\(.*\)/.test(expression.trim())
|
||||
// Support expressions starting with "let/const" like: "let foo = 'bar'"
|
||||
|| /^(let|const)\s/.test(expression.trim())
|
||||
? `(async()=>{ ${expression} })()`
|
||||
: expression
|
||||
|
||||
const safeAsyncFunction = () => {
|
||||
try {
|
||||
let func = new AsyncFunction(
|
||||
["__self", "scope"],
|
||||
`with (scope) { __self.result = ${rightSideSafeExpression} }; __self.finished = true; return __self.result;`
|
||||
)
|
||||
|
||||
Object.defineProperty(func, "name", {
|
||||
value: `[Alpine] ${expression}`,
|
||||
})
|
||||
|
||||
return func
|
||||
} catch ( error ) {
|
||||
handleError( error, el, expression )
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
let func = safeAsyncFunction()
|
||||
|
||||
evaluatorMemo[expression] = func
|
||||
|
||||
return func
|
||||
}
|
||||
|
||||
function generateEvaluatorFromString(dataStack, expression, el) {
|
||||
let func = generateFunctionFromString(expression, el)
|
||||
|
||||
return (receiver = () => {}, { scope = {}, params = [] } = {}) => {
|
||||
func.result = undefined
|
||||
func.finished = false
|
||||
|
||||
// Run the function.
|
||||
|
||||
let completeScope = mergeProxies([ scope, ...dataStack ])
|
||||
|
||||
if (typeof func === 'function' ) {
|
||||
let promise = func(func, completeScope).catch((error) => handleError(error, el, expression))
|
||||
|
||||
// Check if the function ran synchronously,
|
||||
if (func.finished) {
|
||||
// Return the immediate result.
|
||||
runIfTypeOfFunction(receiver, func.result, completeScope, params, el)
|
||||
// Once the function has run, we clear func.result so we don't create
|
||||
// memory leaks. func is stored in the evaluatorMemo and every time
|
||||
// it runs, it assigns the evaluated expression to result which could
|
||||
// potentially store a reference to the DOM element that will be removed later on.
|
||||
func.result = undefined
|
||||
} else {
|
||||
// If not, return the result when the promise resolves.
|
||||
promise.then(result => {
|
||||
runIfTypeOfFunction(receiver, result, completeScope, params, el)
|
||||
}).catch( error => handleError( error, el, expression ) )
|
||||
.finally( () => func.result = undefined )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function runIfTypeOfFunction(receiver, value, scope, params, el) {
|
||||
if (shouldAutoEvaluateFunctions && typeof value === 'function') {
|
||||
let result = value.apply(scope, params)
|
||||
|
||||
if (result instanceof Promise) {
|
||||
result.then(i => runIfTypeOfFunction(receiver, i, scope, params)).catch( error => handleError( error, el, value ) )
|
||||
} else {
|
||||
receiver(result)
|
||||
}
|
||||
} else if (typeof value === 'object' && value instanceof Promise) {
|
||||
value.then(i => receiver(i))
|
||||
} else {
|
||||
receiver(value)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { findClosest } from './lifecycle'
|
||||
|
||||
let globalIdMemo = {}
|
||||
|
||||
export function findAndIncrementId(name) {
|
||||
if (! globalIdMemo[name]) globalIdMemo[name] = 0
|
||||
|
||||
return ++globalIdMemo[name]
|
||||
}
|
||||
|
||||
export function closestIdRoot(el, name) {
|
||||
return findClosest(el, element => {
|
||||
if (element._x_ids && element._x_ids[name]) return true
|
||||
})
|
||||
}
|
||||
|
||||
export function setIdRoot(el, name) {
|
||||
if (! el._x_ids) el._x_ids = {}
|
||||
if (! el._x_ids[name]) el._x_ids[name] = findAndIncrementId(name)
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* _
|
||||
* /\ | | (_) (_)
|
||||
* / \ | |_ __ _ _ __ ___ _ ___
|
||||
* / /\ \ | | '_ \| | '_ \ / _ \ | / __|
|
||||
* / ____ \| | |_) | | | | | __/_| \__ \
|
||||
* /_/ \_\_| .__/|_|_| |_|\___(_) |___/
|
||||
* | | _/ |
|
||||
* |_| |__/
|
||||
*
|
||||
* Let's build Alpine together. It's easier than you think.
|
||||
* For starters, we'll import Alpine's core. This is the
|
||||
* object that will expose all of Alpine's public API.
|
||||
*/
|
||||
import Alpine from './alpine'
|
||||
|
||||
/**
|
||||
* _______________________________________________________
|
||||
* The Evaluator
|
||||
* -------------------------------------------------------
|
||||
*
|
||||
* Now we're ready to bootstrap Alpine's evaluation system.
|
||||
* It's the function that converts raw JavaScript string
|
||||
* expressions like @click="toggle()", into actual JS.
|
||||
*/
|
||||
import { normalEvaluator } from './evaluator'
|
||||
|
||||
Alpine.setEvaluator(normalEvaluator)
|
||||
|
||||
/**
|
||||
* _______________________________________________________
|
||||
* The Reactivity Engine
|
||||
* -------------------------------------------------------
|
||||
*
|
||||
* This is the reactivity core of Alpine. It's the part of
|
||||
* Alpine that triggers an element with x-text="message"
|
||||
* to update its inner text when "message" is changed.
|
||||
*/
|
||||
import { reactive, effect, stop, toRaw } from '@vue/reactivity'
|
||||
|
||||
Alpine.setReactivityEngine({ reactive, effect, release: stop, raw: toRaw })
|
||||
|
||||
/**
|
||||
* _______________________________________________________
|
||||
* The Magics
|
||||
* -------------------------------------------------------
|
||||
*
|
||||
* Yeah, we're calling them magics here like they're nouns.
|
||||
* These are the properties that are magically available
|
||||
* to all the Alpine expressions, within your web app.
|
||||
*/
|
||||
import './magics/index'
|
||||
|
||||
/**
|
||||
* _______________________________________________________
|
||||
* The Directives
|
||||
* -------------------------------------------------------
|
||||
*
|
||||
* Now that the core is all set up, we can register Alpine
|
||||
* directives like x-text or x-on that form the basis of
|
||||
* how Alpine adds behavior to an app's static markup.
|
||||
*/
|
||||
import './directives/index'
|
||||
|
||||
/**
|
||||
* _______________________________________________________
|
||||
* The Alpine Global
|
||||
* -------------------------------------------------------
|
||||
*
|
||||
* Now that we have set everything up internally, anything
|
||||
* Alpine-related that will need to be accessed on-going
|
||||
* will be made available through the "Alpine" global.
|
||||
*/
|
||||
export default Alpine
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
// Warning: The concept of "interceptors" in Alpine is not public API and is subject to change
|
||||
// without tagging a major release.
|
||||
|
||||
export function initInterceptors(data) {
|
||||
let isObject = val => typeof val === 'object' && !Array.isArray(val) && val !== null
|
||||
|
||||
let recurse = (obj, basePath = '') => {
|
||||
Object.entries(Object.getOwnPropertyDescriptors(obj)).forEach(([key, { value, enumerable }]) => {
|
||||
// Skip getters.
|
||||
if (enumerable === false || value === undefined) return
|
||||
if (typeof value === 'object' && value !== null && value.__v_skip) return
|
||||
|
||||
let path = basePath === '' ? key : `${basePath}.${key}`
|
||||
|
||||
if (typeof value === 'object' && value !== null && value._x_interceptor) {
|
||||
obj[key] = value.initialize(data, path, key)
|
||||
} else {
|
||||
if (isObject(value) && value !== obj && ! (value instanceof Element)) {
|
||||
recurse(value, path)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return recurse(data)
|
||||
}
|
||||
|
||||
export function interceptor(callback, mutateObj = () => {}) {
|
||||
let obj = {
|
||||
initialValue: undefined,
|
||||
|
||||
_x_interceptor: true,
|
||||
|
||||
initialize(data, path, key) {
|
||||
return callback(this.initialValue, () => get(data, path), (value) => set(data, path, value), path, key)
|
||||
}
|
||||
}
|
||||
|
||||
mutateObj(obj)
|
||||
|
||||
return initialValue => {
|
||||
if (typeof initialValue === 'object' && initialValue !== null && initialValue._x_interceptor) {
|
||||
// Support nesting interceptors.
|
||||
let initialize = obj.initialize.bind(obj)
|
||||
|
||||
obj.initialize = (data, path, key) => {
|
||||
let innerValue = initialValue.initialize(data, path, key)
|
||||
|
||||
obj.initialValue = innerValue
|
||||
|
||||
return initialize(data, path, key)
|
||||
}
|
||||
} else {
|
||||
obj.initialValue = initialValue
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
}
|
||||
|
||||
function get(obj, path) {
|
||||
return path.split('.').reduce((carry, segment) => carry[segment], obj)
|
||||
}
|
||||
|
||||
function set(obj, path, value) {
|
||||
if (typeof path === 'string') path = path.split('.')
|
||||
|
||||
if (path.length === 1) obj[path[0]] = value;
|
||||
else if (path.length === 0) throw error;
|
||||
else {
|
||||
if (obj[path[0]])
|
||||
return set(obj[path[0]], path.slice(1), value);
|
||||
else {
|
||||
obj[path[0]] = {};
|
||||
return set(obj[path[0]], path.slice(1), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
import { startObservingMutations, onAttributesAdded, onElAdded, onElRemoved, cleanupAttributes, cleanupElement } from "./mutation"
|
||||
import { deferHandlingDirectives, directives } from "./directives"
|
||||
import { dispatch } from './utils/dispatch'
|
||||
import { walk } from "./utils/walk"
|
||||
import { warn } from './utils/warn'
|
||||
|
||||
let started = false
|
||||
|
||||
export function start() {
|
||||
if (started) warn('Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems.')
|
||||
|
||||
started = true
|
||||
|
||||
if (! document.body) warn('Unable to initialize. Trying to load Alpine before `<body>` is available. Did you forget to add `defer` in Alpine\'s `<script>` tag?')
|
||||
|
||||
dispatch(document, 'alpine:init')
|
||||
dispatch(document, 'alpine:initializing')
|
||||
|
||||
startObservingMutations()
|
||||
|
||||
onElAdded(el => initTree(el, walk))
|
||||
onElRemoved(el => destroyTree(el))
|
||||
|
||||
onAttributesAdded((el, attrs) => {
|
||||
directives(el, attrs).forEach(handle => handle())
|
||||
})
|
||||
|
||||
let outNestedComponents = el => ! closestRoot(el.parentElement, true)
|
||||
Array.from(document.querySelectorAll(allSelectors().join(',')))
|
||||
.filter(outNestedComponents)
|
||||
.forEach(el => {
|
||||
initTree(el)
|
||||
})
|
||||
|
||||
dispatch(document, 'alpine:initialized')
|
||||
}
|
||||
|
||||
let rootSelectorCallbacks = []
|
||||
let initSelectorCallbacks = []
|
||||
|
||||
export function rootSelectors() {
|
||||
return rootSelectorCallbacks.map(fn => fn())
|
||||
}
|
||||
|
||||
export function allSelectors() {
|
||||
return rootSelectorCallbacks.concat(initSelectorCallbacks).map(fn => fn())
|
||||
}
|
||||
|
||||
export function addRootSelector(selectorCallback) { rootSelectorCallbacks.push(selectorCallback) }
|
||||
export function addInitSelector(selectorCallback) { initSelectorCallbacks.push(selectorCallback) }
|
||||
|
||||
export function closestRoot(el, includeInitSelectors = false) {
|
||||
return findClosest(el, element => {
|
||||
const selectors = includeInitSelectors ? allSelectors() : rootSelectors()
|
||||
|
||||
if (selectors.some(selector => element.matches(selector))) return true
|
||||
})
|
||||
}
|
||||
|
||||
export function findClosest(el, callback) {
|
||||
if (! el) return
|
||||
|
||||
if (callback(el)) return el
|
||||
|
||||
// Support crawling up teleports.
|
||||
if (el._x_teleportBack) el = el._x_teleportBack
|
||||
|
||||
if (! el.parentElement) return
|
||||
|
||||
return findClosest(el.parentElement, callback)
|
||||
}
|
||||
|
||||
export function isRoot(el) {
|
||||
return rootSelectors().some(selector => el.matches(selector))
|
||||
}
|
||||
|
||||
let initInterceptors = []
|
||||
|
||||
export function interceptInit(callback) { initInterceptors.push(callback) }
|
||||
|
||||
export function initTree(el, walker = walk, intercept = () => {}) {
|
||||
deferHandlingDirectives(() => {
|
||||
walker(el, (el, skip) => {
|
||||
intercept(el, skip)
|
||||
|
||||
initInterceptors.forEach(i => i(el, skip))
|
||||
|
||||
directives(el, el.attributes).forEach(handle => handle())
|
||||
|
||||
el._x_ignore && skip()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function destroyTree(root, walker = walk) {
|
||||
walker(root, el => {
|
||||
cleanupAttributes(el)
|
||||
cleanupElement(el)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import { getElementBoundUtilities } from './directives'
|
||||
import { interceptor } from './interceptor'
|
||||
import { onElRemoved } from './mutation'
|
||||
|
||||
let magics = {}
|
||||
|
||||
export function magic(name, callback) {
|
||||
magics[name] = callback
|
||||
}
|
||||
|
||||
export function injectMagics(obj, el) {
|
||||
Object.entries(magics).forEach(([name, callback]) => {
|
||||
let memoizedUtilities = null;
|
||||
function getUtilities() {
|
||||
if (memoizedUtilities) {
|
||||
return memoizedUtilities;
|
||||
} else {
|
||||
let [utilities, cleanup] = getElementBoundUtilities(el)
|
||||
|
||||
memoizedUtilities = {interceptor, ...utilities}
|
||||
|
||||
onElRemoved(el, cleanup)
|
||||
return memoizedUtilities;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(obj, `$${name}`, {
|
||||
get() {
|
||||
return callback(el, getUtilities());
|
||||
},
|
||||
enumerable: false,
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
obj,
|
||||
cleanup: () => {
|
||||
// MemLeak1: Issue #2140
|
||||
el = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { scope } from '../scope'
|
||||
import { magic } from '../magics'
|
||||
|
||||
magic('data', el => scope(el))
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { dispatch } from '../utils/dispatch'
|
||||
import { magic } from '../magics'
|
||||
|
||||
magic('dispatch', el => dispatch.bind(dispatch, el))
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { magic } from "../magics";
|
||||
|
||||
magic('el', el => el)
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { magic } from '../magics'
|
||||
import { closestIdRoot, findAndIncrementId } from '../ids'
|
||||
import { interceptClone } from '../clone'
|
||||
|
||||
magic('id', (el, { cleanup }) => (name, key = null) => {
|
||||
let cacheKey = `${name}${key ? `-${key}` : ''}`
|
||||
|
||||
return cacheIdByNameOnElement(el, cacheKey, cleanup, () => {
|
||||
let root = closestIdRoot(el, name)
|
||||
|
||||
let id = root
|
||||
? root._x_ids[name]
|
||||
: findAndIncrementId(name)
|
||||
|
||||
return key
|
||||
? `${name}-${id}-${key}`
|
||||
: `${name}-${id}`
|
||||
})
|
||||
})
|
||||
|
||||
interceptClone((from, to) => {
|
||||
// Transfer over existing ID registrations from
|
||||
// the existing dom tree over to the new one
|
||||
// so that there aren't ID mismatches...
|
||||
if (from._x_id) {
|
||||
to._x_id = from._x_id
|
||||
}
|
||||
})
|
||||
|
||||
function cacheIdByNameOnElement(el, cacheKey, cleanup, callback)
|
||||
{
|
||||
if (! el._x_id) el._x_id = {}
|
||||
|
||||
// We only want $id to run once per an element's lifecycle...
|
||||
if (el._x_id[cacheKey]) return el._x_id[cacheKey]
|
||||
|
||||
let output = callback()
|
||||
|
||||
el._x_id[cacheKey] = output
|
||||
|
||||
cleanup(() => {
|
||||
delete el._x_id[cacheKey]
|
||||
})
|
||||
|
||||
return output
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { nextTick } from '../nextTick'
|
||||
import { magic } from '../magics'
|
||||
|
||||
magic('nextTick', () => nextTick)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { closestRoot, findClosest } from '../lifecycle'
|
||||
import { mergeProxies } from '../scope'
|
||||
import { magic } from '../magics'
|
||||
|
||||
magic('refs', el => {
|
||||
if (el._x_refs_proxy) return el._x_refs_proxy
|
||||
|
||||
el._x_refs_proxy = mergeProxies(getArrayOfRefObject(el))
|
||||
|
||||
return el._x_refs_proxy
|
||||
})
|
||||
|
||||
function getArrayOfRefObject(el) {
|
||||
let refObjects = []
|
||||
|
||||
findClosest(el, (i) => {
|
||||
if (i._x_refs) refObjects.push(i._x_refs)
|
||||
})
|
||||
|
||||
return refObjects
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { closestRoot } from "../lifecycle";
|
||||
import { magic } from "../magics";
|
||||
|
||||
magic('root', el => closestRoot(el))
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { getStores } from '../store'
|
||||
import { magic } from '../magics'
|
||||
|
||||
magic('store', getStores)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { magic } from '../magics'
|
||||
import { watch } from '../reactivity'
|
||||
|
||||
magic('watch', (el, { evaluateLater, cleanup }) => (key, callback) => {
|
||||
let evaluate = evaluateLater(key)
|
||||
|
||||
let getter = () => {
|
||||
let value
|
||||
|
||||
evaluate(i => value = i)
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
let unwatch = watch(getter, callback)
|
||||
|
||||
cleanup(unwatch)
|
||||
})
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { warn } from '../utils/warn'
|
||||
import { magic } from '../magics'
|
||||
|
||||
import './$nextTick'
|
||||
import './$dispatch'
|
||||
import './$watch'
|
||||
import './$store'
|
||||
import './$data'
|
||||
import './$root'
|
||||
import './$refs'
|
||||
import './$id'
|
||||
import './$el'
|
||||
|
||||
// Register warnings for people using plugin syntaxes and not loading the plugin itself:
|
||||
warnMissingPluginMagic('Focus', 'focus', 'focus')
|
||||
warnMissingPluginMagic('Persist', 'persist', 'persist')
|
||||
|
||||
function warnMissingPluginMagic(name, magicName, slug) {
|
||||
magic(magicName, (el) => warn(`You can't use [$${magicName}] without first installing the "${name}" plugin here: https://alpinejs.dev/plugins/${slug}`, el))
|
||||
}
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
import { destroyTree } from "./lifecycle"
|
||||
|
||||
let onAttributeAddeds = []
|
||||
let onElRemoveds = []
|
||||
let onElAddeds = []
|
||||
|
||||
export function onElAdded(callback) {
|
||||
onElAddeds.push(callback)
|
||||
}
|
||||
|
||||
export function onElRemoved(el, callback) {
|
||||
if (typeof callback === 'function') {
|
||||
if (! el._x_cleanups) el._x_cleanups = []
|
||||
el._x_cleanups.push(callback)
|
||||
} else {
|
||||
callback = el
|
||||
onElRemoveds.push(callback)
|
||||
}
|
||||
}
|
||||
|
||||
export function onAttributesAdded(callback) {
|
||||
onAttributeAddeds.push(callback)
|
||||
}
|
||||
|
||||
export function onAttributeRemoved(el, name, callback) {
|
||||
if (! el._x_attributeCleanups) el._x_attributeCleanups = {}
|
||||
if (! el._x_attributeCleanups[name]) el._x_attributeCleanups[name] = []
|
||||
|
||||
el._x_attributeCleanups[name].push(callback)
|
||||
}
|
||||
|
||||
export function cleanupAttributes(el, names) {
|
||||
if (! el._x_attributeCleanups) return
|
||||
|
||||
Object.entries(el._x_attributeCleanups).forEach(([name, value]) => {
|
||||
if (names === undefined || names.includes(name)) {
|
||||
value.forEach(i => i())
|
||||
|
||||
delete el._x_attributeCleanups[name]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function cleanupElement(el) {
|
||||
if (el._x_cleanups) {
|
||||
while (el._x_cleanups.length) el._x_cleanups.pop()()
|
||||
}
|
||||
}
|
||||
|
||||
let observer = new MutationObserver(onMutate)
|
||||
|
||||
let currentlyObserving = false
|
||||
|
||||
export function startObservingMutations() {
|
||||
observer.observe(document, { subtree: true, childList: true, attributes: true, attributeOldValue: true })
|
||||
|
||||
currentlyObserving = true
|
||||
}
|
||||
|
||||
export function stopObservingMutations() {
|
||||
flushObserver()
|
||||
|
||||
observer.disconnect()
|
||||
|
||||
currentlyObserving = false
|
||||
}
|
||||
|
||||
let queuedMutations = []
|
||||
|
||||
export function flushObserver() {
|
||||
let records = observer.takeRecords()
|
||||
|
||||
queuedMutations.push(() => records.length > 0 && onMutate(records))
|
||||
|
||||
let queueLengthWhenTriggered = queuedMutations.length
|
||||
|
||||
queueMicrotask(() => {
|
||||
// If these two lengths match, then we KNOW that this is the LAST
|
||||
// flush in the current event loop. This way, we can process
|
||||
// all mutations in one batch at the end of everything...
|
||||
if (queuedMutations.length === queueLengthWhenTriggered) {
|
||||
// Now Alpine can process all the mutations...
|
||||
while (queuedMutations.length > 0) queuedMutations.shift()()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function mutateDom(callback) {
|
||||
if (! currentlyObserving) return callback()
|
||||
|
||||
stopObservingMutations()
|
||||
|
||||
let result = callback()
|
||||
|
||||
startObservingMutations()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
let isCollecting = false
|
||||
let deferredMutations = []
|
||||
|
||||
export function deferMutations() {
|
||||
isCollecting = true
|
||||
}
|
||||
|
||||
export function flushAndStopDeferringMutations() {
|
||||
isCollecting = false
|
||||
|
||||
onMutate(deferredMutations)
|
||||
|
||||
deferredMutations = []
|
||||
}
|
||||
|
||||
function onMutate(mutations) {
|
||||
if (isCollecting) {
|
||||
deferredMutations = deferredMutations.concat(mutations)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let addedNodes = new Set
|
||||
let removedNodes = new Set
|
||||
let addedAttributes = new Map
|
||||
let removedAttributes = new Map
|
||||
|
||||
for (let i = 0; i < mutations.length; i++) {
|
||||
if (mutations[i].target._x_ignoreMutationObserver) continue
|
||||
|
||||
if (mutations[i].type === 'childList') {
|
||||
mutations[i].addedNodes.forEach(node => node.nodeType === 1 && addedNodes.add(node))
|
||||
mutations[i].removedNodes.forEach(node => node.nodeType === 1 && removedNodes.add(node))
|
||||
}
|
||||
|
||||
if (mutations[i].type === 'attributes') {
|
||||
let el = mutations[i].target
|
||||
let name = mutations[i].attributeName
|
||||
let oldValue = mutations[i].oldValue
|
||||
|
||||
let add = () => {
|
||||
if (! addedAttributes.has(el)) addedAttributes.set(el, [])
|
||||
|
||||
addedAttributes.get(el).push({ name, value: el.getAttribute(name) })
|
||||
}
|
||||
|
||||
let remove = () => {
|
||||
if (! removedAttributes.has(el)) removedAttributes.set(el, [])
|
||||
|
||||
removedAttributes.get(el).push(name)
|
||||
}
|
||||
|
||||
// New attribute.
|
||||
if (el.hasAttribute(name) && oldValue === null) {
|
||||
add()
|
||||
// Changed attribute.
|
||||
} else if (el.hasAttribute(name)) {
|
||||
remove()
|
||||
add()
|
||||
// Removed attribute.
|
||||
} else {
|
||||
remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removedAttributes.forEach((attrs, el) => {
|
||||
cleanupAttributes(el, attrs)
|
||||
})
|
||||
|
||||
addedAttributes.forEach((attrs, el) => {
|
||||
onAttributeAddeds.forEach(i => i(el, attrs))
|
||||
})
|
||||
|
||||
for (let node of removedNodes) {
|
||||
// If an element gets moved on a page, it's registered
|
||||
// as both an "add" and "remove", so we want to skip those.
|
||||
if (addedNodes.has(node)) continue
|
||||
|
||||
onElRemoveds.forEach(i => i(node))
|
||||
|
||||
destroyTree(node)
|
||||
}
|
||||
|
||||
// Mutations are bundled together by the browser but sometimes
|
||||
// for complex cases, there may be javascript code adding a wrapper
|
||||
// and then an alpine component as a child of that wrapper in the same
|
||||
// function and the mutation observer will receive 2 different mutations.
|
||||
// when it comes time to run them, the dom contains both changes so the child
|
||||
// element would be processed twice as Alpine calls initTree on
|
||||
// both mutations. We mark all nodes as _x_ignored and only remove the flag
|
||||
// when processing the node to avoid those duplicates.
|
||||
addedNodes.forEach((node) => {
|
||||
node._x_ignoreSelf = true
|
||||
node._x_ignore = true
|
||||
})
|
||||
for (let node of addedNodes) {
|
||||
// If the node was eventually removed as part of one of his
|
||||
// parent mutations, skip it
|
||||
if (removedNodes.has(node)) continue
|
||||
if (! node.isConnected) continue
|
||||
|
||||
delete node._x_ignoreSelf
|
||||
delete node._x_ignore
|
||||
onElAddeds.forEach(i => i(node))
|
||||
node._x_ignore = true
|
||||
node._x_ignoreSelf = true
|
||||
}
|
||||
addedNodes.forEach((node) => {
|
||||
delete node._x_ignoreSelf
|
||||
delete node._x_ignore
|
||||
})
|
||||
|
||||
addedNodes = null
|
||||
removedNodes = null
|
||||
addedAttributes = null
|
||||
removedAttributes = null
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
let tickStack = []
|
||||
|
||||
let isHolding = false
|
||||
|
||||
export function nextTick(callback = () => {}) {
|
||||
queueMicrotask(() => {
|
||||
isHolding || setTimeout(() => {
|
||||
releaseNextTicks()
|
||||
})
|
||||
})
|
||||
|
||||
return new Promise((res) => {
|
||||
tickStack.push(() => {
|
||||
callback();
|
||||
res();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export function releaseNextTicks() {
|
||||
isHolding = false
|
||||
|
||||
while (tickStack.length) tickStack.shift()()
|
||||
}
|
||||
|
||||
export function holdNextTicks() {
|
||||
isHolding = true
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import Alpine from "./alpine";
|
||||
|
||||
export function plugin(callback) {
|
||||
let callbacks = Array.isArray(callback) ? callback : [callback]
|
||||
|
||||
callbacks.forEach(i => i(Alpine))
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
|
||||
import { scheduler } from './scheduler'
|
||||
|
||||
let reactive, effect, release, raw
|
||||
|
||||
let shouldSchedule = true
|
||||
export function disableEffectScheduling(callback) {
|
||||
shouldSchedule = false
|
||||
|
||||
callback()
|
||||
|
||||
shouldSchedule = true
|
||||
}
|
||||
|
||||
export function setReactivityEngine(engine) {
|
||||
reactive = engine.reactive
|
||||
release = engine.release
|
||||
effect = (callback) => engine.effect(callback, { scheduler: task => {
|
||||
if (shouldSchedule) {
|
||||
scheduler(task)
|
||||
} else {
|
||||
task()
|
||||
}
|
||||
} })
|
||||
raw = engine.raw
|
||||
}
|
||||
|
||||
export function overrideEffect(override) { effect = override }
|
||||
|
||||
export function elementBoundEffect(el) {
|
||||
let cleanup = () => {}
|
||||
|
||||
let wrappedEffect = (callback) => {
|
||||
let effectReference = effect(callback)
|
||||
|
||||
if (! el._x_effects) {
|
||||
el._x_effects = new Set
|
||||
|
||||
// Livewire depends on el._x_runEffects.
|
||||
el._x_runEffects = () => { el._x_effects.forEach(i => i()) }
|
||||
}
|
||||
|
||||
el._x_effects.add(effectReference)
|
||||
|
||||
cleanup = () => {
|
||||
if (effectReference === undefined) return
|
||||
|
||||
el._x_effects.delete(effectReference)
|
||||
|
||||
release(effectReference)
|
||||
}
|
||||
|
||||
return effectReference
|
||||
}
|
||||
|
||||
return [wrappedEffect, () => { cleanup() }]
|
||||
}
|
||||
|
||||
export function watch(getter, callback) {
|
||||
let firstTime = true
|
||||
|
||||
let oldValue
|
||||
|
||||
let effectReference = effect(() => {
|
||||
let value = getter()
|
||||
|
||||
// JSON.stringify touches every single property at any level enabling deep watching
|
||||
JSON.stringify(value)
|
||||
|
||||
if (! firstTime) {
|
||||
// We have to queue this watcher as a microtask so that
|
||||
// the watcher doesn't pick up its own dependencies.
|
||||
queueMicrotask(() => {
|
||||
callback(value, oldValue)
|
||||
|
||||
oldValue = value
|
||||
})
|
||||
} else {
|
||||
oldValue = value
|
||||
}
|
||||
|
||||
firstTime = false
|
||||
})
|
||||
|
||||
return () => release(effectReference)
|
||||
}
|
||||
|
||||
export {
|
||||
release,
|
||||
reactive,
|
||||
effect,
|
||||
raw,
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
let flushPending = false
|
||||
let flushing = false
|
||||
let queue = []
|
||||
let lastFlushedIndex = -1
|
||||
|
||||
export function scheduler (callback) { queueJob(callback) }
|
||||
|
||||
function queueJob(job) {
|
||||
if (! queue.includes(job)) queue.push(job)
|
||||
|
||||
queueFlush()
|
||||
}
|
||||
export function dequeueJob(job) {
|
||||
let index = queue.indexOf(job)
|
||||
|
||||
if (index !== -1 && index > lastFlushedIndex) queue.splice(index, 1)
|
||||
}
|
||||
|
||||
function queueFlush() {
|
||||
if (! flushing && ! flushPending) {
|
||||
flushPending = true
|
||||
|
||||
queueMicrotask(flushJobs)
|
||||
}
|
||||
}
|
||||
|
||||
export function flushJobs() {
|
||||
flushPending = false
|
||||
flushing = true
|
||||
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
queue[i]()
|
||||
lastFlushedIndex = i
|
||||
}
|
||||
|
||||
queue.length = 0
|
||||
lastFlushedIndex = -1
|
||||
|
||||
flushing = false
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
|
||||
export function scope(node) {
|
||||
return mergeProxies(closestDataStack(node))
|
||||
}
|
||||
|
||||
export function addScopeToNode(node, data, referenceNode) {
|
||||
node._x_dataStack = [data, ...closestDataStack(referenceNode || node)]
|
||||
|
||||
return () => {
|
||||
node._x_dataStack = node._x_dataStack.filter(i => i !== data)
|
||||
}
|
||||
}
|
||||
|
||||
export function hasScope(node) {
|
||||
return !! node._x_dataStack
|
||||
}
|
||||
|
||||
export function closestDataStack(node) {
|
||||
if (node._x_dataStack) return node._x_dataStack
|
||||
|
||||
if (typeof ShadowRoot === 'function' && node instanceof ShadowRoot) {
|
||||
return closestDataStack(node.host)
|
||||
}
|
||||
|
||||
if (! node.parentNode) {
|
||||
return []
|
||||
}
|
||||
|
||||
return closestDataStack(node.parentNode)
|
||||
}
|
||||
|
||||
export function closestDataProxy(el) {
|
||||
return mergeProxies(closestDataStack(el))
|
||||
}
|
||||
|
||||
export function mergeProxies (objects) {
|
||||
return new Proxy({ objects }, mergeProxyTrap);
|
||||
}
|
||||
|
||||
let mergeProxyTrap = {
|
||||
ownKeys({ objects }) {
|
||||
return Array.from(
|
||||
new Set(objects.flatMap((i) => Object.keys(i)))
|
||||
)
|
||||
},
|
||||
|
||||
has({ objects }, name) {
|
||||
if (name == Symbol.unscopables) return false;
|
||||
|
||||
return objects.some((obj) =>
|
||||
Object.prototype.hasOwnProperty.call(obj, name) ||
|
||||
Reflect.has(obj, name)
|
||||
);
|
||||
},
|
||||
|
||||
get({ objects }, name, thisProxy) {
|
||||
if (name == "toJSON") return collapseProxies
|
||||
|
||||
return Reflect.get(
|
||||
objects.find((obj) =>
|
||||
Reflect.has(obj, name)
|
||||
) || {},
|
||||
name,
|
||||
thisProxy
|
||||
)
|
||||
},
|
||||
|
||||
set({ objects }, name, value, thisProxy) {
|
||||
const target =
|
||||
objects.find((obj) =>
|
||||
Object.prototype.hasOwnProperty.call(obj, name)
|
||||
) || objects[objects.length - 1];
|
||||
const descriptor = Object.getOwnPropertyDescriptor(target, name);
|
||||
if (descriptor?.set && descriptor?.get)
|
||||
return Reflect.set(target, name, value, thisProxy);
|
||||
return Reflect.set(target, name, value);
|
||||
},
|
||||
}
|
||||
|
||||
function collapseProxies() {
|
||||
let keys = Reflect.ownKeys(this)
|
||||
|
||||
return keys.reduce((acc, key) => {
|
||||
acc[key] = Reflect.get(this, key)
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { initInterceptors } from "./interceptor";
|
||||
import { reactive } from "./reactivity"
|
||||
|
||||
let stores = {}
|
||||
let isReactive = false
|
||||
|
||||
export function store(name, value) {
|
||||
if (! isReactive) { stores = reactive(stores); isReactive = true; }
|
||||
|
||||
if (value === undefined) {
|
||||
return stores[name]
|
||||
}
|
||||
|
||||
stores[name] = value
|
||||
|
||||
if (typeof value === 'object' && value !== null && value.hasOwnProperty('init') && typeof value.init === 'function') {
|
||||
stores[name].init()
|
||||
}
|
||||
|
||||
initInterceptors(stores[name])
|
||||
}
|
||||
|
||||
export function getStores() { return stores }
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
import { dontAutoEvaluateFunctions, evaluate } from '../evaluator'
|
||||
import { reactive } from '../reactivity'
|
||||
import { setClasses } from './classes'
|
||||
import { setStyles } from './styles'
|
||||
|
||||
export default function bind(el, name, value, modifiers = []) {
|
||||
// Register bound data as pure observable data for other APIs to use.
|
||||
if (! el._x_bindings) el._x_bindings = reactive({})
|
||||
|
||||
el._x_bindings[name] = value
|
||||
|
||||
name = modifiers.includes('camel') ? camelCase(name) : name
|
||||
|
||||
switch (name) {
|
||||
case 'value':
|
||||
bindInputValue(el, value)
|
||||
break;
|
||||
|
||||
case 'style':
|
||||
bindStyles(el, value)
|
||||
break;
|
||||
|
||||
case 'class':
|
||||
bindClasses(el, value)
|
||||
break;
|
||||
|
||||
// 'selected' and 'checked' are special attributes that aren't necessarily
|
||||
// synced with their corresponding properties when updated, so both the
|
||||
// attribute and property need to be updated when bound.
|
||||
case 'selected':
|
||||
case 'checked':
|
||||
bindAttributeAndProperty(el, name, value)
|
||||
break;
|
||||
|
||||
default:
|
||||
bindAttribute(el, name, value)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function bindInputValue(el, value) {
|
||||
if (el.type === 'radio') {
|
||||
// Set radio value from x-bind:value, if no "value" attribute exists.
|
||||
// If there are any initial state values, radio will have a correct
|
||||
// "checked" value since x-bind:value is processed before x-model.
|
||||
if (el.attributes.value === undefined) {
|
||||
el.value = value
|
||||
}
|
||||
|
||||
// @todo: yuck
|
||||
if (window.fromModel) {
|
||||
if (typeof value === 'boolean') {
|
||||
el.checked = safeParseBoolean(el.value) === value
|
||||
} else {
|
||||
el.checked = checkedAttrLooseCompare(el.value, value)
|
||||
}
|
||||
}
|
||||
} else if (el.type === 'checkbox') {
|
||||
// If we are explicitly binding a string to the :value, set the string,
|
||||
// If the value is a boolean/array/number/null/undefined, leave it alone, it will be set to "on"
|
||||
// automatically.
|
||||
if (Number.isInteger(value)) {
|
||||
el.value = value
|
||||
} else if (! Array.isArray(value) && typeof value !== 'boolean' && ! [null, undefined].includes(value)) {
|
||||
el.value = String(value)
|
||||
} else {
|
||||
if (Array.isArray(value)) {
|
||||
el.checked = value.some(val => checkedAttrLooseCompare(val, el.value))
|
||||
} else {
|
||||
el.checked = !!value
|
||||
}
|
||||
}
|
||||
} else if (el.tagName === 'SELECT') {
|
||||
updateSelect(el, value)
|
||||
} else {
|
||||
if (el.value === value) return
|
||||
|
||||
el.value = value === undefined ? '' : value
|
||||
}
|
||||
}
|
||||
|
||||
function bindClasses(el, value) {
|
||||
if (el._x_undoAddedClasses) el._x_undoAddedClasses()
|
||||
|
||||
el._x_undoAddedClasses = setClasses(el, value)
|
||||
}
|
||||
|
||||
function bindStyles(el, value) {
|
||||
if (el._x_undoAddedStyles) el._x_undoAddedStyles()
|
||||
|
||||
el._x_undoAddedStyles = setStyles(el, value)
|
||||
}
|
||||
|
||||
function bindAttributeAndProperty(el, name, value) {
|
||||
bindAttribute(el, name, value)
|
||||
setPropertyIfChanged(el, name, value)
|
||||
}
|
||||
|
||||
function bindAttribute(el, name, value) {
|
||||
if ([null, undefined, false].includes(value) && attributeShouldntBePreservedIfFalsy(name)) {
|
||||
el.removeAttribute(name)
|
||||
} else {
|
||||
if (isBooleanAttr(name)) value = name
|
||||
|
||||
setIfChanged(el, name, value)
|
||||
}
|
||||
}
|
||||
|
||||
function setIfChanged(el, attrName, value) {
|
||||
if (el.getAttribute(attrName) != value) {
|
||||
el.setAttribute(attrName, value)
|
||||
}
|
||||
}
|
||||
|
||||
function setPropertyIfChanged(el, propName, value) {
|
||||
if (el[propName] !== value) {
|
||||
el[propName] = value
|
||||
}
|
||||
}
|
||||
|
||||
function updateSelect(el, value) {
|
||||
const arrayWrappedValue = [].concat(value).map(value => { return value + '' })
|
||||
|
||||
Array.from(el.options).forEach(option => {
|
||||
option.selected = arrayWrappedValue.includes(option.value)
|
||||
})
|
||||
}
|
||||
|
||||
function camelCase(subject) {
|
||||
return subject.toLowerCase().replace(/-(\w)/g, (match, char) => char.toUpperCase())
|
||||
}
|
||||
|
||||
function checkedAttrLooseCompare(valueA, valueB) {
|
||||
return valueA == valueB
|
||||
}
|
||||
|
||||
export function safeParseBoolean(rawValue) {
|
||||
if ([1, '1', 'true', 'on', 'yes', true].includes(rawValue)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if ([0, '0', 'false', 'off', 'no', false].includes(rawValue)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return rawValue ? Boolean(rawValue) : null
|
||||
}
|
||||
|
||||
function isBooleanAttr(attrName) {
|
||||
// As per HTML spec table https://html.spec.whatwg.org/multipage/indices.html#attributes-3:boolean-attribute
|
||||
// Array roughly ordered by estimated usage
|
||||
const booleanAttributes = [
|
||||
'disabled','checked','required','readonly','open', 'selected',
|
||||
'autofocus', 'itemscope', 'multiple', 'novalidate','allowfullscreen',
|
||||
'allowpaymentrequest', 'formnovalidate', 'autoplay', 'controls', 'loop',
|
||||
'muted', 'playsinline', 'default', 'ismap', 'reversed', 'async', 'defer',
|
||||
'nomodule'
|
||||
]
|
||||
|
||||
return booleanAttributes.includes(attrName)
|
||||
}
|
||||
|
||||
function attributeShouldntBePreservedIfFalsy(name) {
|
||||
return ! ['aria-pressed', 'aria-checked', 'aria-expanded', 'aria-selected'].includes(name)
|
||||
}
|
||||
|
||||
export function getBinding(el, name, fallback) {
|
||||
// First let's get it out of Alpine bound data.
|
||||
if (el._x_bindings && el._x_bindings[name] !== undefined) return el._x_bindings[name]
|
||||
|
||||
return getAttributeBinding(el, name, fallback)
|
||||
}
|
||||
|
||||
export function extractProp(el, name, fallback, extract = true) {
|
||||
// First let's get it out of Alpine bound data.
|
||||
if (el._x_bindings && el._x_bindings[name] !== undefined) return el._x_bindings[name]
|
||||
|
||||
if (el._x_inlineBindings && el._x_inlineBindings[name] !== undefined) {
|
||||
let binding = el._x_inlineBindings[name]
|
||||
|
||||
binding.extract = extract
|
||||
|
||||
return dontAutoEvaluateFunctions(() => {
|
||||
return evaluate(el, binding.expression)
|
||||
})
|
||||
}
|
||||
|
||||
return getAttributeBinding(el, name, fallback)
|
||||
}
|
||||
|
||||
function getAttributeBinding(el, name, fallback) {
|
||||
// If not, we'll return the literal attribute.
|
||||
let attr = el.getAttribute(name)
|
||||
|
||||
// Nothing bound:
|
||||
if (attr === null) return typeof fallback === 'function' ? fallback() : fallback
|
||||
|
||||
// The case of a custom attribute with no value. Ex: <div manual>
|
||||
if (attr === '') return true
|
||||
|
||||
if (isBooleanAttr(name)) {
|
||||
return !! [name, 'true'].includes(attr)
|
||||
}
|
||||
|
||||
return attr
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
|
||||
export function setClasses(el, value) {
|
||||
if (Array.isArray(value)) {
|
||||
return setClassesFromString(el, value.join(' '))
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
return setClassesFromObject(el, value)
|
||||
} else if (typeof value === 'function') {
|
||||
return setClasses(el, value())
|
||||
}
|
||||
|
||||
return setClassesFromString(el, value)
|
||||
}
|
||||
|
||||
function setClassesFromString(el, classString) {
|
||||
let split = classString => classString.split(' ').filter(Boolean)
|
||||
|
||||
let missingClasses = classString => classString.split(' ').filter(i => ! el.classList.contains(i)).filter(Boolean)
|
||||
|
||||
let addClassesAndReturnUndo = classes => {
|
||||
el.classList.add(...classes)
|
||||
|
||||
return () => { el.classList.remove(...classes) }
|
||||
}
|
||||
|
||||
// This is to allow short-circuit expressions like: :class="show || 'hidden'" && "show && 'block'"
|
||||
classString = (classString === true) ? classString = '' : (classString || '')
|
||||
|
||||
return addClassesAndReturnUndo(missingClasses(classString))
|
||||
}
|
||||
|
||||
function setClassesFromObject(el, classObject) {
|
||||
let split = classString => classString.split(' ').filter(Boolean)
|
||||
|
||||
let forAdd = Object.entries(classObject).flatMap(([classString, bool]) => bool ? split(classString) : false).filter(Boolean)
|
||||
let forRemove = Object.entries(classObject).flatMap(([classString, bool]) => ! bool ? split(classString) : false).filter(Boolean)
|
||||
|
||||
let added = []
|
||||
let removed = []
|
||||
|
||||
forRemove.forEach(i => {
|
||||
if (el.classList.contains(i)) {
|
||||
el.classList.remove(i)
|
||||
removed.push(i)
|
||||
}
|
||||
})
|
||||
|
||||
forAdd.forEach(i => {
|
||||
if (! el.classList.contains(i)) {
|
||||
el.classList.add(i)
|
||||
added.push(i)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
removed.forEach(i => el.classList.add(i))
|
||||
added.forEach(i => el.classList.remove(i))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
export function debounce(func, wait) {
|
||||
var timeout
|
||||
|
||||
return function() {
|
||||
var context = this, args = arguments
|
||||
|
||||
var later = function () {
|
||||
timeout = null
|
||||
|
||||
func.apply(context, args)
|
||||
}
|
||||
|
||||
clearTimeout(timeout)
|
||||
|
||||
timeout = setTimeout(later, wait)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
export function dispatch(el, name, detail = {}) {
|
||||
el.dispatchEvent(
|
||||
new CustomEvent(name, {
|
||||
detail,
|
||||
bubbles: true,
|
||||
// Allows events to pass the shadow DOM barrier.
|
||||
composed: true,
|
||||
cancelable: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
export function tryCatch(el, expression, callback, ...args) {
|
||||
try {
|
||||
return callback(...args)
|
||||
} catch (e) {
|
||||
handleError( e, el, expression )
|
||||
}
|
||||
}
|
||||
|
||||
export function handleError(error , el, expression = undefined) {
|
||||
error = Object.assign(
|
||||
error ?? { message: 'No error message given.' },
|
||||
{ el, expression } )
|
||||
|
||||
console.warn(`Alpine Expression Error: ${error.message}\n\n${ expression ? 'Expression: \"' + expression + '\"\n\n' : '' }`, el)
|
||||
|
||||
setTimeout( () => { throw error }, 0 )
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
import { debounce } from './debounce'
|
||||
import { throttle } from './throttle'
|
||||
|
||||
export default function on (el, event, modifiers, callback) {
|
||||
let listenerTarget = el
|
||||
|
||||
let handler = e => callback(e)
|
||||
|
||||
let options = {}
|
||||
|
||||
// This little helper allows us to add functionality to the listener's
|
||||
// handler more flexibly in a "middleware" style.
|
||||
let wrapHandler = (callback, wrapper) => (e) => wrapper(callback, e)
|
||||
|
||||
if (modifiers.includes("dot")) event = dotSyntax(event)
|
||||
if (modifiers.includes('camel')) event = camelCase(event)
|
||||
if (modifiers.includes('passive')) options.passive = true
|
||||
if (modifiers.includes('capture')) options.capture = true
|
||||
if (modifiers.includes('window')) listenerTarget = window
|
||||
if (modifiers.includes('document')) listenerTarget = document
|
||||
|
||||
// By wrapping the handler with debounce & throttle first, we ensure that the wrapping logic itself is not
|
||||
// throttled/debounced, only the user's callback is. This way, if the user expects
|
||||
// `e.preventDefault()` to happen, it'll still happen even if their callback gets throttled.
|
||||
if (modifiers.includes('debounce')) {
|
||||
let nextModifier = modifiers[modifiers.indexOf('debounce')+1] || 'invalid-wait'
|
||||
let wait = isNumeric(nextModifier.split('ms')[0]) ? Number(nextModifier.split('ms')[0]) : 250
|
||||
|
||||
handler = debounce(handler, wait)
|
||||
}
|
||||
if (modifiers.includes('throttle')) {
|
||||
let nextModifier = modifiers[modifiers.indexOf('throttle')+1] || 'invalid-wait'
|
||||
let wait = isNumeric(nextModifier.split('ms')[0]) ? Number(nextModifier.split('ms')[0]) : 250
|
||||
|
||||
handler = throttle(handler, wait)
|
||||
}
|
||||
|
||||
if (modifiers.includes('prevent')) handler = wrapHandler(handler, (next, e) => { e.preventDefault(); next(e) })
|
||||
if (modifiers.includes('stop')) handler = wrapHandler(handler, (next, e) => { e.stopPropagation(); next(e) })
|
||||
if (modifiers.includes('self')) handler = wrapHandler(handler, (next, e) => { e.target === el && next(e) })
|
||||
|
||||
if (modifiers.includes('away') || modifiers.includes('outside')) {
|
||||
listenerTarget = document
|
||||
|
||||
handler = wrapHandler(handler, (next, e) => {
|
||||
if (el.contains(e.target)) return
|
||||
|
||||
if (e.target.isConnected === false) return
|
||||
|
||||
if (el.offsetWidth < 1 && el.offsetHeight < 1) return
|
||||
|
||||
// Additional check for special implementations like x-collapse
|
||||
// where the element doesn't have display: none
|
||||
if (el._x_isShown === false) return
|
||||
|
||||
next(e)
|
||||
})
|
||||
}
|
||||
|
||||
if (modifiers.includes('once')) {
|
||||
handler = wrapHandler(handler, (next, e) => {
|
||||
next(e)
|
||||
|
||||
listenerTarget.removeEventListener(event, handler, options)
|
||||
})
|
||||
}
|
||||
|
||||
// Handle :keydown and :keyup listeners.
|
||||
handler = wrapHandler(handler, (next, e) => {
|
||||
if (isKeyEvent(event)) {
|
||||
if (isListeningForASpecificKeyThatHasntBeenPressed(e, modifiers)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
next(e)
|
||||
})
|
||||
|
||||
listenerTarget.addEventListener(event, handler, options)
|
||||
|
||||
return () => {
|
||||
listenerTarget.removeEventListener(event, handler, options)
|
||||
}
|
||||
}
|
||||
|
||||
function dotSyntax(subject) {
|
||||
return subject.replace(/-/g, ".")
|
||||
}
|
||||
|
||||
function camelCase(subject) {
|
||||
return subject.toLowerCase().replace(/-(\w)/g, (match, char) => char.toUpperCase())
|
||||
}
|
||||
|
||||
function isNumeric(subject){
|
||||
return ! Array.isArray(subject) && ! isNaN(subject)
|
||||
}
|
||||
|
||||
function kebabCase(subject) {
|
||||
if ([' ','_'].includes(subject
|
||||
)) return subject
|
||||
return subject.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[_\s]/, '-').toLowerCase()
|
||||
}
|
||||
|
||||
function isKeyEvent(event) {
|
||||
return ['keydown', 'keyup'].includes(event)
|
||||
}
|
||||
|
||||
function isListeningForASpecificKeyThatHasntBeenPressed(e, modifiers) {
|
||||
let keyModifiers = modifiers.filter(i => {
|
||||
return ! ['window', 'document', 'prevent', 'stop', 'once', 'capture'].includes(i)
|
||||
})
|
||||
|
||||
if (keyModifiers.includes('debounce')) {
|
||||
let debounceIndex = keyModifiers.indexOf('debounce')
|
||||
keyModifiers.splice(debounceIndex, isNumeric((keyModifiers[debounceIndex+1] || 'invalid-wait').split('ms')[0]) ? 2 : 1)
|
||||
}
|
||||
|
||||
if (keyModifiers.includes('throttle')) {
|
||||
let debounceIndex = keyModifiers.indexOf('throttle')
|
||||
keyModifiers.splice(debounceIndex, isNumeric((keyModifiers[debounceIndex+1] || 'invalid-wait').split('ms')[0]) ? 2 : 1)
|
||||
}
|
||||
|
||||
// If no modifier is specified, we'll call it a press.
|
||||
if (keyModifiers.length === 0) return false
|
||||
|
||||
// If one is passed, AND it matches the key pressed, we'll call it a press.
|
||||
if (keyModifiers.length === 1 && keyToModifiers(e.key).includes(keyModifiers[0])) return false
|
||||
|
||||
// The user is listening for key combinations.
|
||||
const systemKeyModifiers = ['ctrl', 'shift', 'alt', 'meta', 'cmd', 'super']
|
||||
const selectedSystemKeyModifiers = systemKeyModifiers.filter(modifier => keyModifiers.includes(modifier))
|
||||
|
||||
keyModifiers = keyModifiers.filter(i => ! selectedSystemKeyModifiers.includes(i))
|
||||
|
||||
if (selectedSystemKeyModifiers.length > 0) {
|
||||
const activelyPressedKeyModifiers = selectedSystemKeyModifiers.filter(modifier => {
|
||||
// Alias "cmd" and "super" to "meta"
|
||||
if (modifier === 'cmd' || modifier === 'super') modifier = 'meta'
|
||||
|
||||
return e[`${modifier}Key`]
|
||||
})
|
||||
|
||||
// If all the modifiers selected are pressed, ...
|
||||
if (activelyPressedKeyModifiers.length === selectedSystemKeyModifiers.length) {
|
||||
// AND the remaining key is pressed as well. It's a press.
|
||||
if (keyToModifiers(e.key).includes(keyModifiers[0])) return false
|
||||
}
|
||||
}
|
||||
|
||||
// We'll call it NOT a valid keypress.
|
||||
return true
|
||||
}
|
||||
|
||||
function keyToModifiers(key) {
|
||||
if (! key) return []
|
||||
|
||||
key = kebabCase(key)
|
||||
|
||||
let modifierToKeyMap = {
|
||||
'ctrl': 'control',
|
||||
'slash': '/',
|
||||
'space': ' ',
|
||||
'spacebar': ' ',
|
||||
'cmd': 'meta',
|
||||
'esc': 'escape',
|
||||
'up': 'arrow-up',
|
||||
'down': 'arrow-down',
|
||||
'left': 'arrow-left',
|
||||
'right': 'arrow-right',
|
||||
'period': '.',
|
||||
'equal': '=',
|
||||
'minus': '-',
|
||||
'underscore': '_',
|
||||
}
|
||||
|
||||
modifierToKeyMap[key] = key
|
||||
|
||||
return Object.keys(modifierToKeyMap).map(modifier => {
|
||||
if (modifierToKeyMap[modifier] === key) return modifier
|
||||
}).filter(modifier => modifier)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
export function once(callback, fallback = () => {}) {
|
||||
let called = false
|
||||
|
||||
return function () {
|
||||
if (! called) {
|
||||
called = true
|
||||
|
||||
callback.apply(this, arguments)
|
||||
} else {
|
||||
fallback.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
export function setStyles(el, value) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
return setStylesFromObject(el, value)
|
||||
}
|
||||
|
||||
return setStylesFromString(el, value)
|
||||
}
|
||||
|
||||
function setStylesFromObject(el, value) {
|
||||
let previousStyles = {}
|
||||
|
||||
Object.entries(value).forEach(([key, value]) => {
|
||||
previousStyles[key] = el.style[key]
|
||||
|
||||
// When we use javascript object, css properties use the camelCase
|
||||
// syntax but when we use setProperty, we need the css format
|
||||
// so we need to convert camelCase to kebab-case.
|
||||
// In case key is a CSS variable, leave it as it is.
|
||||
if (! key.startsWith('--')) {
|
||||
key = kebabCase(key);
|
||||
}
|
||||
|
||||
el.style.setProperty(key, value)
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (el.style.length === 0) {
|
||||
el.removeAttribute('style')
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
setStyles(el, previousStyles)
|
||||
}
|
||||
}
|
||||
|
||||
function setStylesFromString(el, value) {
|
||||
let cache = el.getAttribute('style', value)
|
||||
|
||||
el.setAttribute('style', value)
|
||||
|
||||
return () => {
|
||||
el.setAttribute('style', cache || '')
|
||||
}
|
||||
}
|
||||
|
||||
function kebabCase(subject) {
|
||||
return subject.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
export function throttle(func, limit) {
|
||||
let inThrottle
|
||||
|
||||
return function() {
|
||||
let context = this, args = arguments
|
||||
|
||||
if (! inThrottle) {
|
||||
func.apply(context, args)
|
||||
|
||||
inThrottle = true
|
||||
|
||||
setTimeout(() => inThrottle = false, limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
export function walk(el, callback) {
|
||||
if (typeof ShadowRoot === 'function' && el instanceof ShadowRoot) {
|
||||
Array.from(el.children).forEach(el => walk(el, callback))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let skip = false
|
||||
|
||||
callback(el, () => skip = true)
|
||||
|
||||
if (skip) return
|
||||
|
||||
let node = el.firstElementChild
|
||||
|
||||
while (node) {
|
||||
walk(node, callback, false)
|
||||
|
||||
node = node.nextElementSibling
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
export function warn(message, ...args) {
|
||||
console.warn(`Alpine Warning: ${message}`, ...args)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import anchor from '../src/index.js'
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.Alpine.plugin(anchor)
|
||||
})
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import anchor from '../src/index.js'
|
||||
|
||||
export default anchor
|
||||
|
||||
export { anchor }
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "@alpinejs/anchor",
|
||||
"version": "3.13.8",
|
||||
"description": "Anchor an element's position relative to another",
|
||||
"homepage": "https://alpinejs.dev/plugins/anchor",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/alpinejs/alpine.git",
|
||||
"directory": "packages/anchor"
|
||||
},
|
||||
"author": "Caleb Porzio",
|
||||
"license": "MIT",
|
||||
"main": "dist/module.cjs.js",
|
||||
"module": "dist/module.esm.js",
|
||||
"unpkg": "dist/cdn.min.js",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
import { computePosition, autoUpdate, flip, offset, shift } from '@floating-ui/dom'
|
||||
|
||||
export default function (Alpine) {
|
||||
Alpine.magic('anchor', el => {
|
||||
if (! el._x_anchor) throw 'Alpine: No x-anchor directive found on element using $anchor...'
|
||||
|
||||
return el._x_anchor
|
||||
})
|
||||
|
||||
Alpine.interceptClone((from, to) => {
|
||||
if (from && from._x_anchor && ! to._x_anchor) {
|
||||
to._x_anchor = from._x_anchor
|
||||
}
|
||||
})
|
||||
|
||||
Alpine.directive('anchor', Alpine.skipDuringClone((el, { expression, modifiers, value }, { cleanup, evaluate }) => {
|
||||
let { placement, offsetValue, unstyled } = getOptions(modifiers)
|
||||
|
||||
el._x_anchor = Alpine.reactive({ x: 0, y: 0 })
|
||||
|
||||
let reference = evaluate(expression)
|
||||
|
||||
if (! reference) throw 'Alpine: no element provided to x-anchor...'
|
||||
|
||||
let compute = () => {
|
||||
let previousValue
|
||||
|
||||
computePosition(reference, el, {
|
||||
placement,
|
||||
middleware: [flip(), shift({padding: 5}), offset(offsetValue)],
|
||||
}).then(({ x, y }) => {
|
||||
unstyled || setStyles(el, x, y)
|
||||
|
||||
// Only trigger Alpine reactivity when the value actually changes...
|
||||
if (JSON.stringify({ x, y }) !== previousValue) {
|
||||
el._x_anchor.x = x
|
||||
el._x_anchor.y = y
|
||||
}
|
||||
|
||||
previousValue = JSON.stringify({ x, y })
|
||||
})
|
||||
}
|
||||
|
||||
let release = autoUpdate(reference, el, () => compute())
|
||||
|
||||
cleanup(() => release())
|
||||
},
|
||||
|
||||
// When cloning (or "morphing"), we will graft the style and position data from the live tree...
|
||||
(el, { expression, modifiers, value }, { cleanup, evaluate }) => {
|
||||
let { placement, offsetValue, unstyled } = getOptions(modifiers)
|
||||
|
||||
if (el._x_anchor) {
|
||||
unstyled || setStyles(el, el._x_anchor.x, el._x_anchor.y)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
function setStyles(el, x, y) {
|
||||
Object.assign(el.style, {
|
||||
left: x+'px', top: y+'px', position: 'absolute',
|
||||
})
|
||||
}
|
||||
|
||||
function getOptions(modifiers) {
|
||||
let positions = ['top', 'top-start', 'top-end', 'right', 'right-start', 'right-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end']
|
||||
let placement = positions.find(i => modifiers.includes(i))
|
||||
let offsetValue = 0
|
||||
if (modifiers.includes('offset')) {
|
||||
let idx = modifiers.findIndex(i => i === 'offset')
|
||||
|
||||
offsetValue = modifiers[idx + 1] !== undefined ? Number(modifiers[idx + 1]) : offsetValue
|
||||
}
|
||||
let unstyled = modifiers.includes('no-style')
|
||||
|
||||
return { placement, offsetValue, unstyled }
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import collapse from '../src/index.js'
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.Alpine.plugin(collapse)
|
||||
})
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import collapse from '../src/index.js'
|
||||
|
||||
export default collapse
|
||||
|
||||
export { collapse }
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
(() => {
|
||||
// packages/collapse/src/index.js
|
||||
function src_default(Alpine) {
|
||||
Alpine.directive("collapse", collapse);
|
||||
collapse.inline = (el, { modifiers }) => {
|
||||
if (!modifiers.includes("min"))
|
||||
return;
|
||||
el._x_doShow = () => {
|
||||
};
|
||||
el._x_doHide = () => {
|
||||
};
|
||||
};
|
||||
function collapse(el, { modifiers }) {
|
||||
let duration = modifierValue(modifiers, "duration", 250) / 1e3;
|
||||
let floor = modifierValue(modifiers, "min", 0);
|
||||
let fullyHide = !modifiers.includes("min");
|
||||
if (!el._x_isShown)
|
||||
el.style.height = `${floor}px`;
|
||||
if (!el._x_isShown && fullyHide)
|
||||
el.hidden = true;
|
||||
if (!el._x_isShown)
|
||||
el.style.overflow = "hidden";
|
||||
let setFunction = (el2, styles) => {
|
||||
let revertFunction = Alpine.setStyles(el2, styles);
|
||||
return styles.height ? () => {
|
||||
} : revertFunction;
|
||||
};
|
||||
let transitionStyles = {
|
||||
transitionProperty: "height",
|
||||
transitionDuration: `${duration}s`,
|
||||
transitionTimingFunction: "cubic-bezier(0.4, 0.0, 0.2, 1)"
|
||||
};
|
||||
el._x_transition = {
|
||||
in(before = () => {
|
||||
}, after = () => {
|
||||
}) {
|
||||
if (fullyHide)
|
||||
el.hidden = false;
|
||||
if (fullyHide)
|
||||
el.style.display = null;
|
||||
let current = el.getBoundingClientRect().height;
|
||||
el.style.height = "auto";
|
||||
let full = el.getBoundingClientRect().height;
|
||||
if (current === full) {
|
||||
current = floor;
|
||||
}
|
||||
Alpine.transition(el, Alpine.setStyles, {
|
||||
during: transitionStyles,
|
||||
start: { height: current + "px" },
|
||||
end: { height: full + "px" }
|
||||
}, () => el._x_isShown = true, () => {
|
||||
if (el.getBoundingClientRect().height == full) {
|
||||
el.style.overflow = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
out(before = () => {
|
||||
}, after = () => {
|
||||
}) {
|
||||
let full = el.getBoundingClientRect().height;
|
||||
Alpine.transition(el, setFunction, {
|
||||
during: transitionStyles,
|
||||
start: { height: full + "px" },
|
||||
end: { height: floor + "px" }
|
||||
}, () => el.style.overflow = "hidden", () => {
|
||||
el._x_isShown = false;
|
||||
if (el.style.height == `${floor}px` && fullyHide) {
|
||||
el.style.display = "none";
|
||||
el.hidden = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
function modifierValue(modifiers, key, fallback) {
|
||||
if (modifiers.indexOf(key) === -1)
|
||||
return fallback;
|
||||
const rawValue = modifiers[modifiers.indexOf(key) + 1];
|
||||
if (!rawValue)
|
||||
return fallback;
|
||||
if (key === "duration") {
|
||||
let match = rawValue.match(/([0-9]+)ms/);
|
||||
if (match)
|
||||
return match[1];
|
||||
}
|
||||
if (key === "min") {
|
||||
let match = rawValue.match(/([0-9]+)px/);
|
||||
if (match)
|
||||
return match[1];
|
||||
}
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
// packages/collapse/builds/cdn.js
|
||||
document.addEventListener("alpine:init", () => {
|
||||
window.Alpine.plugin(src_default);
|
||||
});
|
||||
})();
|
||||
|
|
@ -0,0 +1 @@
|
|||
(()=>{function g(n){n.directive("collapse",e),e.inline=(t,{modifiers:i})=>{i.includes("min")&&(t._x_doShow=()=>{},t._x_doHide=()=>{})};function e(t,{modifiers:i}){let r=l(i,"duration",250)/1e3,u=l(i,"min",0),h=!i.includes("min");t._x_isShown||(t.style.height=`${u}px`),!t._x_isShown&&h&&(t.hidden=!0),t._x_isShown||(t.style.overflow="hidden");let c=(d,s)=>{let o=n.setStyles(d,s);return s.height?()=>{}:o},f={transitionProperty:"height",transitionDuration:`${r}s`,transitionTimingFunction:"cubic-bezier(0.4, 0.0, 0.2, 1)"};t._x_transition={in(d=()=>{},s=()=>{}){h&&(t.hidden=!1),h&&(t.style.display=null);let o=t.getBoundingClientRect().height;t.style.height="auto";let a=t.getBoundingClientRect().height;o===a&&(o=u),n.transition(t,n.setStyles,{during:f,start:{height:o+"px"},end:{height:a+"px"}},()=>t._x_isShown=!0,()=>{t.getBoundingClientRect().height==a&&(t.style.overflow=null)})},out(d=()=>{},s=()=>{}){let o=t.getBoundingClientRect().height;n.transition(t,c,{during:f,start:{height:o+"px"},end:{height:u+"px"}},()=>t.style.overflow="hidden",()=>{t._x_isShown=!1,t.style.height==`${u}px`&&h&&(t.style.display="none",t.hidden=!0)})}}}}function l(n,e,t){if(n.indexOf(e)===-1)return t;let i=n[n.indexOf(e)+1];if(!i)return t;if(e==="duration"){let r=i.match(/([0-9]+)ms/);if(r)return r[1]}if(e==="min"){let r=i.match(/([0-9]+)px/);if(r)return r[1]}return i}document.addEventListener("alpine:init",()=>{window.Alpine.plugin(g)});})();
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// packages/collapse/builds/module.js
|
||||
var module_exports = {};
|
||||
__export(module_exports, {
|
||||
collapse: () => src_default,
|
||||
default: () => module_default
|
||||
});
|
||||
module.exports = __toCommonJS(module_exports);
|
||||
|
||||
// packages/collapse/src/index.js
|
||||
function src_default(Alpine) {
|
||||
Alpine.directive("collapse", collapse);
|
||||
collapse.inline = (el, { modifiers }) => {
|
||||
if (!modifiers.includes("min"))
|
||||
return;
|
||||
el._x_doShow = () => {
|
||||
};
|
||||
el._x_doHide = () => {
|
||||
};
|
||||
};
|
||||
function collapse(el, { modifiers }) {
|
||||
let duration = modifierValue(modifiers, "duration", 250) / 1e3;
|
||||
let floor = modifierValue(modifiers, "min", 0);
|
||||
let fullyHide = !modifiers.includes("min");
|
||||
if (!el._x_isShown)
|
||||
el.style.height = `${floor}px`;
|
||||
if (!el._x_isShown && fullyHide)
|
||||
el.hidden = true;
|
||||
if (!el._x_isShown)
|
||||
el.style.overflow = "hidden";
|
||||
let setFunction = (el2, styles) => {
|
||||
let revertFunction = Alpine.setStyles(el2, styles);
|
||||
return styles.height ? () => {
|
||||
} : revertFunction;
|
||||
};
|
||||
let transitionStyles = {
|
||||
transitionProperty: "height",
|
||||
transitionDuration: `${duration}s`,
|
||||
transitionTimingFunction: "cubic-bezier(0.4, 0.0, 0.2, 1)"
|
||||
};
|
||||
el._x_transition = {
|
||||
in(before = () => {
|
||||
}, after = () => {
|
||||
}) {
|
||||
if (fullyHide)
|
||||
el.hidden = false;
|
||||
if (fullyHide)
|
||||
el.style.display = null;
|
||||
let current = el.getBoundingClientRect().height;
|
||||
el.style.height = "auto";
|
||||
let full = el.getBoundingClientRect().height;
|
||||
if (current === full) {
|
||||
current = floor;
|
||||
}
|
||||
Alpine.transition(el, Alpine.setStyles, {
|
||||
during: transitionStyles,
|
||||
start: { height: current + "px" },
|
||||
end: { height: full + "px" }
|
||||
}, () => el._x_isShown = true, () => {
|
||||
if (el.getBoundingClientRect().height == full) {
|
||||
el.style.overflow = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
out(before = () => {
|
||||
}, after = () => {
|
||||
}) {
|
||||
let full = el.getBoundingClientRect().height;
|
||||
Alpine.transition(el, setFunction, {
|
||||
during: transitionStyles,
|
||||
start: { height: full + "px" },
|
||||
end: { height: floor + "px" }
|
||||
}, () => el.style.overflow = "hidden", () => {
|
||||
el._x_isShown = false;
|
||||
if (el.style.height == `${floor}px` && fullyHide) {
|
||||
el.style.display = "none";
|
||||
el.hidden = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
function modifierValue(modifiers, key, fallback) {
|
||||
if (modifiers.indexOf(key) === -1)
|
||||
return fallback;
|
||||
const rawValue = modifiers[modifiers.indexOf(key) + 1];
|
||||
if (!rawValue)
|
||||
return fallback;
|
||||
if (key === "duration") {
|
||||
let match = rawValue.match(/([0-9]+)ms/);
|
||||
if (match)
|
||||
return match[1];
|
||||
}
|
||||
if (key === "min") {
|
||||
let match = rawValue.match(/([0-9]+)px/);
|
||||
if (match)
|
||||
return match[1];
|
||||
}
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
// packages/collapse/builds/module.js
|
||||
var module_default = src_default;
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
collapse
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue