daisyui 1.2.0 → 1.3.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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +52 -0
- data/app/javascript/daisy_ui/controllers/daisy_tooltip_controller.js +231 -0
- data/config/importmap.rb +2 -2
- data/lib/daisy_ui/tooltip.rb +95 -6
- data/lib/daisy_ui/updated_at.rb +1 -1
- data/lib/daisy_ui/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d6868c0a6fbec134472fd51d293059527c056b84c3606fdc460a3335e9ce09f9
|
|
4
|
+
data.tar.gz: 4eab735dbc402b5967a7d9225fc64cd6fea6a312135762aeca77e0cd46d58f72
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: be45f9a0eb9668b3eec64079656919bce38eac29ad02b6d20f1dfd7a596f7db9398f47c1633995b1091855515273f75e3e848dd244228177d1f138b98d3ae7ce
|
|
7
|
+
data.tar.gz: ca6520ba004e142524092b29c4e76622b2c05f1002d11c529659fb8874ef34344f0be5160f84f0b966d997d5913f3e6852abd673f1ae18dc4ae22cf53d285598
|
data/CHANGELOG.md
CHANGED
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
positioning fallback (Safari < 26, Firefox < 147) and optional roving
|
|
20
20
|
keyboard navigation. The default `:popover` dropdown remains zero-JS, and the
|
|
21
21
|
gem stays a plain Phlex library when Rails is absent.
|
|
22
|
+
- `Tooltip` `:popover` modifier for collision-aware tooltips. It renders real
|
|
23
|
+
tooltip content in the browser top layer, opens on hover or focus, flips and
|
|
24
|
+
shifts within the visual viewport, follows scroll/resize while open, and
|
|
25
|
+
exposes `role="tooltip"`/`aria-describedby` semantics. The bundled
|
|
26
|
+
`daisy-tooltip` Stimulus controller is auto-pinned for importmap-rails apps
|
|
27
|
+
and supports a custom controller identifier via `stimulus:`.
|
|
22
28
|
|
|
23
29
|
### Changed
|
|
24
30
|
|
data/README.md
CHANGED
|
@@ -248,6 +248,58 @@ JS-bundler (esbuild/vite/webpack) consumers: import the controller from the
|
|
|
248
248
|
gem's `app/javascript/daisy_ui/controllers/daisy_dropdown_controller.js` and
|
|
249
249
|
register it manually.
|
|
250
250
|
|
|
251
|
+
## Collision-aware `Tooltip(:popover)`
|
|
252
|
+
|
|
253
|
+
The `:popover` Tooltip modifier renders real tooltip content in the browser top
|
|
254
|
+
layer instead of a `data-tip` pseudo-element. It therefore escapes clipping
|
|
255
|
+
ancestors, and the bundled `daisy-tooltip` controller flips and shifts the
|
|
256
|
+
tooltip to remain inside the visual viewport. Geometry listeners are attached
|
|
257
|
+
only while the tooltip is open.
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
Tooltip(:popover, tip: "Helpful details") do
|
|
261
|
+
Button(:circle) { "?" }
|
|
262
|
+
end
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Directional and color modifiers compose normally:
|
|
266
|
+
|
|
267
|
+
```ruby
|
|
268
|
+
Tooltip(:popover, :bottom, :info, tip: "Saved automatically") do
|
|
269
|
+
Button(:ghost) { "Status" }
|
|
270
|
+
end
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Rich content uses the existing `content` sub-component:
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
Tooltip(:popover) do |tooltip|
|
|
277
|
+
tooltip.content(class: "max-w-64") do
|
|
278
|
+
strong { "Keyboard shortcut" }
|
|
279
|
+
kbd { "⌘ K" }
|
|
280
|
+
end
|
|
281
|
+
Button { "Commands" }
|
|
282
|
+
end
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Register the gem controllers once with
|
|
286
|
+
`lazyLoadControllersFrom("daisy_ui/controllers", application)`, as shown in the
|
|
287
|
+
dropdown controller section above. Importmap-rails applications receive the
|
|
288
|
+
controller pin from the gem automatically. Bundler applications can import
|
|
289
|
+
`app/javascript/daisy_ui/controllers/daisy_tooltip_controller.js` and register
|
|
290
|
+
it as `daisy-tooltip`.
|
|
291
|
+
|
|
292
|
+
Popover tooltips:
|
|
293
|
+
|
|
294
|
+
- open on pointer hover or keyboard focus and close on leave, blur, or Escape;
|
|
295
|
+
- use `role="tooltip"` and merge their id into the first interactive trigger's
|
|
296
|
+
`aria-describedby`;
|
|
297
|
+
- preserve caller-supplied Stimulus controllers;
|
|
298
|
+
- accept `popover_id:` for a stable tooltip id and `stimulus: "your-id"` to
|
|
299
|
+
override the controller identifier.
|
|
300
|
+
|
|
301
|
+
Classic `Tooltip(tip: ...)` rendering remains unchanged.
|
|
302
|
+
|
|
251
303
|
# MCP Server (Claude Code Integration)
|
|
252
304
|
|
|
253
305
|
This gem includes an MCP (Model Context Protocol) server that provides component information to AI assistants like Claude Code.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
const VIEWPORT_PADDING = 8
|
|
4
|
+
const GAP = 8
|
|
5
|
+
const ARROW_WIDTH = 10
|
|
6
|
+
const ARROW_HEIGHT = 4
|
|
7
|
+
const FOCUSABLE_SELECTOR = 'button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
8
|
+
const OPPOSITE = { top: "bottom", bottom: "top", left: "right", right: "left" }
|
|
9
|
+
|
|
10
|
+
// daisy-tooltip — positions a :popover Tooltip in the browser top layer.
|
|
11
|
+
//
|
|
12
|
+
// The Popover API escapes clipping ancestors. This controller supplies the
|
|
13
|
+
// hover/focus lifecycle expected of a tooltip, then flips and shifts the real
|
|
14
|
+
// tooltip element only while it is open so it remains inside the visual
|
|
15
|
+
// viewport. No global listeners remain attached while the tooltip is closed.
|
|
16
|
+
export default class extends Controller {
|
|
17
|
+
static targets = ["content", "arrow"]
|
|
18
|
+
static values = { placement: { type: String, default: "top" } }
|
|
19
|
+
|
|
20
|
+
connect() {
|
|
21
|
+
if (!this.hasContentTarget) return
|
|
22
|
+
|
|
23
|
+
this.originalMaxWidth = this.contentTarget.style.maxWidth
|
|
24
|
+
this.onPointerEnter = () => this.show()
|
|
25
|
+
this.onPointerLeave = () => this.#hideUnlessFocused()
|
|
26
|
+
this.onFocusIn = () => this.show()
|
|
27
|
+
this.onFocusOut = (event) => this.#handleFocusOut(event)
|
|
28
|
+
this.onKeydown = (event) => this.#handleKeydown(event)
|
|
29
|
+
this.onGeometryChange = () => this.#schedulePosition()
|
|
30
|
+
|
|
31
|
+
this.element.addEventListener("pointerenter", this.onPointerEnter)
|
|
32
|
+
this.element.addEventListener("pointerleave", this.onPointerLeave)
|
|
33
|
+
this.element.addEventListener("focusin", this.onFocusIn)
|
|
34
|
+
this.element.addEventListener("focusout", this.onFocusOut)
|
|
35
|
+
this.#describeTrigger()
|
|
36
|
+
|
|
37
|
+
if (this.element.classList.contains("tooltip-open")) this.show()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
disconnect() {
|
|
41
|
+
this.element.removeEventListener("pointerenter", this.onPointerEnter)
|
|
42
|
+
this.element.removeEventListener("pointerleave", this.onPointerLeave)
|
|
43
|
+
this.element.removeEventListener("focusin", this.onFocusIn)
|
|
44
|
+
this.element.removeEventListener("focusout", this.onFocusOut)
|
|
45
|
+
this.#teardownOpen()
|
|
46
|
+
this.#restoreDescription()
|
|
47
|
+
if (this.hasContentTarget) this.contentTarget.style.maxWidth = this.originalMaxWidth
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
show() {
|
|
51
|
+
if (!this.hasContentTarget || this.#isOpen()) return
|
|
52
|
+
|
|
53
|
+
this.contentTarget.style.visibility = "hidden"
|
|
54
|
+
this.contentTarget.showPopover()
|
|
55
|
+
this.#setupOpen()
|
|
56
|
+
this.#schedulePosition()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
hide() {
|
|
60
|
+
if (this.#isOpen()) this.contentTarget.hidePopover()
|
|
61
|
+
this.contentTarget.style.visibility = "hidden"
|
|
62
|
+
this.#teardownOpen()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#setupOpen() {
|
|
66
|
+
window.addEventListener("resize", this.onGeometryChange)
|
|
67
|
+
window.addEventListener("scroll", this.onGeometryChange, true)
|
|
68
|
+
window.addEventListener("keydown", this.onKeydown)
|
|
69
|
+
window.visualViewport?.addEventListener("resize", this.onGeometryChange)
|
|
70
|
+
window.visualViewport?.addEventListener("scroll", this.onGeometryChange)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#teardownOpen() {
|
|
74
|
+
cancelAnimationFrame(this.frame)
|
|
75
|
+
window.removeEventListener("resize", this.onGeometryChange)
|
|
76
|
+
window.removeEventListener("scroll", this.onGeometryChange, true)
|
|
77
|
+
window.removeEventListener("keydown", this.onKeydown)
|
|
78
|
+
window.visualViewport?.removeEventListener("resize", this.onGeometryChange)
|
|
79
|
+
window.visualViewport?.removeEventListener("scroll", this.onGeometryChange)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#schedulePosition() {
|
|
83
|
+
cancelAnimationFrame(this.frame)
|
|
84
|
+
this.frame = requestAnimationFrame(() => this.#position())
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#position() {
|
|
88
|
+
if (!this.#isOpen()) return
|
|
89
|
+
|
|
90
|
+
const viewport = this.#viewport()
|
|
91
|
+
const availableWidth = Math.max(0, viewport.width - VIEWPORT_PADDING * 2)
|
|
92
|
+
this.contentTarget.style.maxWidth = this.originalMaxWidth
|
|
93
|
+
const computedMaxWidth = Number.parseFloat(getComputedStyle(this.contentTarget).maxWidth)
|
|
94
|
+
const maxWidth = Number.isFinite(computedMaxWidth) ? Math.min(computedMaxWidth, availableWidth) : availableWidth
|
|
95
|
+
this.contentTarget.style.maxWidth = `${maxWidth}px`
|
|
96
|
+
|
|
97
|
+
const trigger = this.element.getBoundingClientRect()
|
|
98
|
+
const tooltip = this.contentTarget.getBoundingClientRect()
|
|
99
|
+
const preferred = this.#coordinates(this.placementValue, trigger, tooltip)
|
|
100
|
+
const oppositePlacement = OPPOSITE[this.placementValue] || "bottom"
|
|
101
|
+
const opposite = this.#coordinates(oppositePlacement, trigger, tooltip)
|
|
102
|
+
const useOpposite = this.#overflow(opposite, tooltip, viewport) < this.#overflow(preferred, tooltip, viewport)
|
|
103
|
+
const placement = useOpposite ? oppositePlacement : this.placementValue
|
|
104
|
+
const coordinates = useOpposite ? opposite : preferred
|
|
105
|
+
const { x, y } = this.#shift(coordinates, tooltip, viewport)
|
|
106
|
+
|
|
107
|
+
Object.assign(this.contentTarget.style, { left: `${x}px`, top: `${y}px`, visibility: "visible" })
|
|
108
|
+
this.#positionArrow(placement, trigger, tooltip, x, y)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#coordinates(placement, trigger, tooltip) {
|
|
112
|
+
const centerX = trigger.left + trigger.width / 2
|
|
113
|
+
const centerY = trigger.top + trigger.height / 2
|
|
114
|
+
|
|
115
|
+
switch (placement) {
|
|
116
|
+
case "bottom":
|
|
117
|
+
return { x: centerX - tooltip.width / 2, y: trigger.bottom + GAP }
|
|
118
|
+
case "left":
|
|
119
|
+
return { x: trigger.left - tooltip.width - GAP, y: centerY - tooltip.height / 2 }
|
|
120
|
+
case "right":
|
|
121
|
+
return { x: trigger.right + GAP, y: centerY - tooltip.height / 2 }
|
|
122
|
+
default:
|
|
123
|
+
return { x: centerX - tooltip.width / 2, y: trigger.top - tooltip.height - GAP }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#overflow({ x, y }, tooltip, viewport) {
|
|
128
|
+
const left = viewport.left + VIEWPORT_PADDING
|
|
129
|
+
const right = viewport.left + viewport.width - VIEWPORT_PADDING
|
|
130
|
+
const top = viewport.top + VIEWPORT_PADDING
|
|
131
|
+
const bottom = viewport.top + viewport.height - VIEWPORT_PADDING
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
Math.max(0, left - x) +
|
|
135
|
+
Math.max(0, x + tooltip.width - right) +
|
|
136
|
+
Math.max(0, top - y) +
|
|
137
|
+
Math.max(0, y + tooltip.height - bottom)
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#shift({ x, y }, tooltip, viewport) {
|
|
142
|
+
const left = viewport.left + VIEWPORT_PADDING
|
|
143
|
+
const right = viewport.left + viewport.width - VIEWPORT_PADDING
|
|
144
|
+
const top = viewport.top + VIEWPORT_PADDING
|
|
145
|
+
const bottom = viewport.top + viewport.height - VIEWPORT_PADDING
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
x: tooltip.width > right - left ? left : Math.min(Math.max(x, left), right - tooltip.width),
|
|
149
|
+
y: tooltip.height > bottom - top ? top : Math.min(Math.max(y, top), bottom - tooltip.height),
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#positionArrow(placement, trigger, tooltip, x, y) {
|
|
154
|
+
if (!this.hasArrowTarget) return
|
|
155
|
+
|
|
156
|
+
const centerX = trigger.left + trigger.width / 2 - x
|
|
157
|
+
const centerY = trigger.top + trigger.height / 2 - y
|
|
158
|
+
const arrow = this.arrowTarget.style
|
|
159
|
+
Object.assign(arrow, { inset: "auto", transform: "none" })
|
|
160
|
+
|
|
161
|
+
if (placement === "top" || placement === "bottom") {
|
|
162
|
+
arrow.left = `${this.#clamp(centerX - ARROW_WIDTH / 2, 4, tooltip.width - ARROW_WIDTH - 4)}px`
|
|
163
|
+
if (placement === "top") arrow.bottom = `${-ARROW_HEIGHT}px`
|
|
164
|
+
else {
|
|
165
|
+
arrow.top = `${-ARROW_HEIGHT}px`
|
|
166
|
+
arrow.transform = "rotate(180deg)"
|
|
167
|
+
}
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
arrow.top = `${this.#clamp(centerY - ARROW_HEIGHT / 2, 4, tooltip.height - ARROW_HEIGHT - 4)}px`
|
|
172
|
+
if (placement === "left") {
|
|
173
|
+
arrow.right = "-7px"
|
|
174
|
+
arrow.transform = "rotate(-90deg)"
|
|
175
|
+
} else {
|
|
176
|
+
arrow.left = "-7px"
|
|
177
|
+
arrow.transform = "rotate(90deg)"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#viewport() {
|
|
182
|
+
const visualViewport = window.visualViewport
|
|
183
|
+
return {
|
|
184
|
+
left: visualViewport?.offsetLeft ?? 0,
|
|
185
|
+
top: visualViewport?.offsetTop ?? 0,
|
|
186
|
+
width: visualViewport?.width ?? window.innerWidth,
|
|
187
|
+
height: visualViewport?.height ?? window.innerHeight,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#handleFocusOut(event) {
|
|
192
|
+
if (!this.element.contains(event.relatedTarget)) this.hide()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#hideUnlessFocused() {
|
|
196
|
+
if (!this.element.matches(":focus-within")) this.hide()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#handleKeydown(event) {
|
|
200
|
+
if (event.key !== "Escape") return
|
|
201
|
+
|
|
202
|
+
this.hide()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
#describeTrigger() {
|
|
206
|
+
const trigger = this.element.matches(FOCUSABLE_SELECTOR)
|
|
207
|
+
? this.element
|
|
208
|
+
: this.element.querySelector(FOCUSABLE_SELECTOR)
|
|
209
|
+
if (!trigger || !this.contentTarget.id) return
|
|
210
|
+
|
|
211
|
+
this.describedTrigger = trigger
|
|
212
|
+
this.previousDescription = trigger.getAttribute("aria-describedby")
|
|
213
|
+
const descriptions = this.previousDescription?.split(/\s+/).filter(Boolean) || []
|
|
214
|
+
trigger.setAttribute("aria-describedby", [...new Set([...descriptions, this.contentTarget.id])].join(" "))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#restoreDescription() {
|
|
218
|
+
if (!this.describedTrigger) return
|
|
219
|
+
|
|
220
|
+
if (this.previousDescription === null) this.describedTrigger.removeAttribute("aria-describedby")
|
|
221
|
+
else this.describedTrigger.setAttribute("aria-describedby", this.previousDescription)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#isOpen() {
|
|
225
|
+
return this.hasContentTarget && this.contentTarget.matches(":popover-open")
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#clamp(value, minimum, maximum) {
|
|
229
|
+
return Math.min(Math.max(value, minimum), Math.max(minimum, maximum))
|
|
230
|
+
}
|
|
231
|
+
}
|
data/config/importmap.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Auto-pins the gem's bundled Stimulus controllers for importmap-rails consumers.
|
|
4
|
-
# Exposes `
|
|
5
|
-
# host app (lazily recommended):
|
|
4
|
+
# Exposes the bundled `daisy-dropdown` and `daisy-tooltip` controllers. Register
|
|
5
|
+
# them in the host app (lazily recommended):
|
|
6
6
|
#
|
|
7
7
|
# // app/javascript/controllers/index.js
|
|
8
8
|
# import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
|
data/lib/daisy_ui/tooltip.rb
CHANGED
|
@@ -1,28 +1,117 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
3
5
|
module DaisyUI
|
|
4
6
|
class Tooltip < Base
|
|
5
7
|
self.component_class = :tooltip
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
PLACEMENTS = %i[top bottom left right].freeze
|
|
10
|
+
DEFAULT_STIMULUS_IDENTIFIER = "daisy-tooltip"
|
|
11
|
+
POPOVER_STYLE = "position:fixed;inset:auto;margin:0;border:0;opacity:1;transform:none;pointer-events:none;visibility:hidden"
|
|
12
|
+
ARROW_STYLE = "position:absolute;width:.625rem;height:.25rem;background:var(--tt-bg);" \
|
|
13
|
+
"clip-path:polygon(0 0,100% 0,50% 100%);pointer-events:none"
|
|
14
|
+
|
|
15
|
+
def initialize(*modifiers, tip: nil, as: :div, popover_id: nil, stimulus: true, **options)
|
|
16
|
+
@popover = modifiers.include?(:popover)
|
|
17
|
+
@popover_id = popover_id if @popover
|
|
18
|
+
@stimulus = stimulus
|
|
9
19
|
@tip = tip
|
|
20
|
+
if popover?
|
|
21
|
+
validate_stimulus!
|
|
22
|
+
boolean_placements = PLACEMENTS.select { |candidate| options[candidate] == true }
|
|
23
|
+
wire_controller(options, modifiers + boolean_placements)
|
|
24
|
+
end
|
|
25
|
+
super(*modifiers, as:, **options)
|
|
10
26
|
end
|
|
11
27
|
|
|
12
28
|
def view_template(&)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
29
|
+
if popover?
|
|
30
|
+
public_send(as, class: merge_classes(classes, "after:hidden"), **attributes) do
|
|
31
|
+
yield self if block_given?
|
|
32
|
+
content { plain tip } if tip && !@content_rendered
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
opts = { class: classes, **attributes }
|
|
36
|
+
opts[:data_tip] = tip if tip
|
|
37
|
+
public_send(as, **opts, &)
|
|
38
|
+
end
|
|
16
39
|
end
|
|
17
40
|
|
|
18
41
|
def content(**options, &)
|
|
19
|
-
|
|
42
|
+
if popover?
|
|
43
|
+
@content_rendered = true
|
|
44
|
+
options[:id] ||= popover_id
|
|
45
|
+
options[:popover] = "manual"
|
|
46
|
+
options[:role] ||= "tooltip"
|
|
47
|
+
options[:style] = merge_style(options[:style], POPOVER_STYLE)
|
|
48
|
+
merge_stimulus_target(options, "content")
|
|
49
|
+
|
|
50
|
+
div(class: component_classes("tooltip-content", options:), **options) do
|
|
51
|
+
span(
|
|
52
|
+
aria_hidden: "true",
|
|
53
|
+
data: { stimulus_target_key => "arrow" },
|
|
54
|
+
style: ARROW_STYLE
|
|
55
|
+
)
|
|
56
|
+
yield if block_given?
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
div(class: component_classes("tooltip-content", options:), **options, &)
|
|
60
|
+
end
|
|
20
61
|
end
|
|
21
62
|
|
|
22
63
|
private
|
|
23
64
|
|
|
24
65
|
attr_reader :tip
|
|
25
66
|
|
|
67
|
+
def popover?
|
|
68
|
+
@popover
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def popover_id
|
|
72
|
+
@popover_id ||= "tooltip_#{SecureRandom.hex(8)}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def stimulus_identifier
|
|
76
|
+
return DEFAULT_STIMULUS_IDENTIFIER if @stimulus == true
|
|
77
|
+
|
|
78
|
+
@stimulus.to_s
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def validate_stimulus!
|
|
82
|
+
valid_identifier = (@stimulus.is_a?(String) || @stimulus.is_a?(Symbol)) && @stimulus.to_s.match?(/\A\S+\z/)
|
|
83
|
+
return if @stimulus == true || valid_identifier
|
|
84
|
+
|
|
85
|
+
raise ArgumentError, "stimulus must be true or a non-empty controller identifier in popover mode"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def stimulus_target_key
|
|
89
|
+
:"#{stimulus_identifier}_target"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def placement(source_modifiers = modifiers)
|
|
93
|
+
PLACEMENTS.find { |candidate| source_modifiers.include?(candidate) } || :top
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def wire_controller(options, source_modifiers)
|
|
97
|
+
data = (options[:data] || {}).dup
|
|
98
|
+
controllers = data[:controller].to_s.split
|
|
99
|
+
data[:controller] = (controllers + [stimulus_identifier]).uniq.join(" ")
|
|
100
|
+
data[:"#{stimulus_identifier}_placement_value"] = placement(source_modifiers)
|
|
101
|
+
options[:data] = data
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def merge_stimulus_target(options, target)
|
|
105
|
+
data = (options[:data] || {}).dup
|
|
106
|
+
targets = data[stimulus_target_key].to_s.split
|
|
107
|
+
data[stimulus_target_key] = (targets + [target]).uniq.join(" ")
|
|
108
|
+
options[:data] = data
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def merge_style(existing, added)
|
|
112
|
+
[existing&.chomp(";"), added].compact.join(";")
|
|
113
|
+
end
|
|
114
|
+
|
|
26
115
|
register_modifiers(
|
|
27
116
|
# "sm:tooltip-open"
|
|
28
117
|
# "@sm:tooltip-open"
|
data/lib/daisy_ui/updated_at.rb
CHANGED
data/lib/daisy_ui/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: daisyui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -56,6 +56,7 @@ files:
|
|
|
56
56
|
- LICENSE.txt
|
|
57
57
|
- README.md
|
|
58
58
|
- app/javascript/daisy_ui/controllers/daisy_dropdown_controller.js
|
|
59
|
+
- app/javascript/daisy_ui/controllers/daisy_tooltip_controller.js
|
|
59
60
|
- config/importmap.rb
|
|
60
61
|
- exe/daisyui-mcp
|
|
61
62
|
- lib/daisy_ui.rb
|