daisyui 2.0.1 → 2.1.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 +78 -1
- data/README.md +81 -1
- data/app/javascript/daisy_ui/controllers/daisy_dropdown_controller.min.js +4 -0
- data/app/javascript/daisy_ui/controllers/daisy_dropdown_controller.min.js.map +10 -0
- data/app/javascript/daisy_ui/controllers/daisy_tooltip_controller.min.js +4 -0
- data/app/javascript/daisy_ui/controllers/daisy_tooltip_controller.min.js.map +10 -0
- data/config/importmap.rb +12 -3
- data/lib/daisy_ui/base.rb +87 -9
- data/lib/daisy_ui/chat.rb +1 -0
- data/lib/daisy_ui/checkbox.rb +1 -0
- data/lib/daisy_ui/class_merge/configuration.rb +18 -3
- data/lib/daisy_ui/class_merge/daisy_groups.rb +8 -5
- data/lib/daisy_ui/class_merge/tailwind_config.rb +8 -18
- data/lib/daisy_ui/class_merge/validators.rb +4 -0
- data/lib/daisy_ui/class_merge.rb +23 -12
- data/lib/daisy_ui/collapsible_sub_menu.rb +1 -0
- data/lib/daisy_ui/configurable/report_log.rb +36 -0
- data/lib/daisy_ui/configurable.rb +129 -5
- data/lib/daisy_ui/dropdown.rb +1 -0
- data/lib/daisy_ui/engine.rb +26 -9
- data/lib/daisy_ui/indicator.rb +1 -0
- data/lib/daisy_ui/mcp_server.rb +1 -0
- data/lib/daisy_ui/modal.rb +1 -0
- data/lib/daisy_ui/shareable.rb +56 -0
- data/lib/daisy_ui/steps.rb +1 -0
- data/lib/daisy_ui/store.rb +71 -0
- data/lib/daisy_ui/sub_menu.rb +2 -0
- data/lib/daisy_ui/tab.rb +6 -0
- data/lib/daisy_ui/tab_with_content.rb +1 -0
- data/lib/daisy_ui/tab_without_content.rb +1 -0
- data/lib/daisy_ui/tooltip.rb +1 -0
- data/lib/daisy_ui/unknown_modifier_error.rb +31 -0
- data/lib/daisy_ui/updated_at.rb +1 -1
- data/lib/daisy_ui/version.rb +1 -1
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 41fa98f82c0377c4615fa5e4a1091ae8383688a951acb726122866cefa273665
|
|
4
|
+
data.tar.gz: 651713886837f46040942fc397089d1194dc35787ab1ba18308db97f12d94a06
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: be24df2be52d01adbda5f9e257650d788352a0dfd286bee5c734dfce66aee0377dd0ad601269363ffd734d908cd0c2cde612062017dc59fde61086f4f53e716c
|
|
7
|
+
data.tar.gz: 2320af028c2539b849de1feb7c1d2690cc6ca0e75da24bd7d838415171c4d922f59b26f5833e1cc1a1e035c330ce532eaae75e21107bd89119f24653f7ab3eb4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,73 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [2.1.0]
|
|
2
|
+
|
|
3
|
+
### DaisyUI finalizes itself at boot: class merging in any Ractor (#58)
|
|
4
|
+
|
|
5
|
+
At boot DaisyUI now freezes its configuration and every component's
|
|
6
|
+
registries, so a non-main Ractor can read them. `DaisyUI::ClassMerge.merge`
|
|
7
|
+
then works in any Ractor; each Ractor builds its own merger and LRU.
|
|
8
|
+
|
|
9
|
+
- Automatic: `DaisyUI.configure` finalizes at its end, and the Rails engine
|
|
10
|
+
finalizes again in `after_initialize`, with no app code. A Rails-less app
|
|
11
|
+
that never calls `configure` calls `DaisyUI.finalize!` at the end of boot.
|
|
12
|
+
- **The configuration is frozen after `DaisyUI.configure` (and, in a Rails
|
|
13
|
+
app, after boot).** Writing to `DaisyUI.configuration` directly now raises
|
|
14
|
+
`FrozenError`, e.g. `DaisyUI.configuration.unknown_modifiers = :ignore` in a
|
|
15
|
+
spec: use `DaisyUI.configure { |config| config.unknown_modifiers = :ignore }`.
|
|
16
|
+
`DaisyUI.configure` can be called again at any time: it yields a writable
|
|
17
|
+
copy and publishes it once that is finalized, so no reader ever meets a
|
|
18
|
+
half-changed configuration, and a block that raises changes nothing. A
|
|
19
|
+
Ractor that already built its merger rebuilds it from the new
|
|
20
|
+
configuration.
|
|
21
|
+
- Finalizing never raises. A configured value that cannot be made shareable
|
|
22
|
+
is kept as it is, unfrozen, and the configuration is then readable on the
|
|
23
|
+
main Ractor only: a callable `unknown_modifiers` or a raw `class_merge`
|
|
24
|
+
validator that closes over app state (such as
|
|
25
|
+
`->(error) { Appsignal.report_error(error) }` in an initializer), or an
|
|
26
|
+
object that holds a logger.
|
|
27
|
+
- Component registries (`register_modifiers`, `register_flags`,
|
|
28
|
+
`self.component_class =`) are frozen as they are written, so a component
|
|
29
|
+
that loads after boot (lazy loading in development) is readable from any
|
|
30
|
+
Ractor too. Writers now replace them instead of mutating them, and
|
|
31
|
+
mutating one in place raises `FrozenError`.
|
|
32
|
+
- Thread safety: the Tailwind class groups behind `ClassMerge` were built
|
|
33
|
+
with an unguarded `||=` (8 threads built them 8 times); they are now built
|
|
34
|
+
once per Ractor.
|
|
35
|
+
- Rendering components inside a Ractor waits on Phlex
|
|
36
|
+
([yippee-fun/phlex#1055](https://github.com/yippee-fun/phlex/pull/1055)).
|
|
37
|
+
|
|
38
|
+
## [2.0.2] and earlier
|
|
39
|
+
|
|
40
|
+
### Unknown modifiers are reported (#54)
|
|
41
|
+
|
|
42
|
+
A positional or responsive modifier a component does not know used to render
|
|
43
|
+
nothing, silently. `config.unknown_modifiers` now decides what happens:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
DaisyUI.configure do |config|
|
|
47
|
+
config.unknown_modifiers = :raise # :raise | :warn | :ignore | ->(error) { ... }
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- The default is `:raise` under Rails in development and test, and `:warn`
|
|
52
|
+
everywhere else (production, plain Ruby). A Rails app's test suite can go red
|
|
53
|
+
after upgrading if it passes a modifier a component never had, e.g.
|
|
54
|
+
`Toggle(:checked)` or `Radio(:checked)` (use `checked: true`): fix the call,
|
|
55
|
+
register the modifier, or set `:warn`/`:ignore`.
|
|
56
|
+
- `:raise` raises `DaisyUI::UnknownModifierError` (an `ArgumentError`) naming
|
|
57
|
+
the component, the modifier and the closest registered ones.
|
|
58
|
+
- `:warn` and a callable fire once per component (or part, so `Chat` and
|
|
59
|
+
`Chat#bubble` count separately) and modifier. Thread-safe; the memory of what
|
|
60
|
+
was reported resets after 1,000 distinct keys, so a modifier may be reported
|
|
61
|
+
again after that. Use a callable to send it to an
|
|
62
|
+
error tracker:
|
|
63
|
+
`config.unknown_modifiers = ->(error) { Appsignal.report_error(error) }`.
|
|
64
|
+
- Known means the component's `register_modifiers`, `config.modifiers.add`, and
|
|
65
|
+
the new `register_flags` for positional symbols a component handles itself
|
|
66
|
+
without a class (Dropdown's `:popover`, Modal's `:tap_outside_to_close`, ...).
|
|
67
|
+
Flags are not valid in `responsive:`.
|
|
68
|
+
- `Chat#bubble`, `Steps#step` and `Indicator#item` check their modifiers too
|
|
69
|
+
(`DaisyUI::Chat#bubble has no modifier :primray`).
|
|
70
|
+
- Keyword booleans are not validated: unknown keywords stay HTML attributes.
|
|
2
71
|
|
|
3
72
|
### Upgrading to 2.0
|
|
4
73
|
|
|
@@ -67,6 +136,12 @@ wins. `Button(:sm, class: "btn-lg")` renders `btn btn-lg` (1.x rendered
|
|
|
67
136
|
|
|
68
137
|
### Changed
|
|
69
138
|
|
|
139
|
+
- The gem ships minified Stimulus controllers with linked sourcemaps, and the
|
|
140
|
+
importmap pins them without preloading, so with the lazy registration the
|
|
141
|
+
README shows (`lazyLoadControllersFrom`) a page that never uses one never
|
|
142
|
+
fetches it. `daisy_dropdown_controller` 6.1 KB → 2.8 KB (2.4 → 1.3 KB gzipped),
|
|
143
|
+
`daisy_tooltip_controller` 8.8 KB → 5.2 KB (2.4 → 1.8 KB gzipped). The
|
|
144
|
+
`daisy_ui/controllers/*` specifiers and the bundler import paths are unchanged.
|
|
70
145
|
- Maintainers cut releases with `bin/release` (`list`, `--dry-run`, `patch`/`minor`/`major`/`X.Y.Z`), which checks the lockfiles for gems that do not accept the new version yet and then drives `rake release`.
|
|
71
146
|
|
|
72
147
|
- Range component: added range-vertical modifier for vertical orientation (DaisyUI 5.6)
|
|
@@ -74,6 +149,8 @@ wins. `Button(:sm, class: "btn-lg")` renders `btn btn-lg` (1.x rendered
|
|
|
74
149
|
|
|
75
150
|
### Fixed
|
|
76
151
|
|
|
152
|
+
- Modifiers added with `config.modifiers.add(component: X)` now apply to
|
|
153
|
+
subclasses of `X` too; the closest class wins.
|
|
77
154
|
- Popover tooltips no longer clip arrows that extend beyond the tooltip card.
|
|
78
155
|
- Button renders the `disabled` attribute on `button`/`input` elements when given
|
|
79
156
|
`:disabled` or `disabled: true`, so a disabled submit button no longer submits
|
data/README.md
CHANGED
|
@@ -177,6 +177,77 @@ arrays, `nil` and `false`; returns a frozen String). Opt out globally with
|
|
|
177
177
|
`config.class_merge.enabled = false`, or per component by overriding the
|
|
178
178
|
private `merge_classes`.
|
|
179
179
|
|
|
180
|
+
## Unknown modifiers
|
|
181
|
+
|
|
182
|
+
A positional or responsive modifier the component does not know
|
|
183
|
+
(`Toggle(:primray)`) is reported instead of silently rendering nothing:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
DaisyUI.configure do |config|
|
|
187
|
+
# Default: :raise under Rails in development/test, :warn everywhere else.
|
|
188
|
+
config.unknown_modifiers = :raise # raises DaisyUI::UnknownModifierError
|
|
189
|
+
# Or :warn (Kernel#warn), :ignore, or a callable such as
|
|
190
|
+
# ->(error) { Appsignal.report_error(error) }. :warn and a callable fire once
|
|
191
|
+
# per component (or sub-component part, e.g. Chat#bubble) and modifier; the
|
|
192
|
+
# memory of what was reported resets after 1,000 distinct keys.
|
|
193
|
+
end
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Known modifiers are the component's `register_modifiers`, `config.modifiers.add`
|
|
197
|
+
(for the component or any DaisyUI ancestor), and `register_flags`: positional
|
|
198
|
+
symbols your component handles itself without emitting a class. Flags are not
|
|
199
|
+
valid in `responsive:`, since they have no class to prefix. Sub-component
|
|
200
|
+
methods with their own modifiers (`Chat#bubble`, `Steps#step`,
|
|
201
|
+
`Indicator#item`) are checked too.
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
class Widget < DaisyUI::Base
|
|
205
|
+
register_flags :collapsed # Widget(:collapsed) is valid; read it via `modifiers.include?(:collapsed)`
|
|
206
|
+
end
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Keyword arguments are not validated: `Toggle(primray: true)` renders a
|
|
210
|
+
`primray` attribute, because unknown keywords are HTML attributes.
|
|
211
|
+
|
|
212
|
+
## Threads, fibers and Ractors
|
|
213
|
+
|
|
214
|
+
Components and `DaisyUI::ClassMerge` are safe to share between threads (Puma)
|
|
215
|
+
and fibers (Falcon). For Ractors, DaisyUI finalizes itself at boot: it freezes
|
|
216
|
+
the configuration and every component's registries, so
|
|
217
|
+
`DaisyUI::ClassMerge.merge` runs in any Ractor (as long as the configuration
|
|
218
|
+
is shareable, see below), each Ractor with a merger of its own.
|
|
219
|
+
`DaisyUI.configure` finalizes at its end and the Rails engine
|
|
220
|
+
finalizes again once the app has booted, so there is nothing to call. A
|
|
221
|
+
Rails-less app that never calls `configure` calls `DaisyUI.finalize!` at the
|
|
222
|
+
end of boot.
|
|
223
|
+
|
|
224
|
+
After that the configuration is frozen: writing to `DaisyUI.configuration`
|
|
225
|
+
directly raises `FrozenError`. `DaisyUI.configure` keeps working at any time:
|
|
226
|
+
it yields a writable copy and publishes it once that is finalized, and a
|
|
227
|
+
Ractor that already built its merger rebuilds it from the new configuration.
|
|
228
|
+
A test that changes a setting goes through it too:
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
around do |example|
|
|
232
|
+
DaisyUI.configure { |config| config.unknown_modifiers = :ignore }
|
|
233
|
+
example.run
|
|
234
|
+
ensure
|
|
235
|
+
DaisyUI.configure { |config| config.unknown_modifiers = :raise }
|
|
236
|
+
end
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Finalizing never raises. A configured value that cannot be made shareable is
|
|
240
|
+
kept as it is, and the configuration is then readable on the main Ractor only:
|
|
241
|
+
a callable `unknown_modifiers` or a raw `class_merge` validator that closes
|
|
242
|
+
over app state (`->(error) { Appsignal.report_error(error) }` in an
|
|
243
|
+
initializer), or an object that holds a logger. A lambda is shareable when it
|
|
244
|
+
captures no local variables and is defined inside a module, or made with
|
|
245
|
+
`Ractor.shareable_lambda` on Ruby 4.0. A component that loads after boot (lazy
|
|
246
|
+
loading in development) freezes its registries as it writes them, so any
|
|
247
|
+
Ractor can read it; a Ractor that already built its merger picks up its groups
|
|
248
|
+
after the next `configure`. Rendering components inside a Ractor waits on Phlex
|
|
249
|
+
([yippee-fun/phlex#1055](https://github.com/yippee-fun/phlex/pull/1055)).
|
|
250
|
+
|
|
180
251
|
# Compatibility Notes
|
|
181
252
|
|
|
182
253
|
## @tailwindcss/forms plugin
|
|
@@ -270,6 +341,10 @@ import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
|
|
|
270
341
|
lazyLoadControllersFrom("daisy_ui/controllers", application)
|
|
271
342
|
```
|
|
272
343
|
|
|
344
|
+
The pin resolves to a minified build with a linked sourcemap (devtools shows the
|
|
345
|
+
readable source) and is not preloaded: a page fetches the controller the first
|
|
346
|
+
time one of its elements appears.
|
|
347
|
+
|
|
273
348
|
Then opt in per call site:
|
|
274
349
|
|
|
275
350
|
```ruby
|
|
@@ -343,7 +418,7 @@ end
|
|
|
343
418
|
Register the gem controllers once with
|
|
344
419
|
`lazyLoadControllersFrom("daisy_ui/controllers", application)`, as shown in the
|
|
345
420
|
dropdown controller section above. Importmap-rails applications receive the
|
|
346
|
-
controller pin from the gem automatically. Bundler applications can import
|
|
421
|
+
controller pin (minified, not preloaded) from the gem automatically. Bundler applications can import
|
|
347
422
|
`app/javascript/daisy_ui/controllers/daisy_tooltip_controller.js` and register
|
|
348
423
|
it as `daisy-tooltip`.
|
|
349
424
|
|
|
@@ -416,6 +491,11 @@ Which produces:
|
|
|
416
491
|
|
|
417
492
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
418
493
|
|
|
494
|
+
After editing a Stimulus controller in `app/javascript/daisy_ui/controllers/`,
|
|
495
|
+
rebuild its minified twin with `bundle exec rake build:js` (needs the bun in
|
|
496
|
+
`.bun-version`) and commit the `.min.js` and `.min.js.map`. CI runs
|
|
497
|
+
`bundle exec rake build:js_check` and fails when they are stale.
|
|
498
|
+
|
|
419
499
|
# Contributing
|
|
420
500
|
|
|
421
501
|
Bug reports and pull requests are welcome on GitHub at https://github.com/mhenrixon/daisyui. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/mhenrixon/daisyui/blob/main/CODE_OF_CONDUCT.md).
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{Controller as l}from"@hotwired/stimulus";var h=4,d='[role="menuitem"]:not([aria-disabled="true"]), a[href]:not([aria-disabled="true"]), button:not([disabled])';class p extends l{static targets=["trigger","menu"];static values={keyboard:{type:Boolean,default:!1}};connect(){if(this.menuEl=this.hasMenuTarget?this.menuTarget:this.element.querySelector("[popover]"),this.triggerEl=this.hasTriggerTarget?this.triggerTarget:this.element.querySelector("[popovertarget]"),!this.menuEl)return;this.needsJsPositioning=!this.#d(),this.onToggle=(t)=>this.#n(t),this.menuEl.addEventListener("toggle",this.onToggle)}disconnect(){this.menuEl?.removeEventListener("toggle",this.onToggle),this.#t()}#n(t){if(t.newState==="open")this.#i();else this.#s()}async#i(){if(this.triggerEl?.setAttribute("aria-expanded","true"),this.needsJsPositioning)await this.#r();if(this.keyboardValue)this.#l()}#s(){this.triggerEl?.setAttribute("aria-expanded","false"),this.#t()}#o(){return this.menuEl?.matches(":popover-open")??!1}#t(){if(this.stopAutoUpdate?.(),this.stopAutoUpdate=null,this.keyHandler)this.menuEl?.removeEventListener("keydown",this.keyHandler),this.keyHandler=null}async#r(){if(!this.triggerEl)return;let t;try{t=await import("@floating-ui/dom")}catch(i){console.warn("[daisy-dropdown] @floating-ui/dom is not available; the popover will open unpositioned. Pin it in your import map to enable the legacy-browser positioning fallback.",i);return}if(!this.#o())return;let{computePosition:e,autoUpdate:n,flip:s,shift:o,offset:r}=t;this.menuEl.style.position="fixed",this.menuEl.style.margin="0",this.stopAutoUpdate=n(this.triggerEl,this.menuEl,()=>{e(this.triggerEl,this.menuEl,{placement:this.#a(),middleware:[r(h),s(),o({padding:8})]}).then(({x:i,y:a})=>{Object.assign(this.menuEl.style,{left:`${i}px`,top:`${a}px`})})})}#a(){let t=this.menuEl.classList,e=t.contains("dropdown-top")?"top":t.contains("dropdown-left")?"left":t.contains("dropdown-right")?"right":"bottom",n=t.contains("dropdown-end")?"-end":t.contains("dropdown-center")?"":"-start";return e==="left"||e==="right"?e:`${e}${n}`}#l(){this.keyHandler=(t)=>this.#h(t),this.menuEl.addEventListener("keydown",this.keyHandler),this.#e()[0]?.focus()}#h(t){let e=this.#e();if(e.length===0)return;let n=e.indexOf(document.activeElement);switch(t.key){case"ArrowDown":t.preventDefault(),e[(n+1+e.length)%e.length].focus();break;case"ArrowUp":t.preventDefault(),e[(n-1+e.length)%e.length].focus();break;case"Home":t.preventDefault(),e[0].focus();break;case"End":t.preventDefault(),e[e.length-1].focus();break}}#e(){return Array.from(this.menuEl.querySelectorAll(d))}#d(){return typeof CSS<"u"&&typeof CSS.supports==="function"&&CSS.supports("anchor-name: --x")}}export{p as default};
|
|
2
|
+
|
|
3
|
+
//# debugId=E14AFCB07E09B55864756E2164756E21
|
|
4
|
+
//# sourceMappingURL=daisy_dropdown_controller.min.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["daisy_dropdown_controller.js"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { Controller } from \"@hotwired/stimulus\"\n\n// daisy-dropdown — OPT-IN enhancement for the :popover Dropdown variant.\n//\n// The native Popover API already handles open/toggle, light-dismiss (outside\n// click), Escape, and focus return — so this controller deliberately does NOT\n// re-implement any of that. Its only jobs are:\n//\n// 1. A positioning fallback for browsers WITHOUT CSS anchor positioning\n// (Safari < 26, Firefox < 147). On modern browsers it does nothing and\n// fetches no extra JavaScript. The fallback lazily imports @floating-ui/dom,\n// which the HOST app must pin (see README) — if it is missing the menu still\n// opens (native popover), just unpositioned, and a console warning is logged.\n// 2. Optional roving keyboard navigation over the menu's focusable items\n// (enable with data-daisy-dropdown-keyboard-value=\"true\").\n//\n// State is driven entirely by the popover's native `toggle` event; there is no\n// internal open flag, no document-level listeners, and no display toggling.\nconst OFFSET = 4\n\n// Focusable, interactive menu items. Prefers explicit role=\"menuitem\", but falls\n// back to the links/buttons DaisyUI menus actually render (<li><a>…</a></li>), so\n// keyboard nav works on stock markup without forcing roles onto caller content.\nconst ITEM_SELECTOR = '[role=\"menuitem\"]:not([aria-disabled=\"true\"]), a[href]:not([aria-disabled=\"true\"]), button:not([disabled])'\n\nexport default class extends Controller {\n static targets = [\"trigger\", \"menu\"]\n static values = { keyboard: { type: Boolean, default: false } }\n\n connect() {\n this.menuEl = this.hasMenuTarget ? this.menuTarget : this.element.querySelector(\"[popover]\")\n this.triggerEl = this.hasTriggerTarget ? this.triggerTarget : this.element.querySelector(\"[popovertarget]\")\n if (!this.menuEl) return\n\n this.needsJsPositioning = !this.#supportsAnchorPositioning()\n this.onToggle = (event) => this.#handleToggle(event)\n this.menuEl.addEventListener(\"toggle\", this.onToggle)\n }\n\n disconnect() {\n this.menuEl?.removeEventListener(\"toggle\", this.onToggle)\n this.#teardownOpen()\n }\n\n #handleToggle(event) {\n if (event.newState === \"open\") this.#onOpen()\n else this.#onClose()\n }\n\n async #onOpen() {\n // The native Popover API does not mirror open state onto the invoker, so\n // sync aria-expanded ourselves for assistive tech.\n this.triggerEl?.setAttribute(\"aria-expanded\", \"true\")\n if (this.needsJsPositioning) await this.#position()\n if (this.keyboardValue) this.#bindKeyboard()\n }\n\n #onClose() {\n this.triggerEl?.setAttribute(\"aria-expanded\", \"false\")\n this.#teardownOpen()\n // Do NOT restore focus — the native popover already returns focus to the trigger.\n }\n\n #isOpen() {\n return this.menuEl?.matches(\":popover-open\") ?? false\n }\n\n #teardownOpen() {\n this.stopAutoUpdate?.()\n this.stopAutoUpdate = null\n if (this.keyHandler) {\n this.menuEl?.removeEventListener(\"keydown\", this.keyHandler)\n this.keyHandler = null\n }\n }\n\n // --- positioning fallback (only when CSS anchor positioning is unavailable) ---\n\n async #position() {\n if (!this.triggerEl) return\n\n // Lazy + feature-gated, so modern browsers never fetch the positioning lib.\n // The host app is responsible for pinning @floating-ui/dom; if it is absent\n // we degrade gracefully (native popover still opens, just unpositioned).\n let floatingUi\n try {\n floatingUi = await import(\"@floating-ui/dom\")\n } catch (error) {\n console.warn(\n \"[daisy-dropdown] @floating-ui/dom is not available; the popover will open unpositioned. \" +\n \"Pin it in your import map to enable the legacy-browser positioning fallback.\",\n error,\n )\n return\n }\n\n // A fast open→close during the dynamic import may have already closed us;\n // bail rather than position (and leak an autoUpdate on) a closed menu.\n if (!this.#isOpen()) return\n\n const { computePosition, autoUpdate, flip, shift, offset } = floatingUi\n\n this.menuEl.style.position = \"fixed\"\n this.menuEl.style.margin = \"0\"\n\n this.stopAutoUpdate = autoUpdate(this.triggerEl, this.menuEl, () => {\n computePosition(this.triggerEl, this.menuEl, {\n placement: this.#placement(),\n middleware: [offset(OFFSET), flip(), shift({ padding: 8 })],\n }).then(({ x, y }) => {\n Object.assign(this.menuEl.style, { left: `${x}px`, top: `${y}px` })\n })\n })\n }\n\n // Derive Floating UI placement from DaisyUI's dropdown-* classes on the menu.\n #placement() {\n const cl = this.menuEl.classList\n const side = cl.contains(\"dropdown-top\")\n ? \"top\"\n : cl.contains(\"dropdown-left\")\n ? \"left\"\n : cl.contains(\"dropdown-right\")\n ? \"right\"\n : \"bottom\"\n const align = cl.contains(\"dropdown-end\") ? \"-end\" : cl.contains(\"dropdown-center\") ? \"\" : \"-start\"\n return (side === \"left\" || side === \"right\") ? side : `${side}${align}`\n }\n\n // --- optional roving keyboard navigation ---\n\n #bindKeyboard() {\n this.keyHandler = (event) => this.#onKeydown(event)\n this.menuEl.addEventListener(\"keydown\", this.keyHandler)\n this.#items()[0]?.focus()\n }\n\n #onKeydown(event) {\n const items = this.#items()\n if (items.length === 0) return\n const index = items.indexOf(document.activeElement)\n\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault()\n items[(index + 1 + items.length) % items.length].focus()\n break\n case \"ArrowUp\":\n event.preventDefault()\n items[(index - 1 + items.length) % items.length].focus()\n break\n case \"Home\":\n event.preventDefault()\n items[0].focus()\n break\n case \"End\":\n event.preventDefault()\n items[items.length - 1].focus()\n break\n }\n }\n\n #items() {\n return Array.from(this.menuEl.querySelectorAll(ITEM_SELECTOR))\n }\n\n #supportsAnchorPositioning() {\n return typeof CSS !== \"undefined\" && typeof CSS.supports === \"function\" && CSS.supports(\"anchor-name: --x\")\n }\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "E14AFCB07E09B55864756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{Controller as y}from"@hotwired/stimulus";var a=8,c=8,g=10,d=4,f='button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])',b={top:"bottom",bottom:"top",left:"right",right:"left"};class T extends y{static targets=["content","arrow"];static values={placement:{type:String,default:"top"}};connect(){if(!this.hasContentTarget)return;if(this.originalMaxWidth=this.contentTarget.style.maxWidth,this.onPointerEnter=()=>this.show(),this.onPointerLeave=()=>this.#m(),this.onFocusIn=()=>this.show(),this.onFocusOut=(t)=>this.#l(t),this.onKeydown=(t)=>this.#u(t),this.onGeometryChange=()=>this.#i(),this.element.addEventListener("pointerenter",this.onPointerEnter),this.element.addEventListener("pointerleave",this.onPointerLeave),this.element.addEventListener("focusin",this.onFocusIn),this.element.addEventListener("focusout",this.onFocusOut),this.#p(),this.element.classList.contains("tooltip-open"))this.show()}disconnect(){if(this.element.removeEventListener("pointerenter",this.onPointerEnter),this.element.removeEventListener("pointerleave",this.onPointerLeave),this.element.removeEventListener("focusin",this.onFocusIn),this.element.removeEventListener("focusout",this.onFocusOut),this.#e(),this.#g(),this.hasContentTarget)this.contentTarget.style.maxWidth=this.originalMaxWidth}show(){if(!this.hasContentTarget||this.#t())return;this.contentTarget.style.visibility="hidden",this.contentTarget.showPopover(),this.#h(),this.#i()}hide(){if(this.#t())this.contentTarget.hidePopover();this.contentTarget.style.visibility="hidden",this.#e()}#h(){window.addEventListener("resize",this.onGeometryChange),window.addEventListener("scroll",this.onGeometryChange,!0),window.addEventListener("keydown",this.onKeydown),window.visualViewport?.addEventListener("resize",this.onGeometryChange),window.visualViewport?.addEventListener("scroll",this.onGeometryChange)}#e(){cancelAnimationFrame(this.frame),window.removeEventListener("resize",this.onGeometryChange),window.removeEventListener("scroll",this.onGeometryChange,!0),window.removeEventListener("keydown",this.onKeydown),window.visualViewport?.removeEventListener("resize",this.onGeometryChange),window.visualViewport?.removeEventListener("scroll",this.onGeometryChange)}#i(){cancelAnimationFrame(this.frame),this.frame=requestAnimationFrame(()=>this.#r())}#r(){if(!this.#t())return;let t=this.#d(),e=Math.max(0,t.width-a*2);this.contentTarget.style.maxWidth=this.originalMaxWidth;let i=Number.parseFloat(getComputedStyle(this.contentTarget).maxWidth),n=Number.isFinite(i)?Math.min(i,e):e;this.contentTarget.style.maxWidth=`${n}px`;let o=this.element.getBoundingClientRect(),h=this.contentTarget.getBoundingClientRect(),r=this.#n(this.placementValue,o,h),s=b[this.placementValue]||"bottom",l=this.#n(s,o,h),m=this.#s(l,h,t)<this.#s(r,h,t),w=m?s:this.placementValue,v=m?l:r,{x:u,y:p}=this.#a(v,h,t);Object.assign(this.contentTarget.style,{left:`${u}px`,top:`${p}px`,visibility:"visible"}),this.#c(w,o,h,u,p)}#n(t,e,i){let n=e.left+e.width/2,o=e.top+e.height/2;switch(t){case"bottom":return{x:n-i.width/2,y:e.bottom+c};case"left":return{x:e.left-i.width-c,y:o-i.height/2};case"right":return{x:e.right+c,y:o-i.height/2};default:return{x:n-i.width/2,y:e.top-i.height-c}}}#s({x:t,y:e},i,n){let o=n.left+a,h=n.left+n.width-a,r=n.top+a,s=n.top+n.height-a;return Math.max(0,o-t)+Math.max(0,t+i.width-h)+Math.max(0,r-e)+Math.max(0,e+i.height-s)}#a({x:t,y:e},i,n){let o=n.left+a,h=n.left+n.width-a,r=n.top+a,s=n.top+n.height-a;return{x:i.width>h-o?o:Math.min(Math.max(t,o),h-i.width),y:i.height>s-r?r:Math.min(Math.max(e,r),s-i.height)}}#c(t,e,i,n,o){if(!this.hasArrowTarget)return;let h=e.left+e.width/2-n,r=e.top+e.height/2-o,s=this.arrowTarget.style;if(Object.assign(s,{inset:"auto",transform:"none"}),t==="top"||t==="bottom"){if(s.left=`${this.#o(h-g/2,4,i.width-g-4)}px`,t==="top")s.bottom=`${-d}px`;else s.top=`${-d}px`,s.transform="rotate(180deg)";return}if(s.top=`${this.#o(r-d/2,4,i.height-d-4)}px`,t==="left")s.right="-7px",s.transform="rotate(-90deg)";else s.left="-7px",s.transform="rotate(90deg)"}#d(){let t=window.visualViewport;return{left:t?.offsetLeft??0,top:t?.offsetTop??0,width:t?.width??window.innerWidth,height:t?.height??window.innerHeight}}#l(t){if(!this.element.contains(t.relatedTarget))this.hide()}#m(){if(!this.element.matches(":focus-within"))this.hide()}#u(t){if(t.key!=="Escape")return;this.hide()}#p(){let t=this.element.matches(f)?this.element:this.element.querySelector(f);if(!t||!this.contentTarget.id)return;this.describedTrigger=t,this.previousDescription=t.getAttribute("aria-describedby");let e=this.previousDescription?.split(/\s+/).filter(Boolean)||[];t.setAttribute("aria-describedby",[...new Set([...e,this.contentTarget.id])].join(" "))}#g(){if(!this.describedTrigger)return;if(this.previousDescription===null)this.describedTrigger.removeAttribute("aria-describedby");else this.describedTrigger.setAttribute("aria-describedby",this.previousDescription)}#t(){return this.hasContentTarget&&this.contentTarget.matches(":popover-open")}#o(t,e,i){return Math.min(Math.max(t,e),Math.max(e,i))}}export{T as default};
|
|
2
|
+
|
|
3
|
+
//# debugId=5716AAA30AA53C0864756E2164756E21
|
|
4
|
+
//# sourceMappingURL=daisy_tooltip_controller.min.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["daisy_tooltip_controller.js"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { Controller } from \"@hotwired/stimulus\"\n\nconst VIEWPORT_PADDING = 8\nconst GAP = 8\nconst ARROW_WIDTH = 10\nconst ARROW_HEIGHT = 4\nconst FOCUSABLE_SELECTOR = 'button, a[href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\nconst OPPOSITE = { top: \"bottom\", bottom: \"top\", left: \"right\", right: \"left\" }\n\n// daisy-tooltip — positions a :popover Tooltip in the browser top layer.\n//\n// The Popover API escapes clipping ancestors. This controller supplies the\n// hover/focus lifecycle expected of a tooltip, then flips and shifts the real\n// tooltip element only while it is open so it remains inside the visual\n// viewport. No global listeners remain attached while the tooltip is closed.\nexport default class extends Controller {\n static targets = [\"content\", \"arrow\"]\n static values = { placement: { type: String, default: \"top\" } }\n\n connect() {\n if (!this.hasContentTarget) return\n\n this.originalMaxWidth = this.contentTarget.style.maxWidth\n this.onPointerEnter = () => this.show()\n this.onPointerLeave = () => this.#hideUnlessFocused()\n this.onFocusIn = () => this.show()\n this.onFocusOut = (event) => this.#handleFocusOut(event)\n this.onKeydown = (event) => this.#handleKeydown(event)\n this.onGeometryChange = () => this.#schedulePosition()\n\n this.element.addEventListener(\"pointerenter\", this.onPointerEnter)\n this.element.addEventListener(\"pointerleave\", this.onPointerLeave)\n this.element.addEventListener(\"focusin\", this.onFocusIn)\n this.element.addEventListener(\"focusout\", this.onFocusOut)\n this.#describeTrigger()\n\n if (this.element.classList.contains(\"tooltip-open\")) this.show()\n }\n\n disconnect() {\n this.element.removeEventListener(\"pointerenter\", this.onPointerEnter)\n this.element.removeEventListener(\"pointerleave\", this.onPointerLeave)\n this.element.removeEventListener(\"focusin\", this.onFocusIn)\n this.element.removeEventListener(\"focusout\", this.onFocusOut)\n this.#teardownOpen()\n this.#restoreDescription()\n if (this.hasContentTarget) this.contentTarget.style.maxWidth = this.originalMaxWidth\n }\n\n show() {\n if (!this.hasContentTarget || this.#isOpen()) return\n\n this.contentTarget.style.visibility = \"hidden\"\n this.contentTarget.showPopover()\n this.#setupOpen()\n this.#schedulePosition()\n }\n\n hide() {\n if (this.#isOpen()) this.contentTarget.hidePopover()\n this.contentTarget.style.visibility = \"hidden\"\n this.#teardownOpen()\n }\n\n #setupOpen() {\n window.addEventListener(\"resize\", this.onGeometryChange)\n window.addEventListener(\"scroll\", this.onGeometryChange, true)\n window.addEventListener(\"keydown\", this.onKeydown)\n window.visualViewport?.addEventListener(\"resize\", this.onGeometryChange)\n window.visualViewport?.addEventListener(\"scroll\", this.onGeometryChange)\n }\n\n #teardownOpen() {\n cancelAnimationFrame(this.frame)\n window.removeEventListener(\"resize\", this.onGeometryChange)\n window.removeEventListener(\"scroll\", this.onGeometryChange, true)\n window.removeEventListener(\"keydown\", this.onKeydown)\n window.visualViewport?.removeEventListener(\"resize\", this.onGeometryChange)\n window.visualViewport?.removeEventListener(\"scroll\", this.onGeometryChange)\n }\n\n #schedulePosition() {\n cancelAnimationFrame(this.frame)\n this.frame = requestAnimationFrame(() => this.#position())\n }\n\n #position() {\n if (!this.#isOpen()) return\n\n const viewport = this.#viewport()\n const availableWidth = Math.max(0, viewport.width - VIEWPORT_PADDING * 2)\n this.contentTarget.style.maxWidth = this.originalMaxWidth\n const computedMaxWidth = Number.parseFloat(getComputedStyle(this.contentTarget).maxWidth)\n const maxWidth = Number.isFinite(computedMaxWidth) ? Math.min(computedMaxWidth, availableWidth) : availableWidth\n this.contentTarget.style.maxWidth = `${maxWidth}px`\n\n const trigger = this.element.getBoundingClientRect()\n const tooltip = this.contentTarget.getBoundingClientRect()\n const preferred = this.#coordinates(this.placementValue, trigger, tooltip)\n const oppositePlacement = OPPOSITE[this.placementValue] || \"bottom\"\n const opposite = this.#coordinates(oppositePlacement, trigger, tooltip)\n const useOpposite = this.#overflow(opposite, tooltip, viewport) < this.#overflow(preferred, tooltip, viewport)\n const placement = useOpposite ? oppositePlacement : this.placementValue\n const coordinates = useOpposite ? opposite : preferred\n const { x, y } = this.#shift(coordinates, tooltip, viewport)\n\n Object.assign(this.contentTarget.style, { left: `${x}px`, top: `${y}px`, visibility: \"visible\" })\n this.#positionArrow(placement, trigger, tooltip, x, y)\n }\n\n #coordinates(placement, trigger, tooltip) {\n const centerX = trigger.left + trigger.width / 2\n const centerY = trigger.top + trigger.height / 2\n\n switch (placement) {\n case \"bottom\":\n return { x: centerX - tooltip.width / 2, y: trigger.bottom + GAP }\n case \"left\":\n return { x: trigger.left - tooltip.width - GAP, y: centerY - tooltip.height / 2 }\n case \"right\":\n return { x: trigger.right + GAP, y: centerY - tooltip.height / 2 }\n default:\n return { x: centerX - tooltip.width / 2, y: trigger.top - tooltip.height - GAP }\n }\n }\n\n #overflow({ x, y }, tooltip, viewport) {\n const left = viewport.left + VIEWPORT_PADDING\n const right = viewport.left + viewport.width - VIEWPORT_PADDING\n const top = viewport.top + VIEWPORT_PADDING\n const bottom = viewport.top + viewport.height - VIEWPORT_PADDING\n\n return (\n Math.max(0, left - x) +\n Math.max(0, x + tooltip.width - right) +\n Math.max(0, top - y) +\n Math.max(0, y + tooltip.height - bottom)\n )\n }\n\n #shift({ x, y }, tooltip, viewport) {\n const left = viewport.left + VIEWPORT_PADDING\n const right = viewport.left + viewport.width - VIEWPORT_PADDING\n const top = viewport.top + VIEWPORT_PADDING\n const bottom = viewport.top + viewport.height - VIEWPORT_PADDING\n\n return {\n x: tooltip.width > right - left ? left : Math.min(Math.max(x, left), right - tooltip.width),\n y: tooltip.height > bottom - top ? top : Math.min(Math.max(y, top), bottom - tooltip.height),\n }\n }\n\n #positionArrow(placement, trigger, tooltip, x, y) {\n if (!this.hasArrowTarget) return\n\n const centerX = trigger.left + trigger.width / 2 - x\n const centerY = trigger.top + trigger.height / 2 - y\n const arrow = this.arrowTarget.style\n Object.assign(arrow, { inset: \"auto\", transform: \"none\" })\n\n if (placement === \"top\" || placement === \"bottom\") {\n arrow.left = `${this.#clamp(centerX - ARROW_WIDTH / 2, 4, tooltip.width - ARROW_WIDTH - 4)}px`\n if (placement === \"top\") arrow.bottom = `${-ARROW_HEIGHT}px`\n else {\n arrow.top = `${-ARROW_HEIGHT}px`\n arrow.transform = \"rotate(180deg)\"\n }\n return\n }\n\n arrow.top = `${this.#clamp(centerY - ARROW_HEIGHT / 2, 4, tooltip.height - ARROW_HEIGHT - 4)}px`\n if (placement === \"left\") {\n arrow.right = \"-7px\"\n arrow.transform = \"rotate(-90deg)\"\n } else {\n arrow.left = \"-7px\"\n arrow.transform = \"rotate(90deg)\"\n }\n }\n\n #viewport() {\n const visualViewport = window.visualViewport\n return {\n left: visualViewport?.offsetLeft ?? 0,\n top: visualViewport?.offsetTop ?? 0,\n width: visualViewport?.width ?? window.innerWidth,\n height: visualViewport?.height ?? window.innerHeight,\n }\n }\n\n #handleFocusOut(event) {\n if (!this.element.contains(event.relatedTarget)) this.hide()\n }\n\n #hideUnlessFocused() {\n if (!this.element.matches(\":focus-within\")) this.hide()\n }\n\n #handleKeydown(event) {\n if (event.key !== \"Escape\") return\n\n this.hide()\n }\n\n #describeTrigger() {\n const trigger = this.element.matches(FOCUSABLE_SELECTOR)\n ? this.element\n : this.element.querySelector(FOCUSABLE_SELECTOR)\n if (!trigger || !this.contentTarget.id) return\n\n this.describedTrigger = trigger\n this.previousDescription = trigger.getAttribute(\"aria-describedby\")\n const descriptions = this.previousDescription?.split(/\\s+/).filter(Boolean) || []\n trigger.setAttribute(\"aria-describedby\", [...new Set([...descriptions, this.contentTarget.id])].join(\" \"))\n }\n\n #restoreDescription() {\n if (!this.describedTrigger) return\n\n if (this.previousDescription === null) this.describedTrigger.removeAttribute(\"aria-describedby\")\n else this.describedTrigger.setAttribute(\"aria-describedby\", this.previousDescription)\n }\n\n #isOpen() {\n return this.hasContentTarget && this.contentTarget.matches(\":popover-open\")\n }\n\n #clamp(value, minimum, maximum) {\n return Math.min(Math.max(value, minimum), Math.max(minimum, maximum))\n }\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "5716AAA30AA53C0864756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
data/config/importmap.rb
CHANGED
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
# // app/javascript/controllers/index.js
|
|
8
8
|
# import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
|
|
9
9
|
# lazyLoadControllersFrom("daisy_ui/controllers", application)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
#
|
|
11
|
+
# Each pin resolves to the minified build (`rake build:js`), whose linked
|
|
12
|
+
# sourcemap shows the readable source in devtools. Pinned one by one, not with
|
|
13
|
+
# pin_all_from: the directory also holds the .min.js twins, which would become
|
|
14
|
+
# pins of their own. Not preloaded: a lazily registered controller is fetched
|
|
15
|
+
# the first time its data-controller appears, so a page without one skips it.
|
|
16
|
+
pin "daisy_ui/controllers/daisy_dropdown_controller",
|
|
17
|
+
to: "daisy_ui/controllers/daisy_dropdown_controller.min.js",
|
|
18
|
+
preload: false
|
|
19
|
+
pin "daisy_ui/controllers/daisy_tooltip_controller",
|
|
20
|
+
to: "daisy_ui/controllers/daisy_tooltip_controller.min.js",
|
|
21
|
+
preload: false
|
data/lib/daisy_ui/base.rb
CHANGED
|
@@ -118,38 +118,77 @@ module DaisyUI
|
|
|
118
118
|
error: "bg-error text-error-content"
|
|
119
119
|
}.freeze
|
|
120
120
|
|
|
121
|
+
# Component registries: written by the class body (register_modifiers,
|
|
122
|
+
# register_flags, `self.component_class =`) and by `inherited`, on the main
|
|
123
|
+
# Ractor while the class loads; rendering only reads them. Every writer
|
|
124
|
+
# assigns a new value instead of mutating the old one and freezes it as it
|
|
125
|
+
# writes (Shareable.share), so a non-main Ractor can read the registries of
|
|
126
|
+
# a component that loaded after boot too, and a later register_* still
|
|
127
|
+
# works.
|
|
121
128
|
class << self
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
# rubocop:disable ThreadSafety/ClassInstanceVariable -- see above
|
|
130
|
+
attr_reader :modifiers
|
|
131
|
+
|
|
132
|
+
def modifiers=(modifiers)
|
|
133
|
+
@modifiers = Shareable.share(modifiers)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def modifier_groups=(groups)
|
|
137
|
+
@modifier_groups = Shareable.share(groups)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def flags=(flags)
|
|
141
|
+
@flags = Shareable.share(flags)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def component_class=(component_class)
|
|
145
|
+
@component_class = Shareable.share(component_class)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Positional symbols a component handles itself (e.g. Dropdown's
|
|
149
|
+
# :popover): accepted as modifiers, but they emit no class.
|
|
150
|
+
def flags
|
|
151
|
+
@flags || []
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def register_flags(*names)
|
|
155
|
+
self.flags = flags | names
|
|
156
|
+
end
|
|
124
157
|
|
|
125
158
|
# { group => [modifier, ...] }: modifiers in one group are alternatives
|
|
126
159
|
# (sizes, colors, ...), so ClassMerge keeps only the last one.
|
|
127
160
|
def modifier_groups
|
|
128
|
-
@modifier_groups
|
|
161
|
+
@modifier_groups || {}
|
|
129
162
|
end
|
|
130
163
|
|
|
164
|
+
# Derived from the class name on first use, the same value every time.
|
|
165
|
+
# Remembered on the main Ractor only (DaisyUI.finalize! derives it at
|
|
166
|
+
# boot); a non-main Ractor cannot set a class ivar, so it derives it.
|
|
131
167
|
def component_class
|
|
132
168
|
return @component_class if instance_variable_defined?(:@component_class)
|
|
133
169
|
|
|
134
|
-
|
|
170
|
+
derived = begin
|
|
135
171
|
# Convert "DaisyUI::Button" -> "button"
|
|
136
172
|
class_name = name.split("::").last # demodulize
|
|
137
173
|
class_name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
138
174
|
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
139
175
|
.tr("_", "-")
|
|
140
|
-
.downcase
|
|
176
|
+
.downcase.freeze
|
|
141
177
|
end
|
|
178
|
+
Store.main_ractor? ? (@component_class = derived) : derived
|
|
142
179
|
end
|
|
143
180
|
|
|
144
181
|
def inherited(subclass)
|
|
145
182
|
super
|
|
146
183
|
subclass.modifiers = (modifiers || {}).dup
|
|
147
184
|
subclass.modifier_groups = modifier_groups.transform_values(&:dup)
|
|
185
|
+
subclass.flags = flags.dup
|
|
148
186
|
# Inherit component_class if it was explicitly set
|
|
149
187
|
return unless instance_variable_defined?(:@component_class)
|
|
150
188
|
|
|
151
189
|
subclass.component_class = @component_class
|
|
152
190
|
end
|
|
191
|
+
# rubocop:enable ThreadSafety/ClassInstanceVariable
|
|
153
192
|
|
|
154
193
|
# Registers modifiers. A Hash value declares a group of alternatives:
|
|
155
194
|
#
|
|
@@ -167,15 +206,23 @@ module DaisyUI
|
|
|
167
206
|
end
|
|
168
207
|
end
|
|
169
208
|
|
|
170
|
-
modifier_groups.
|
|
209
|
+
groups = modifier_groups.transform_values { |members| members.reject { |member| flat.key?(member) } }
|
|
171
210
|
mods.each do |group, value|
|
|
172
|
-
(
|
|
211
|
+
groups[group] = (groups[group] || []) + value.keys if value.is_a?(Hash)
|
|
173
212
|
end
|
|
174
|
-
modifier_groups.delete_if { |_group, members| members.empty? }
|
|
175
213
|
|
|
214
|
+
self.modifier_groups = groups.reject { |_group, members| members.empty? }
|
|
176
215
|
self.modifiers = (modifiers || {}).merge(flat)
|
|
177
216
|
ClassMerge.reset!
|
|
178
217
|
end
|
|
218
|
+
|
|
219
|
+
# @api private Called by DaisyUI.finalize! for every component: derives
|
|
220
|
+
# component_class on the main Ractor and makes sure every registry is
|
|
221
|
+
# frozen for non-main Ractors.
|
|
222
|
+
def finalize!
|
|
223
|
+
[component_class, modifiers, modifier_groups, flags].each { |registry| Shareable.share(registry) }
|
|
224
|
+
self
|
|
225
|
+
end
|
|
179
226
|
end
|
|
180
227
|
|
|
181
228
|
def initialize(*modifiers, as: :div, id: nil, **options)
|
|
@@ -188,6 +235,7 @@ module DaisyUI
|
|
|
188
235
|
# Store all keyword arguments (class, data, aria, etc.)
|
|
189
236
|
# This will be processed by `classes` and `attributes` methods
|
|
190
237
|
@options = options
|
|
238
|
+
validate_modifiers
|
|
191
239
|
super()
|
|
192
240
|
end
|
|
193
241
|
|
|
@@ -277,11 +325,41 @@ module DaisyUI
|
|
|
277
325
|
{
|
|
278
326
|
skeleton: "skeleton",
|
|
279
327
|
**(self.class.modifiers || {}),
|
|
280
|
-
**
|
|
328
|
+
**configured_modifiers,
|
|
281
329
|
**DaisyUI.configuration.modifiers.for(component: nil)
|
|
282
330
|
}
|
|
283
331
|
end
|
|
284
332
|
|
|
333
|
+
# Modifiers added with `config.modifiers.add(component:)` for this class or
|
|
334
|
+
# any DaisyUI ancestor; the closest class wins.
|
|
335
|
+
def configured_modifiers
|
|
336
|
+
self.class.ancestors.take_while { |klass| klass != Base }.reverse.reduce({}) do |result, klass|
|
|
337
|
+
result.merge(DaisyUI.configuration.modifiers.for(component: klass))
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Reports positional modifiers that are neither in modifier_map nor a
|
|
342
|
+
# registered flag, and responsive ones not in modifier_map (a flag has no
|
|
343
|
+
# class to prefix). See Configuration#unknown_modifiers.
|
|
344
|
+
def validate_modifiers
|
|
345
|
+
responsive = (options[:responsive]&.values || []).flat_map { |mods| Array(mods) }
|
|
346
|
+
return if modifiers.empty? && responsive.empty?
|
|
347
|
+
|
|
348
|
+
map_keys = modifier_map.keys
|
|
349
|
+
report_unknown_modifiers(modifiers, map_keys | self.class.flags)
|
|
350
|
+
report_unknown_modifiers(responsive, map_keys)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# For sub-component methods with their own modifiers, e.g. Chat#bubble:
|
|
354
|
+
# `report_unknown_modifiers(modifiers, BUBBLE_MODIFIERS.keys, part: :bubble)`.
|
|
355
|
+
def report_unknown_modifiers(requested, known, part: nil)
|
|
356
|
+
requested.each do |modifier|
|
|
357
|
+
next if !modifier.is_a?(Symbol) || known.include?(modifier)
|
|
358
|
+
|
|
359
|
+
DaisyUI.configuration.report_unknown_modifier(component: self.class, modifier:, known:, part:)
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
285
363
|
def extract_boolean_modifiers(options)
|
|
286
364
|
modifier_keys = modifier_map.keys
|
|
287
365
|
boolean_mods = []
|
data/lib/daisy_ui/chat.rb
CHANGED
|
@@ -108,6 +108,7 @@ module DaisyUI
|
|
|
108
108
|
private
|
|
109
109
|
|
|
110
110
|
def build_bubble_classes(modifiers, options)
|
|
111
|
+
report_unknown_modifiers(modifiers, BUBBLE_MODIFIERS.keys, part: :bubble)
|
|
111
112
|
modifier_classes = modifiers.filter_map { |mod| apply_prefix(BUBBLE_MODIFIERS[mod]) }
|
|
112
113
|
merge_classes(apply_prefix("chat-bubble"), *modifier_classes, options.delete(:class))
|
|
113
114
|
end
|
data/lib/daisy_ui/checkbox.rb
CHANGED
|
@@ -24,6 +24,20 @@ module DaisyUI
|
|
|
24
24
|
@conflicts = {}
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def initialize_copy(source)
|
|
28
|
+
super
|
|
29
|
+
@theme = @theme.transform_values(&:dup)
|
|
30
|
+
@class_groups = @class_groups.transform_values(&:dup)
|
|
31
|
+
@conflicts = @conflicts.transform_values(&:dup)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @api private Called by DaisyUI.finalize!. A raw class group's
|
|
35
|
+
# validator that closes over app state is kept as it is.
|
|
36
|
+
def finalize!
|
|
37
|
+
[@theme, @class_groups, @conflicts].each { |settings| Shareable.share(settings) }
|
|
38
|
+
freeze
|
|
39
|
+
end
|
|
40
|
+
|
|
27
41
|
# Registers utilities that behave like an existing one:
|
|
28
42
|
# `utility("text-display", like: "text-lg")` makes `text-display` a font
|
|
29
43
|
# size, so it replaces `text-sm` and sits next to `text-error`.
|
|
@@ -57,11 +71,12 @@ module DaisyUI
|
|
|
57
71
|
append(@conflicts, conflicts)
|
|
58
72
|
end
|
|
59
73
|
|
|
60
|
-
# @api private The config passed to Merger.new, including the daisyUI
|
|
61
|
-
|
|
74
|
+
# @api private The config passed to Merger.new, including the daisyUI
|
|
75
|
+
# groups of +configuration+ (the DaisyUI configuration holding this one).
|
|
76
|
+
def merger_config(configuration = DaisyUI.configuration)
|
|
62
77
|
{
|
|
63
78
|
theme: @theme,
|
|
64
|
-
class_groups: DaisyGroups.build.merge(@class_groups) { |_id, existing, added| existing + added },
|
|
79
|
+
class_groups: DaisyGroups.build(configuration).merge(@class_groups) { |_id, existing, added| existing + added },
|
|
65
80
|
conflicting_class_groups: @conflicts
|
|
66
81
|
}
|
|
67
82
|
end
|
|
@@ -18,8 +18,9 @@ module DaisyUI
|
|
|
18
18
|
# @api private
|
|
19
19
|
module DaisyGroups
|
|
20
20
|
class << self
|
|
21
|
-
# Class groups for the merger: `{ "daisy:group:btn-size" => ["btn-xs", ...], ... }
|
|
22
|
-
|
|
21
|
+
# Class groups for the merger: `{ "daisy:group:btn-size" => ["btn-xs", ...], ... }`,
|
|
22
|
+
# from one configuration's prefix and modifiers.
|
|
23
|
+
def build(configuration = DaisyUI.configuration, prefix: configuration.prefix)
|
|
23
24
|
components = self.components
|
|
24
25
|
groups = {}
|
|
25
26
|
|
|
@@ -37,7 +38,7 @@ module DaisyUI
|
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
# Configured modifiers join the component's own group of that name.
|
|
40
|
-
|
|
41
|
+
configuration.modifiers.groups.each do |(component, group), classes|
|
|
41
42
|
owner = component ? component.component_class || component.name : "global"
|
|
42
43
|
add(groups, "daisy:group:#{owner}-#{group}", classes.flat_map(&:split), prefix)
|
|
43
44
|
end
|
|
@@ -51,9 +52,11 @@ module DaisyUI
|
|
|
51
52
|
|
|
52
53
|
# Every named DaisyUI::Base subclass: the gem's components (eager
|
|
53
54
|
# loaded) and any app component loaded so far. An app component that
|
|
54
|
-
# loads later resets the merger through `register_modifiers`.
|
|
55
|
+
# loads later resets the merger through `register_modifiers`. Only the
|
|
56
|
+
# main Ractor can eager-load; DaisyUI.finalize! did it there before
|
|
57
|
+
# any other Ractor builds a merger.
|
|
55
58
|
def components
|
|
56
|
-
Zeitwerk::Loader.eager_load_namespace(DaisyUI)
|
|
59
|
+
Zeitwerk::Loader.eager_load_namespace(DaisyUI) if Store.main_ractor?
|
|
57
60
|
descendants(DaisyUI::Base).select(&:name)
|
|
58
61
|
end
|
|
59
62
|
|
|
@@ -8,21 +8,6 @@ module DaisyUI
|
|
|
8
8
|
module TailwindConfig
|
|
9
9
|
include Validators
|
|
10
10
|
|
|
11
|
-
class << self
|
|
12
|
-
private
|
|
13
|
-
|
|
14
|
-
# Recursively freezes nested Hash/Array structures so shared config
|
|
15
|
-
# constants can't be mutated in place. Keys are strings/symbols and are
|
|
16
|
-
# already immutable, so only values need walking.
|
|
17
|
-
def deep_freeze(object)
|
|
18
|
-
case object
|
|
19
|
-
when Hash then object.each_value { |value| deep_freeze(value) }
|
|
20
|
-
when Array then object.each { |value| deep_freeze(value) }
|
|
21
|
-
end
|
|
22
|
-
object.freeze
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
11
|
THEME_KEYS = %w[
|
|
27
12
|
color
|
|
28
13
|
font
|
|
@@ -46,8 +31,8 @@ module DaisyUI
|
|
|
46
31
|
].freeze
|
|
47
32
|
THEME_KEYS.each do |key|
|
|
48
33
|
const_set("THEME_#{key.upcase.tr('-', '_')}", lambda { |config|
|
|
49
|
-
|
|
50
|
-
|
|
34
|
+
config[:theme].fetch(key, nil)
|
|
35
|
+
}
|
|
51
36
|
)
|
|
52
37
|
end
|
|
53
38
|
|
|
@@ -2465,7 +2450,12 @@ module DaisyUI
|
|
|
2465
2450
|
"selection"
|
|
2466
2451
|
]
|
|
2467
2452
|
}.freeze
|
|
2468
|
-
|
|
2453
|
+
|
|
2454
|
+
# Every constant above, DEFAULTS deeply, is made Ractor-shareable so a
|
|
2455
|
+
# non-main Ractor can build its own merger from them: the scale and theme
|
|
2456
|
+
# lambdas are capture-free (THEME_* only reads its frozen key) and their
|
|
2457
|
+
# self is this module.
|
|
2458
|
+
constants(false).each { |name| Shareable.make(const_get(name)) }
|
|
2469
2459
|
|
|
2470
2460
|
# Extends DEFAULTS with incoming_config without mutating either.
|
|
2471
2461
|
# Theme scales are appended. Unlike upstream, `class_groups` and
|
|
@@ -197,6 +197,10 @@ module DaisyUI
|
|
|
197
197
|
IS_LABEL_SHADOW = lambda { |label|
|
|
198
198
|
label == "shadow"
|
|
199
199
|
}
|
|
200
|
+
|
|
201
|
+
# Ractor-shareable, so a non-main Ractor can read them: every validator
|
|
202
|
+
# is a capture-free lambda whose self is this module.
|
|
203
|
+
constants(false).each { |name| Shareable.make(const_get(name)) }
|
|
200
204
|
end
|
|
201
205
|
end
|
|
202
206
|
end
|