odt-ui-rails 2.2.4 → 2.2.6
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 +4 -4
- data/README.md +23 -3
- data/app/assets/stylesheets/odt_ui/styles.css +489 -109
- data/app/assets/stylesheets/odt_ui.css +489 -109
- data/lib/generators/odt/install_generator.rb +2 -3
- data/lib/generators/odt/templates/odt_dropdown_controller.js +137 -0
- data/lib/generators/odt/templates/odt_modal_controller.js +24 -0
- data/lib/generators/odt/templates/odt_toast_controller.js +12 -0
- data/lib/generators/odt/templates/odt_toaster_controller.js +195 -0
- data/lib/generators/odt/templates/odt_ui.rbs +119 -61
- data/lib/odt_ui/engine.rb +0 -1
- data/lib/odt_ui/helper.rb +324 -282
- data/lib/odt_ui/version.rb +1 -1
- data/lib/odt_ui.rb +6 -1
- data/sig/odt_ui.rbs +119 -61
- metadata +3 -4
- data/app/assets/stylesheets/odt_ui/theme.css +0 -126
- data/app/javascript/controllers/odt_modal_controller.js +0 -61
- data/app/javascript/controllers/odt_toast_controller.js +0 -40
|
@@ -10,7 +10,6 @@ module Odt
|
|
|
10
10
|
desc "Configures ODT UI standalone stylesheets, Tailwind theme, and RBS type signatures for Rails application"
|
|
11
11
|
|
|
12
12
|
def setup_stylesheets
|
|
13
|
-
# 1. Setup Tailwind CSS v4 Theme if Tailwind is present
|
|
14
13
|
tailwind_candidates = [
|
|
15
14
|
"app/assets/tailwind/application.css",
|
|
16
15
|
"app/assets/stylesheets/application.tailwind.css"
|
|
@@ -29,12 +28,10 @@ module Odt
|
|
|
29
28
|
end
|
|
30
29
|
end
|
|
31
30
|
|
|
32
|
-
# 2. Inject stylesheet_link_tag into application.html.erb layout
|
|
33
31
|
layout_file = "app/views/layouts/application.html.erb"
|
|
34
32
|
if File.exist?(layout_file)
|
|
35
33
|
layout_content = File.read(layout_file)
|
|
36
34
|
unless layout_content.include?("odt_ui")
|
|
37
|
-
# Inject right after stylesheet_link_tag :app or in <head>
|
|
38
35
|
if layout_content.include?("<%= stylesheet_link_tag :app")
|
|
39
36
|
insert_into_file layout_file, "\n <%= stylesheet_link_tag \"odt_ui\", \"data-turbo-track\": \"reload\" %>", after: /<%= stylesheet_link_tag :app[^\n]*%>/
|
|
40
37
|
elsif layout_content.include?("<%= stylesheet_link_tag \"application\"")
|
|
@@ -56,8 +53,10 @@ module Odt
|
|
|
56
53
|
def copy_stimulus_controller
|
|
57
54
|
if File.directory?("app/javascript/controllers") || File.directory?("app/javascript")
|
|
58
55
|
empty_directory "app/javascript/controllers"
|
|
56
|
+
copy_file "odt_toaster_controller.js", "app/javascript/controllers/odt_toaster_controller.js"
|
|
59
57
|
copy_file "odt_toast_controller.js", "app/javascript/controllers/odt_toast_controller.js"
|
|
60
58
|
copy_file "odt_modal_controller.js", "app/javascript/controllers/odt_modal_controller.js"
|
|
59
|
+
copy_file "odt_dropdown_controller.js", "app/javascript/controllers/odt_dropdown_controller.js"
|
|
61
60
|
say "Copied Stimulus controllers to app/javascript/controllers/", :green
|
|
62
61
|
end
|
|
63
62
|
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["trigger", "content", "chevron", "hiddenInput", "text"]
|
|
5
|
+
static values = {
|
|
6
|
+
open: { type: Boolean, default: false },
|
|
7
|
+
closeOnClickOutside: { type: Boolean, default: true },
|
|
8
|
+
closeOnSelect: { type: Boolean, default: true }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
connect() {
|
|
12
|
+
this.boundPointerDown = (event) => {
|
|
13
|
+
if (!this.openValue || !this.closeOnClickOutsideValue) return
|
|
14
|
+
if (!this.element.contains(event.target)) {
|
|
15
|
+
this.close()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
document.addEventListener("pointerdown", this.boundPointerDown)
|
|
19
|
+
|
|
20
|
+
this.boundKeyDown = (event) => {
|
|
21
|
+
if (!this.openValue) return
|
|
22
|
+
if (event.key === "Escape") {
|
|
23
|
+
event.stopPropagation()
|
|
24
|
+
this.close()
|
|
25
|
+
if (this.hasTriggerTarget) this.triggerTarget.focus()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
window.addEventListener("keydown", this.boundKeyDown)
|
|
29
|
+
|
|
30
|
+
if (this.openValue) {
|
|
31
|
+
this.open()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
disconnect() {
|
|
36
|
+
if (this.boundPointerDown) {
|
|
37
|
+
document.removeEventListener("pointerdown", this.boundPointerDown)
|
|
38
|
+
}
|
|
39
|
+
if (this.boundKeyDown) {
|
|
40
|
+
window.removeEventListener("keydown", this.boundKeyDown)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
toggle(event) {
|
|
45
|
+
if (event) event.preventDefault()
|
|
46
|
+
if (this.openValue) {
|
|
47
|
+
this.close()
|
|
48
|
+
} else {
|
|
49
|
+
this.open()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
open(event) {
|
|
54
|
+
if (event) event.preventDefault()
|
|
55
|
+
this.openValue = true
|
|
56
|
+
|
|
57
|
+
if (this.hasContentTarget) {
|
|
58
|
+
this.contentTarget.classList.remove("hidden")
|
|
59
|
+
this.contentTarget.classList.remove("odt-dropdown__content--exiting")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (this.hasTriggerTarget) {
|
|
63
|
+
this.triggerTarget.classList.add("odt-dropdown__trigger--open")
|
|
64
|
+
this.triggerTarget.setAttribute("aria-expanded", "true")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (this.hasChevronTarget) {
|
|
68
|
+
this.chevronTarget.classList.add("odt-dropdown__chevron--open")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.element.dispatchEvent(new CustomEvent("odt-dropdown:opened", { bubbles: true }))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
close(event) {
|
|
75
|
+
if (event) event.preventDefault()
|
|
76
|
+
this.openValue = false
|
|
77
|
+
|
|
78
|
+
if (this.hasTriggerTarget) {
|
|
79
|
+
this.triggerTarget.classList.remove("odt-dropdown__trigger--open")
|
|
80
|
+
this.triggerTarget.setAttribute("aria-expanded", "false")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (this.hasChevronTarget) {
|
|
84
|
+
this.chevronTarget.classList.remove("odt-dropdown__chevron--open")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (this.hasContentTarget) {
|
|
88
|
+
this.contentTarget.classList.add("odt-dropdown__content--exiting")
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
this.contentTarget.classList.add("hidden")
|
|
91
|
+
this.contentTarget.classList.remove("odt-dropdown__content--exiting")
|
|
92
|
+
this.element.dispatchEvent(new CustomEvent("odt-dropdown:closed", { bubbles: true }))
|
|
93
|
+
}, 110)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
select(event) {
|
|
98
|
+
const item = event.currentTarget
|
|
99
|
+
const value = item.dataset.value
|
|
100
|
+
const label = item.dataset.label || item.textContent.trim()
|
|
101
|
+
|
|
102
|
+
if (this.hasHiddenInputTarget && value !== undefined) {
|
|
103
|
+
this.hiddenInputTarget.value = value
|
|
104
|
+
this.hiddenInputTarget.dispatchEvent(new Event("change", { bubbles: true }))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (this.hasTextTarget && label) {
|
|
108
|
+
this.textTarget.textContent = label
|
|
109
|
+
this.textTarget.classList.remove("odt-dropdown__placeholder")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (this.closeOnSelectValue) {
|
|
113
|
+
this.close()
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
navigate(event) {
|
|
118
|
+
if (!this.hasContentTarget || !this.openValue) return
|
|
119
|
+
|
|
120
|
+
const items = Array.from(
|
|
121
|
+
this.contentTarget.querySelectorAll('[role="menuitem"]:not([disabled]):not(.odt-dropdown__item--disabled)')
|
|
122
|
+
)
|
|
123
|
+
if (!items.length) return
|
|
124
|
+
|
|
125
|
+
const activeIndex = items.indexOf(document.activeElement)
|
|
126
|
+
|
|
127
|
+
if (event.key === "ArrowDown") {
|
|
128
|
+
event.preventDefault()
|
|
129
|
+
const nextIndex = activeIndex < items.length - 1 ? activeIndex + 1 : 0
|
|
130
|
+
items[nextIndex]?.focus()
|
|
131
|
+
} else if (event.key === "ArrowUp") {
|
|
132
|
+
event.preventDefault()
|
|
133
|
+
const prevIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1
|
|
134
|
+
items[prevIndex]?.focus()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -8,6 +8,24 @@ export default class extends Controller {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
connect() {
|
|
11
|
+
if (this.element.id) {
|
|
12
|
+
this.boundTriggerHandler = (event) => {
|
|
13
|
+
const trigger = event.target.closest(`[data-odt-modal-target-id="${this.element.id}"], [data-modal-target="${this.element.id}"], [href="#${this.element.id}"]`)
|
|
14
|
+
if (trigger) {
|
|
15
|
+
event.preventDefault()
|
|
16
|
+
this.open()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
document.addEventListener("click", this.boundTriggerHandler)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.boundTurboSubmitHandler = (event) => {
|
|
23
|
+
if (event.detail?.success && this.element.contains(event.target)) {
|
|
24
|
+
this.close()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
this.element.addEventListener("turbo:submit-end", this.boundTurboSubmitHandler)
|
|
28
|
+
|
|
11
29
|
if (this.openValue) {
|
|
12
30
|
this.open()
|
|
13
31
|
}
|
|
@@ -57,5 +75,11 @@ export default class extends Controller {
|
|
|
57
75
|
|
|
58
76
|
disconnect() {
|
|
59
77
|
document.body.style.overflow = ""
|
|
78
|
+
if (this.boundTriggerHandler) {
|
|
79
|
+
document.removeEventListener("click", this.boundTriggerHandler)
|
|
80
|
+
}
|
|
81
|
+
if (this.boundTurboSubmitHandler) {
|
|
82
|
+
this.element.removeEventListener("turbo:submit-end", this.boundTurboSubmitHandler)
|
|
83
|
+
}
|
|
60
84
|
}
|
|
61
85
|
}
|
|
@@ -7,6 +7,7 @@ export default class extends Controller {
|
|
|
7
7
|
|
|
8
8
|
connect() {
|
|
9
9
|
this.startTimer()
|
|
10
|
+
this.notifyToaster()
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
startTimer() {
|
|
@@ -26,11 +27,22 @@ export default class extends Controller {
|
|
|
26
27
|
this.startTimer()
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
notifyToaster() {
|
|
31
|
+
const toaster = this.element.closest("[data-controller~='odt-toaster']")
|
|
32
|
+
if (toaster) {
|
|
33
|
+
const toasterController = this.application.getControllerForElementAndIdentifier(toaster, "odt-toaster")
|
|
34
|
+
toasterController?.updateStack()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
dismiss() {
|
|
30
39
|
this.pauseTimer()
|
|
31
40
|
this.element.classList.add("odt-toast--exiting")
|
|
41
|
+
this.notifyToaster()
|
|
42
|
+
|
|
32
43
|
setTimeout(() => {
|
|
33
44
|
this.element.remove()
|
|
45
|
+
this.notifyToaster()
|
|
34
46
|
}, 250)
|
|
35
47
|
}
|
|
36
48
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static values = {
|
|
5
|
+
position: { type: String, default: "top-right" },
|
|
6
|
+
expand: { type: Boolean, default: true }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
this.isHovered = false
|
|
11
|
+
this.leaveTimeout = null
|
|
12
|
+
this.mutationObserver = new MutationObserver(() => this.handleMutations())
|
|
13
|
+
this.mutationObserver.observe(this.element, { childList: true })
|
|
14
|
+
this.handleMutations()
|
|
15
|
+
|
|
16
|
+
this.boundTriggerHandler = (event) => {
|
|
17
|
+
const trigger = event.target.closest("[data-odt-toast-message], [data-odt-toast-title]")
|
|
18
|
+
if (trigger) {
|
|
19
|
+
event.preventDefault()
|
|
20
|
+
this.show({
|
|
21
|
+
title: trigger.dataset.odtToastTitle,
|
|
22
|
+
message: trigger.dataset.odtToastMessage,
|
|
23
|
+
type: trigger.dataset.odtToastType || "default",
|
|
24
|
+
variant: trigger.dataset.odtToastVariant || "elevated",
|
|
25
|
+
duration: Number(trigger.dataset.odtToastDuration) || 4000
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
document.addEventListener("click", this.boundTriggerHandler)
|
|
30
|
+
|
|
31
|
+
window.odtToast = {
|
|
32
|
+
show: (options) => this.show(options),
|
|
33
|
+
success: (title, message) => this.show({ title, message, type: "success" }),
|
|
34
|
+
error: (title, message) => this.show({ title, message, type: "danger" }),
|
|
35
|
+
warning: (title, message) => this.show({ title, message, type: "warning" }),
|
|
36
|
+
info: (title, message) => this.show({ title, message, type: "info" })
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
disconnect() {
|
|
41
|
+
if (this.mutationObserver) {
|
|
42
|
+
this.mutationObserver.disconnect()
|
|
43
|
+
}
|
|
44
|
+
if (this.leaveTimeout) {
|
|
45
|
+
clearTimeout(this.leaveTimeout)
|
|
46
|
+
}
|
|
47
|
+
if (this.boundTriggerHandler) {
|
|
48
|
+
document.removeEventListener("click", this.boundTriggerHandler)
|
|
49
|
+
}
|
|
50
|
+
if (window.odtToast) {
|
|
51
|
+
delete window.odtToast
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
show(eventOrOptions = {}) {
|
|
56
|
+
const options = eventOrOptions?.params || eventOrOptions || {}
|
|
57
|
+
const title = options.title || ""
|
|
58
|
+
const message = options.message || ""
|
|
59
|
+
const type = options.type || "default"
|
|
60
|
+
const variant = options.variant || "elevated"
|
|
61
|
+
const duration = options.duration !== undefined ? Number(options.duration) : 4000
|
|
62
|
+
|
|
63
|
+
const toast = document.createElement("div")
|
|
64
|
+
toast.className = `odt-toast odt-toast--variant-${variant}`
|
|
65
|
+
toast.setAttribute("role", "status")
|
|
66
|
+
toast.setAttribute("data-controller", "odt-toast")
|
|
67
|
+
toast.setAttribute("data-odt-toast-duration-value", duration.toString())
|
|
68
|
+
|
|
69
|
+
const parts = []
|
|
70
|
+
|
|
71
|
+
const icons = {
|
|
72
|
+
success: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>',
|
|
73
|
+
info: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>',
|
|
74
|
+
warning: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
|
|
75
|
+
danger: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
|
|
76
|
+
error: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
|
|
77
|
+
loading: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-6.219-8.56"/></svg>'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (icons[type]) {
|
|
81
|
+
parts.push(`<span class="odt-toast__icon odt-toast__icon--${type}">${icons[type]}</span>`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let bodyHtml = '<div class="odt-toast__body">'
|
|
85
|
+
if (title) {
|
|
86
|
+
bodyHtml += `<div class="odt-toast__title">${title}</div>`
|
|
87
|
+
}
|
|
88
|
+
if (message) {
|
|
89
|
+
bodyHtml += `<div class="odt-toast__description">${message}</div>`
|
|
90
|
+
}
|
|
91
|
+
bodyHtml += '</div>'
|
|
92
|
+
parts.push(bodyHtml)
|
|
93
|
+
|
|
94
|
+
parts.push(`
|
|
95
|
+
<button type="button" class="odt-toast__close" aria-label="Close" data-action="click->odt-toast#dismiss">
|
|
96
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
97
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
98
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
99
|
+
</svg>
|
|
100
|
+
</button>
|
|
101
|
+
`)
|
|
102
|
+
|
|
103
|
+
toast.innerHTML = parts.join("")
|
|
104
|
+
this.element.appendChild(toast)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
handleMutations() {
|
|
108
|
+
const unmountedToasts = this.element.querySelectorAll(":scope > .odt-toast:not([data-toast-mounted]):not(.odt-toast--exiting)")
|
|
109
|
+
const isTop = this.positionValue.startsWith("top")
|
|
110
|
+
|
|
111
|
+
unmountedToasts.forEach((toast) => {
|
|
112
|
+
toast.style.transform = `translate3d(0, ${isTop ? -24 : 24}px, 0) scale(0.92)`
|
|
113
|
+
toast.style.opacity = "0"
|
|
114
|
+
toast.style.pointerEvents = "none"
|
|
115
|
+
toast.dataset.toastMounted = "pending"
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
requestAnimationFrame(() => {
|
|
119
|
+
requestAnimationFrame(() => {
|
|
120
|
+
unmountedToasts.forEach((toast) => {
|
|
121
|
+
toast.dataset.toastMounted = "true"
|
|
122
|
+
})
|
|
123
|
+
this.updateStack()
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
hover() {
|
|
129
|
+
if (!this.expandValue) return
|
|
130
|
+
if (this.leaveTimeout) {
|
|
131
|
+
clearTimeout(this.leaveTimeout)
|
|
132
|
+
this.leaveTimeout = null
|
|
133
|
+
}
|
|
134
|
+
if (!this.isHovered) {
|
|
135
|
+
this.isHovered = true
|
|
136
|
+
this.element.style.pointerEvents = "auto"
|
|
137
|
+
this.updateStack()
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
leave() {
|
|
142
|
+
if (!this.expandValue) return
|
|
143
|
+
if (this.leaveTimeout) {
|
|
144
|
+
clearTimeout(this.leaveTimeout)
|
|
145
|
+
}
|
|
146
|
+
this.leaveTimeout = setTimeout(() => {
|
|
147
|
+
this.isHovered = false
|
|
148
|
+
this.element.style.pointerEvents = "none"
|
|
149
|
+
this.updateStack()
|
|
150
|
+
}, 180)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
get toasts() {
|
|
154
|
+
return Array.from(this.element.querySelectorAll(":scope > .odt-toast:not(.odt-toast--exiting)"))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
updateStack() {
|
|
158
|
+
const toasts = this.toasts.reverse()
|
|
159
|
+
const isTop = this.positionValue.startsWith("top")
|
|
160
|
+
const GAP = 12
|
|
161
|
+
|
|
162
|
+
let accumulatedOffset = 0
|
|
163
|
+
|
|
164
|
+
toasts.forEach((toast, index) => {
|
|
165
|
+
if (toast.dataset.toastMounted === "pending") return
|
|
166
|
+
|
|
167
|
+
const isVisible = index < 3 || this.isHovered
|
|
168
|
+
const zIndex = 50 - index
|
|
169
|
+
const stackedBaseY = isTop ? index * 14 : -(index * 14)
|
|
170
|
+
const stackedBaseScale = Math.max(0.84, 1 - index * 0.06)
|
|
171
|
+
|
|
172
|
+
let targetY = 0
|
|
173
|
+
let targetScale = 1
|
|
174
|
+
let targetOpacity = 1
|
|
175
|
+
|
|
176
|
+
if (this.isHovered) {
|
|
177
|
+
targetY = isTop ? accumulatedOffset : -accumulatedOffset
|
|
178
|
+
targetScale = 1
|
|
179
|
+
targetOpacity = isVisible ? 1 : 0
|
|
180
|
+
} else {
|
|
181
|
+
targetY = stackedBaseY
|
|
182
|
+
targetScale = stackedBaseScale
|
|
183
|
+
targetOpacity = index === 0 ? 1 : index === 1 ? 0.92 : index === 2 ? 0.75 : 0
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
toast.style.transform = `translate3d(0, ${targetY}px, 0) scale(${targetScale})`
|
|
187
|
+
toast.style.opacity = targetOpacity
|
|
188
|
+
toast.style.zIndex = zIndex
|
|
189
|
+
toast.style.pointerEvents = isVisible ? "auto" : "none"
|
|
190
|
+
|
|
191
|
+
const height = toast.offsetHeight || 64
|
|
192
|
+
accumulatedOffset += height + GAP
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
}
|