stimulus-components 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +133 -0
- data/app/javascript/animated-number.js +66 -0
- data/app/javascript/auto-submit.js +27 -0
- data/app/javascript/carousel.js +28 -0
- data/app/javascript/character-counter.js +47 -0
- data/app/javascript/chartjs.js +58 -0
- data/app/javascript/checkbox-select-all.js +76 -0
- data/app/javascript/clipboard.js +48 -0
- data/app/javascript/color-picker.js +75 -0
- data/app/javascript/confirmation.js +30 -0
- data/app/javascript/content-loader.js +109 -0
- data/app/javascript/dialog.js +53 -0
- data/app/javascript/dropdown.js +27 -0
- data/app/javascript/glow.js +30 -0
- data/app/javascript/hotkey.js +26 -0
- data/app/javascript/lightbox.js +26 -0
- data/app/javascript/notification.js +49 -0
- data/app/javascript/password-visibility.js +27 -0
- data/app/javascript/places-autocomplete.js +123 -0
- data/app/javascript/popover.js +53 -0
- data/app/javascript/prefetch.js +56 -0
- data/app/javascript/rails-nested-form.js +43 -0
- data/app/javascript/read-more.js +38 -0
- data/app/javascript/remote-rails.js +27 -0
- data/app/javascript/reveal-controller.js +33 -0
- data/app/javascript/scroll-progress.js +32 -0
- data/app/javascript/scroll-reveal.js +62 -0
- data/app/javascript/scroll-to.js +56 -0
- data/app/javascript/sortable.js +74 -0
- data/app/javascript/sound.js +39 -0
- data/app/javascript/speech-recognition.js +92 -0
- data/app/javascript/stimulus-components.js +71 -0
- data/app/javascript/textarea-autogrow.js +45 -0
- data/app/javascript/timeago.js +66 -0
- data/lib/stimulus-components/engine.rb +9 -0
- data/lib/stimulus-components/version.rb +3 -0
- data/lib/stimulus-components.rb +43 -0
- metadata +98 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class ContentLoader extends Controller<HTMLElement> {
|
|
4
|
+
declare refreshTimer: number
|
|
5
|
+
declare readonly hasUrlValue: boolean
|
|
6
|
+
declare readonly hasLazyLoadingValue: boolean
|
|
7
|
+
declare readonly hasRefreshIntervalValue: boolean
|
|
8
|
+
declare readonly lazyLoadingThresholdValue: number
|
|
9
|
+
declare readonly lazyLoadingRootMarginValue: string
|
|
10
|
+
declare readonly urlValue: string
|
|
11
|
+
declare readonly loadScriptsValue: boolean
|
|
12
|
+
declare readonly refreshIntervalValue: number
|
|
13
|
+
|
|
14
|
+
static values = {
|
|
15
|
+
url: String,
|
|
16
|
+
lazyLoading: Boolean,
|
|
17
|
+
lazyLoadingThreshold: Number,
|
|
18
|
+
lazyLoadingRootMargin: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: "0px",
|
|
21
|
+
},
|
|
22
|
+
refreshInterval: Number,
|
|
23
|
+
loadScripts: Boolean,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
connect(): void {
|
|
27
|
+
if (!this.hasUrlValue) {
|
|
28
|
+
console.error("[stimulus-content-loader] You need to pass an url to fetch the remote content.")
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.hasLazyLoadingValue ? this.lazyLoad() : this.load()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
disconnect(): void {
|
|
36
|
+
this.stopRefreshing()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
load(): void {
|
|
40
|
+
this.fetch()
|
|
41
|
+
|
|
42
|
+
if (this.hasRefreshIntervalValue) {
|
|
43
|
+
this.startRefreshing()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
lazyLoad(): void {
|
|
48
|
+
const options: IntersectionObserverInit = {
|
|
49
|
+
threshold: this.lazyLoadingThresholdValue,
|
|
50
|
+
rootMargin: this.lazyLoadingRootMarginValue,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const observer = new IntersectionObserver(
|
|
54
|
+
(entries, observer) => {
|
|
55
|
+
entries.forEach((entry) => {
|
|
56
|
+
if (entry.isIntersecting) {
|
|
57
|
+
this.load()
|
|
58
|
+
observer.unobserve(entry.target)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
},
|
|
62
|
+
options
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
observer.observe(this.element)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fetch(): void {
|
|
69
|
+
fetch(this.urlValue)
|
|
70
|
+
.then((response) => {
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new Error(response.statusText)
|
|
73
|
+
}
|
|
74
|
+
return response.text()
|
|
75
|
+
})
|
|
76
|
+
.then((html) => {
|
|
77
|
+
this.element.innerHTML = html
|
|
78
|
+
|
|
79
|
+
if (this.loadScriptsValue) {
|
|
80
|
+
this.loadScripts()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.dispatch("success")
|
|
84
|
+
})
|
|
85
|
+
.catch((error) => {
|
|
86
|
+
this.dispatch("error", { detail: { error } })
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
startRefreshing(): void {
|
|
91
|
+
this.refreshTimer = setInterval(() => {
|
|
92
|
+
this.fetch()
|
|
93
|
+
}, this.refreshIntervalValue)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
stopRefreshing(): void {
|
|
97
|
+
if (this.refreshTimer) {
|
|
98
|
+
clearInterval(this.refreshTimer)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
loadScripts(): void {
|
|
103
|
+
this.element.querySelectorAll("script").forEach((content) => {
|
|
104
|
+
const script = document.createElement("script")
|
|
105
|
+
script.innerHTML = content.innerHTML
|
|
106
|
+
document.head.appendChild(script).parentNode.removeChild(script)
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class Dialog extends Controller {
|
|
4
|
+
declare readonly dialogTarget: HTMLDialogElement
|
|
5
|
+
declare readonly openValue: boolean
|
|
6
|
+
|
|
7
|
+
static targets = ["dialog"]
|
|
8
|
+
static values = {
|
|
9
|
+
open: {
|
|
10
|
+
type: Boolean,
|
|
11
|
+
default: false,
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
initialize() {
|
|
16
|
+
this.forceClose = this.forceClose.bind(this)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
connect(): void {
|
|
20
|
+
if (this.openValue) {
|
|
21
|
+
this.open()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
document.addEventListener("turbo:before-render", this.forceClose)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
disconnect(): void {
|
|
28
|
+
document.removeEventListener("turbo:before-render", this.forceClose)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
open(): void {
|
|
32
|
+
this.dialogTarget.showModal()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
close(): void {
|
|
36
|
+
this.dialogTarget.setAttribute("closing", "")
|
|
37
|
+
|
|
38
|
+
Promise.all(this.dialogTarget.getAnimations().map((animation) => animation.finished)).then(() => {
|
|
39
|
+
this.dialogTarget.removeAttribute("closing")
|
|
40
|
+
this.dialogTarget.close()
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
backdropClose(event: Event): void {
|
|
45
|
+
if (event.target === this.dialogTarget) {
|
|
46
|
+
this.close()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
forceClose(): void {
|
|
51
|
+
this.dialogTarget.close()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { useTransition } from "stimulus-use"
|
|
3
|
+
|
|
4
|
+
export default class Dropdown extends Controller {
|
|
5
|
+
declare readonly menuTarget: HTMLElement
|
|
6
|
+
declare toggleTransition: (event?: Event) => void
|
|
7
|
+
declare leave: (event?: Event) => void
|
|
8
|
+
declare transitioned: false
|
|
9
|
+
|
|
10
|
+
static targets = ["menu"]
|
|
11
|
+
|
|
12
|
+
connect(): void {
|
|
13
|
+
useTransition(this, {
|
|
14
|
+
element: this.menuTarget,
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
toggle(): void {
|
|
19
|
+
this.toggleTransition()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
hide(event: Event): void {
|
|
23
|
+
if (!this.element.contains(event.target) && !this.menuTarget.classList.contains("hidden")) {
|
|
24
|
+
this.leave()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class Glow extends Controller<HTMLElement> {
|
|
4
|
+
declare childTarget: HTMLElement
|
|
5
|
+
declare overlayTarget: HTMLElement
|
|
6
|
+
|
|
7
|
+
static targets = ["child", "overlay"]
|
|
8
|
+
|
|
9
|
+
initialize(): void {
|
|
10
|
+
this.move = this.move.bind(this)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
connect(): void {
|
|
14
|
+
this.overlayTarget.append(this.childTarget.cloneNode(true))
|
|
15
|
+
document.body.addEventListener("pointermove", this.move)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
disconnect(): void {
|
|
19
|
+
document.body.removeEventListener("pointermove", this.move)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
move(e: PointerEvent): void {
|
|
23
|
+
const x = e.pageX - this.element.offsetLeft
|
|
24
|
+
const y = e.pageY - this.element.offsetTop
|
|
25
|
+
|
|
26
|
+
this.element.style.setProperty("--glow-opacity", "1")
|
|
27
|
+
this.element.style.setProperty("--glow-x", `${x}px`)
|
|
28
|
+
this.element.style.setProperty("--glow-y", `${y}px`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class Hotkey extends Controller<HTMLElement> {
|
|
4
|
+
click(event: KeyboardEvent): void {
|
|
5
|
+
if (this.isClickable && !this.shouldIgnore(event)) {
|
|
6
|
+
event.preventDefault()
|
|
7
|
+
this.element.click()
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
focus(event: KeyboardEvent): void {
|
|
12
|
+
if (this.isClickable && !this.shouldIgnore(event)) {
|
|
13
|
+
event.preventDefault()
|
|
14
|
+
this.element.focus()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private shouldIgnore(event: KeyboardEvent): boolean {
|
|
19
|
+
const target = event.target as Element | null
|
|
20
|
+
return event.defaultPrevented || !!target?.closest("input, textarea, [contenteditable]")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private get isClickable(): boolean {
|
|
24
|
+
return getComputedStyle(this.element).pointerEvents !== "none"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import lightGallery, { LightGallerySettings, LightGallery } from "lightgallery"
|
|
3
|
+
|
|
4
|
+
export default class Lightbox extends Controller<HTMLElement> {
|
|
5
|
+
declare optionsValue: LightGallerySettings
|
|
6
|
+
declare lightGallery: LightGallery
|
|
7
|
+
|
|
8
|
+
static values = {
|
|
9
|
+
options: Object,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
connect(): void {
|
|
13
|
+
this.lightGallery = lightGallery(this.element, {
|
|
14
|
+
...this.defaultOptions,
|
|
15
|
+
...this.optionsValue,
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
disconnect(): void {
|
|
20
|
+
this.lightGallery.destroy()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get defaultOptions(): LightGallerySettings {
|
|
24
|
+
return {}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { useTransition } from "stimulus-use"
|
|
3
|
+
|
|
4
|
+
export default class Notification extends Controller {
|
|
5
|
+
declare timeout: number
|
|
6
|
+
declare enter: (event?: Event) => void
|
|
7
|
+
declare leave: (event?: Event) => void
|
|
8
|
+
declare transitioned: false
|
|
9
|
+
declare delayValue: number
|
|
10
|
+
declare hiddenValue: boolean
|
|
11
|
+
|
|
12
|
+
static values = {
|
|
13
|
+
delay: {
|
|
14
|
+
type: Number,
|
|
15
|
+
default: 3000,
|
|
16
|
+
},
|
|
17
|
+
hidden: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: false,
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
initialize() {
|
|
24
|
+
this.hide = this.hide.bind(this)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
connect() {
|
|
28
|
+
useTransition(this)
|
|
29
|
+
|
|
30
|
+
if (this.hiddenValue === false) {
|
|
31
|
+
this.show()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
show() {
|
|
36
|
+
this.enter()
|
|
37
|
+
|
|
38
|
+
this.timeout = setTimeout(this.hide, this.delayValue)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async hide() {
|
|
42
|
+
if (this.timeout) {
|
|
43
|
+
clearTimeout(this.timeout)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await this.leave()
|
|
47
|
+
this.element.remove()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class PasswordVisibility extends Controller {
|
|
4
|
+
declare hasHiddenClass: boolean
|
|
5
|
+
declare hiddenClass: string
|
|
6
|
+
declare class: string
|
|
7
|
+
declare hidden: boolean
|
|
8
|
+
declare inputTarget: HTMLInputElement
|
|
9
|
+
declare iconTargets: HTMLElement[]
|
|
10
|
+
|
|
11
|
+
static targets = ["input", "icon"]
|
|
12
|
+
static classes = ["hidden"]
|
|
13
|
+
|
|
14
|
+
connect(): void {
|
|
15
|
+
this.hidden = this.inputTarget.type === "password"
|
|
16
|
+
this.class = this.hasHiddenClass ? this.hiddenClass : "hidden"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
toggle(e: Event): void {
|
|
20
|
+
e.preventDefault()
|
|
21
|
+
|
|
22
|
+
this.inputTarget.type = this.hidden ? "text" : "password"
|
|
23
|
+
this.hidden = !this.hidden
|
|
24
|
+
|
|
25
|
+
this.iconTargets.forEach((icon) => icon.classList.toggle(this.class))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
interface Address {
|
|
4
|
+
street_number: string
|
|
5
|
+
route: string
|
|
6
|
+
locality: string
|
|
7
|
+
administrative_area_level_2: string
|
|
8
|
+
administrative_area_level_1: string
|
|
9
|
+
country: string
|
|
10
|
+
postal_code: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default class PlacesAutocomplete extends Controller {
|
|
14
|
+
declare autocomplete: google.maps.places.Autocomplete
|
|
15
|
+
declare place: google.maps.places.PlaceResult
|
|
16
|
+
declare addressTarget: HTMLInputElement
|
|
17
|
+
declare streetNumberTarget: HTMLInputElement
|
|
18
|
+
declare routeTarget: HTMLInputElement
|
|
19
|
+
declare cityTarget: HTMLInputElement
|
|
20
|
+
declare countyTarget: HTMLInputElement
|
|
21
|
+
declare stateTarget: HTMLInputElement
|
|
22
|
+
declare countryTarget: HTMLInputElement
|
|
23
|
+
declare postalCodeTarget: HTMLInputElement
|
|
24
|
+
declare longitudeTarget: HTMLInputElement
|
|
25
|
+
declare latitudeTarget: HTMLInputElement
|
|
26
|
+
declare hasStreetNumberTarget: boolean
|
|
27
|
+
declare hasRouteTarget: boolean
|
|
28
|
+
declare hasCityTarget: boolean
|
|
29
|
+
declare hasCountryTarget: boolean
|
|
30
|
+
declare hasCountyTarget: boolean
|
|
31
|
+
declare hasPostalCodeTarget: boolean
|
|
32
|
+
declare hasStateTarget: boolean
|
|
33
|
+
declare hasLongitudeTarget: boolean
|
|
34
|
+
declare hasLatitudeTarget: boolean
|
|
35
|
+
declare countryValue: Array<string>
|
|
36
|
+
|
|
37
|
+
static targets = [
|
|
38
|
+
"address",
|
|
39
|
+
"city",
|
|
40
|
+
"streetNumber",
|
|
41
|
+
"route",
|
|
42
|
+
"postalCode",
|
|
43
|
+
"country",
|
|
44
|
+
"county",
|
|
45
|
+
"state",
|
|
46
|
+
"longitude",
|
|
47
|
+
"latitude",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
static values = {
|
|
51
|
+
country: Array,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
initialize(): void {
|
|
55
|
+
this.placeChanged = this.placeChanged.bind(this)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
connect(): void {
|
|
59
|
+
if (typeof google !== "undefined") {
|
|
60
|
+
this.initAutocomplete()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
initAutocomplete(): void {
|
|
65
|
+
this.autocomplete = new google.maps.places.Autocomplete(this.addressTarget, this.autocompleteOptions)
|
|
66
|
+
this.autocomplete.addListener("place_changed", this.placeChanged)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
placeChanged(): void {
|
|
70
|
+
this.place = this.autocomplete.getPlace()
|
|
71
|
+
const addressComponents = this.place.address_components
|
|
72
|
+
|
|
73
|
+
if (addressComponents !== undefined) {
|
|
74
|
+
const formattedAddress = this.formatAddressComponents(addressComponents) as Address
|
|
75
|
+
this.setAddressComponents(formattedAddress)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.place.geometry !== undefined) {
|
|
79
|
+
this.setGeometry(this.place.geometry)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setAddressComponents(address: Address): void {
|
|
84
|
+
if (this.hasStreetNumberTarget) this.streetNumberTarget.value = address.street_number || ""
|
|
85
|
+
if (this.hasRouteTarget) this.routeTarget.value = address.route || ""
|
|
86
|
+
if (this.hasCityTarget) this.cityTarget.value = address.locality || ""
|
|
87
|
+
if (this.hasCountyTarget) this.countyTarget.value = address.administrative_area_level_2 || ""
|
|
88
|
+
if (this.hasStateTarget) this.stateTarget.value = address.administrative_area_level_1 || ""
|
|
89
|
+
if (this.hasCountryTarget) this.countryTarget.value = address.country || ""
|
|
90
|
+
if (this.hasPostalCodeTarget) this.postalCodeTarget.value = address.postal_code || ""
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setGeometry(geometry: google.maps.places.PlaceGeometry): void {
|
|
94
|
+
if (this.hasLongitudeTarget) this.longitudeTarget.value = geometry.location.lng().toString()
|
|
95
|
+
if (this.hasLatitudeTarget) this.latitudeTarget.value = geometry.location.lat().toString()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get autocompleteOptions(): google.maps.places.AutocompleteOptions {
|
|
99
|
+
return {
|
|
100
|
+
fields: ["address_components", "geometry"],
|
|
101
|
+
componentRestrictions: {
|
|
102
|
+
country: this.countryValue,
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
preventSubmit(event: KeyboardEvent): void {
|
|
108
|
+
if (event.code === "Enter") {
|
|
109
|
+
event.preventDefault()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private formatAddressComponents(addressComponents): Address {
|
|
114
|
+
const data = {}
|
|
115
|
+
|
|
116
|
+
addressComponents.forEach((component) => {
|
|
117
|
+
const type = component.types[0]
|
|
118
|
+
data[type] = component.long_name
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return data as Address
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class Popover extends Controller {
|
|
4
|
+
declare readonly hasCardTarget: boolean
|
|
5
|
+
declare readonly hasContentTarget: boolean
|
|
6
|
+
declare readonly hasUrlValue: boolean
|
|
7
|
+
declare readonly contentTarget: HTMLElement
|
|
8
|
+
declare readonly cardTarget: HTMLElement
|
|
9
|
+
declare readonly urlValue: string
|
|
10
|
+
declare remoteContent: string
|
|
11
|
+
|
|
12
|
+
static targets = ["card", "content"]
|
|
13
|
+
static values = {
|
|
14
|
+
url: String,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async show(event: Event): Promise<void> {
|
|
18
|
+
const element = event.currentTarget
|
|
19
|
+
|
|
20
|
+
let content: string = null
|
|
21
|
+
|
|
22
|
+
if (this.hasContentTarget) {
|
|
23
|
+
content = this.contentTarget.innerHTML
|
|
24
|
+
} else {
|
|
25
|
+
content = await this.fetch()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!content) return
|
|
29
|
+
|
|
30
|
+
const fragment = document.createRange().createContextualFragment(content)
|
|
31
|
+
element.appendChild(fragment)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
hide(): void {
|
|
35
|
+
if (this.hasCardTarget) {
|
|
36
|
+
this.cardTarget.remove()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async fetch(): Promise<string> {
|
|
41
|
+
if (!this.remoteContent) {
|
|
42
|
+
if (!this.hasUrlValue) {
|
|
43
|
+
console.error("[stimulus-popover] You need to pass an url to fetch the popover content.")
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const response = await fetch(this.urlValue)
|
|
48
|
+
this.remoteContent = await response.text()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return this.remoteContent
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class Prefetch extends Controller<HTMLAnchorElement> {
|
|
4
|
+
initialize(): void {
|
|
5
|
+
this.prefetch = this.prefetch.bind(this)
|
|
6
|
+
this.load = this.load.bind(this)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
connect(): void {
|
|
10
|
+
if (!this.hasPrefetch) return
|
|
11
|
+
this.load()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
load(): void {
|
|
15
|
+
const observer = new IntersectionObserver(
|
|
16
|
+
(entries, observer) => {
|
|
17
|
+
entries.forEach((entry) => {
|
|
18
|
+
if (entry.isIntersecting) {
|
|
19
|
+
this.prefetch()
|
|
20
|
+
observer.unobserve(entry.target)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
observer.observe(this.element)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
prefetch(): void {
|
|
30
|
+
const connection = navigator.connection
|
|
31
|
+
|
|
32
|
+
if (connection) {
|
|
33
|
+
if (connection.saveData) {
|
|
34
|
+
console.warn("[@stimulus-components/prefetch] Cannot prefetch, Save-Data is enabled.")
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (connection.effectiveType !== "4g") {
|
|
39
|
+
console.warn("[@stimulus-components/prefetch] Cannot prefetch, network conditions are poor.")
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const link = document.createElement("link")
|
|
45
|
+
link.rel = "prefetch"
|
|
46
|
+
link.href = this.element.href
|
|
47
|
+
link.as = "document"
|
|
48
|
+
|
|
49
|
+
document.head.appendChild(link)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get hasPrefetch(): boolean {
|
|
53
|
+
const link = document.createElement("link")
|
|
54
|
+
return link.relList && link.relList.supports && link.relList.supports("prefetch")
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class RailsNestedForm extends Controller {
|
|
4
|
+
declare targetTarget: HTMLElement
|
|
5
|
+
declare templateTarget: HTMLElement
|
|
6
|
+
declare wrapperSelectorValue: string
|
|
7
|
+
|
|
8
|
+
static targets = ["target", "template"]
|
|
9
|
+
static values = {
|
|
10
|
+
wrapperSelector: {
|
|
11
|
+
type: String,
|
|
12
|
+
default: ".nested-form-wrapper",
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
add(e: Event): void {
|
|
17
|
+
e.preventDefault()
|
|
18
|
+
|
|
19
|
+
const content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime().toString())
|
|
20
|
+
this.targetTarget.insertAdjacentHTML("beforebegin", content)
|
|
21
|
+
|
|
22
|
+
const event = new CustomEvent("rails-nested-form:add", { bubbles: true })
|
|
23
|
+
this.element.dispatchEvent(event)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
remove(e: Event): void {
|
|
27
|
+
e.preventDefault()
|
|
28
|
+
|
|
29
|
+
const wrapper = e.target.closest(this.wrapperSelectorValue)
|
|
30
|
+
|
|
31
|
+
if (wrapper.dataset.newRecord === "true") {
|
|
32
|
+
wrapper.remove()
|
|
33
|
+
} else {
|
|
34
|
+
wrapper.style.display = "none"
|
|
35
|
+
|
|
36
|
+
const input = wrapper.querySelector("input[name*='_destroy']")
|
|
37
|
+
input.value = "1"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const event = new CustomEvent("rails-nested-form:remove", { bubbles: true })
|
|
41
|
+
this.element.dispatchEvent(event)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class ReadMore extends Controller {
|
|
4
|
+
declare open: boolean
|
|
5
|
+
declare contentTarget: HTMLElement
|
|
6
|
+
declare moreTextValue: string
|
|
7
|
+
declare lessTextValue: string
|
|
8
|
+
|
|
9
|
+
static targets: string[] = ["content"]
|
|
10
|
+
static values = {
|
|
11
|
+
moreText: String,
|
|
12
|
+
lessText: String,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
connect(): void {
|
|
16
|
+
this.open = false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
toggle(event: Event): void {
|
|
20
|
+
this.open === false ? this.show(event) : this.hide(event)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
show(event: Event): void {
|
|
24
|
+
this.open = true
|
|
25
|
+
|
|
26
|
+
const target = event.target as HTMLElement
|
|
27
|
+
target.innerHTML = this.lessTextValue
|
|
28
|
+
this.contentTarget.style.setProperty("--read-more-line-clamp", "'unset'")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
hide(event: Event): void {
|
|
32
|
+
this.open = false
|
|
33
|
+
|
|
34
|
+
const target = event.target as HTMLElement
|
|
35
|
+
target.innerHTML = this.moreTextValue
|
|
36
|
+
this.contentTarget.style.removeProperty("--read-more-line-clamp")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class RemoteRails extends Controller<HTMLElement> {
|
|
4
|
+
replace(event: CustomEvent): void {
|
|
5
|
+
event.preventDefault()
|
|
6
|
+
event.stopPropagation()
|
|
7
|
+
|
|
8
|
+
const [, , xhr] = event.detail
|
|
9
|
+
this.element.outerHTML = xhr.response
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
append(event: CustomEvent): void {
|
|
13
|
+
event.preventDefault()
|
|
14
|
+
event.stopPropagation()
|
|
15
|
+
|
|
16
|
+
const [, , xhr] = event.detail
|
|
17
|
+
this.element.insertAdjacentHTML("afterend", xhr.response)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
prepend(event: CustomEvent): void {
|
|
21
|
+
event.preventDefault()
|
|
22
|
+
event.stopPropagation()
|
|
23
|
+
|
|
24
|
+
const [, , xhr] = event.detail
|
|
25
|
+
this.element.insertAdjacentHTML("beforebegin", xhr.response)
|
|
26
|
+
}
|
|
27
|
+
}
|