Add supporting themes required for Lotusdocs
This commit is contained in:
21
themes/hugo-mod-bootstrap-scss/LICENSE
Normal file
21
themes/hugo-mod-bootstrap-scss/LICENSE
Normal file
@@ -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.
|
||||||
91
themes/hugo-mod-bootstrap-scss/README.md
Normal file
91
themes/hugo-mod-bootstrap-scss/README.md
Normal file
@@ -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);
|
||||||
|
}
|
||||||
21
themes/hugo-mod-bootstrap-scss/config.toml
Normal file
21
themes/hugo-mod-bootstrap-scss/config.toml
Normal file
@@ -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);
|
||||||
|
}
|
||||||
14
themes/hugo-mod-bootstrap-scss/exampleSite/config.toml
Normal file
14
themes/hugo-mod-bootstrap-scss/exampleSite/config.toml
Normal file
@@ -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"
|
||||||
9
themes/hugo-mod-bootstrap-scss/exampleSite/go.mod
Normal file
9
themes/hugo-mod-bootstrap-scss/exampleSite/go.mod
Normal file
@@ -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 => ../
|
||||||
8
themes/hugo-mod-bootstrap-scss/exampleSite/go.sum
Normal file
8
themes/hugo-mod-bootstrap-scss/exampleSite/go.sum
Normal file
@@ -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=
|
||||||
165
themes/hugo-mod-bootstrap-scss/exampleSite/layouts/index.html
Normal file
165
themes/hugo-mod-bootstrap-scss/exampleSite/layouts/index.html
Normal file
@@ -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>
|
||||||
8
themes/hugo-mod-bootstrap-scss/go.mod
Normal file
8
themes/hugo-mod-bootstrap-scss/go.mod
Normal file
@@ -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
|
||||||
|
)
|
||||||
4
themes/hugo-mod-bootstrap-scss/go.sum
Normal file
4
themes/hugo-mod-bootstrap-scss/go.sum
Normal file
@@ -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=
|
||||||
12
themes/hugo-mod-bootstrap-scss/netlify.toml
Normal file
12
themes/hugo-mod-bootstrap-scss/netlify.toml
Normal file
@@ -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"
|
||||||
21
themes/hugo-mod-jslibs-dist/LICENSE
Normal file
21
themes/hugo-mod-jslibs-dist/LICENSE
Normal file
@@ -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.
|
||||||
6
themes/hugo-mod-jslibs-dist/README.md
Normal file
6
themes/hugo-mod-jslibs-dist/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# hugo-mod-jslibs-dist
|
||||||
|
|
||||||
|
Thin Hugo Module wrappers around some popular JS libs' distribution source code.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
41
themes/hugo-mod-jslibs-dist/alpinejs/README.md
Normal file
41
themes/hugo-mod-jslibs-dist/alpinejs/README.md
Normal file
@@ -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.
|
||||||
4
themes/hugo-mod-jslibs-dist/alpinejs/config.toml
Normal file
4
themes/hugo-mod-jslibs-dist/alpinejs/config.toml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[module]
|
||||||
|
[[module.mounts]]
|
||||||
|
source = "packages"
|
||||||
|
target = "assets/jslibs/alpinejs/v3"
|
||||||
3
themes/hugo-mod-jslibs-dist/alpinejs/go.mod
Normal file
3
themes/hugo-mod-jslibs-dist/alpinejs/go.mod
Normal file
@@ -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 }
|
||||||
3364
themes/hugo-mod-jslibs-dist/alpinejs/packages/alpinejs/dist/cdn.js
vendored
Normal file
3364
themes/hugo-mod-jslibs-dist/alpinejs/packages/alpinejs/dist/cdn.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5
themes/hugo-mod-jslibs-dist/alpinejs/packages/alpinejs/dist/cdn.min.js
vendored
Normal file
5
themes/hugo-mod-jslibs-dist/alpinejs/packages/alpinejs/dist/cdn.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4072
themes/hugo-mod-jslibs-dist/alpinejs/packages/alpinejs/dist/module.cjs.js
vendored
Normal file
4072
themes/hugo-mod-jslibs-dist/alpinejs/packages/alpinejs/dist/module.cjs.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3363
themes/hugo-mod-jslibs-dist/alpinejs/packages/alpinejs/dist/module.esm.js
vendored
Normal file
3363
themes/hugo-mod-jslibs-dist/alpinejs/packages/alpinejs/dist/module.esm.js
vendored
Normal file
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 }
|
||||||
1258
themes/hugo-mod-jslibs-dist/alpinejs/packages/anchor/dist/cdn.js
vendored
Normal file
1258
themes/hugo-mod-jslibs-dist/alpinejs/packages/anchor/dist/cdn.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
themes/hugo-mod-jslibs-dist/alpinejs/packages/anchor/dist/cdn.min.js
vendored
Normal file
1
themes/hugo-mod-jslibs-dist/alpinejs/packages/anchor/dist/cdn.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1284
themes/hugo-mod-jslibs-dist/alpinejs/packages/anchor/dist/module.cjs.js
vendored
Normal file
1284
themes/hugo-mod-jslibs-dist/alpinejs/packages/anchor/dist/module.cjs.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1258
themes/hugo-mod-jslibs-dist/alpinejs/packages/anchor/dist/module.esm.js
vendored
Normal file
1258
themes/hugo-mod-jslibs-dist/alpinejs/packages/anchor/dist/module.esm.js
vendored
Normal file
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 }
|
||||||
99
themes/hugo-mod-jslibs-dist/alpinejs/packages/collapse/dist/cdn.js
vendored
Normal file
99
themes/hugo-mod-jslibs-dist/alpinejs/packages/collapse/dist/cdn.js
vendored
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
})();
|
||||||
1
themes/hugo-mod-jslibs-dist/alpinejs/packages/collapse/dist/cdn.min.js
vendored
Normal file
1
themes/hugo-mod-jslibs-dist/alpinejs/packages/collapse/dist/cdn.min.js
vendored
Normal file
@@ -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)});})();
|
||||||
125
themes/hugo-mod-jslibs-dist/alpinejs/packages/collapse/dist/module.cjs.js
vendored
Normal file
125
themes/hugo-mod-jslibs-dist/alpinejs/packages/collapse/dist/module.cjs.js
vendored
Normal file
@@ -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
|
||||||
|
});
|
||||||
99
themes/hugo-mod-jslibs-dist/alpinejs/packages/collapse/dist/module.esm.js
vendored
Normal file
99
themes/hugo-mod-jslibs-dist/alpinejs/packages/collapse/dist/module.esm.js
vendored
Normal file
@@ -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/module.js
|
||||||
|
var module_default = src_default;
|
||||||
|
export {
|
||||||
|
src_default as collapse,
|
||||||
|
module_default as default
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "@alpinejs/collapse",
|
||||||
|
"version": "3.13.8",
|
||||||
|
"description": "Collapse and expand elements with robust animations",
|
||||||
|
"homepage": "https://alpinejs.dev/plugins/collapse",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/alpinejs/alpine.git",
|
||||||
|
"directory": "packages/collapse"
|
||||||
|
},
|
||||||
|
"author": "Caleb Porzio",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "dist/module.cjs.js",
|
||||||
|
"module": "dist/module.esm.js",
|
||||||
|
"unpkg": "dist/cdn.min.js",
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user