poetry-core 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/DESIGN.md +145 -0
- data/LICENSE.txt +21 -0
- data/README.md +30 -0
- data/THIRD_PARTY_NOTICES.md +507 -0
- data/app/components/poetry/core/box/component.rb +82 -0
- data/app/components/poetry/core/component.rb +390 -0
- data/app/components/poetry/core/concerns/agent_tools.rb +418 -0
- data/app/components/poetry/core/concerns/declared_attributes.rb +267 -0
- data/app/components/poetry/core/concerns/introspection.rb +533 -0
- data/app/components/poetry/core/concerns/options.rb +262 -0
- data/app/components/poetry/core/concerns/parts.rb +148 -0
- data/app/components/poetry/core/concerns/stimulus.rb +363 -0
- data/app/components/poetry/core/concerns/styles.rb +323 -0
- data/app/components/poetry/core/style.rb +150 -0
- data/app/components/poetry/core/wrapper/component.rb +90 -0
- data/app/javascript/poetry/core/accordion_controller.js +165 -0
- data/app/javascript/poetry/core/action_bar_controller.js +109 -0
- data/app/javascript/poetry/core/autocomplete_controller.js +220 -0
- data/app/javascript/poetry/core/calendar_controller.js +406 -0
- data/app/javascript/poetry/core/carousel_controller.js +167 -0
- data/app/javascript/poetry/core/checkbox_group_controller.js +97 -0
- data/app/javascript/poetry/core/checked_controller.js +208 -0
- data/app/javascript/poetry/core/clipboard_text_controller.js +111 -0
- data/app/javascript/poetry/core/combobox_controller.js +1095 -0
- data/app/javascript/poetry/core/command_controller.js +510 -0
- data/app/javascript/poetry/core/context_menu_controller.js +204 -0
- data/app/javascript/poetry/core/date_field_controller.js +584 -0
- data/app/javascript/poetry/core/date_picker_controller.js +111 -0
- data/app/javascript/poetry/core/deferred_controller.js +111 -0
- data/app/javascript/poetry/core/dialog_controller.js +206 -0
- data/app/javascript/poetry/core/dismissable_controller.js +182 -0
- data/app/javascript/poetry/core/drawer_controller.js +365 -0
- data/app/javascript/poetry/core/file_input_controller.js +191 -0
- data/app/javascript/poetry/core/focus_scope_controller.js +162 -0
- data/app/javascript/poetry/core/helpers/announce.js +228 -0
- data/app/javascript/poetry/core/helpers/breakpoint.js +28 -0
- data/app/javascript/poetry/core/helpers/collection.js +18 -0
- data/app/javascript/poetry/core/helpers/direction.js +15 -0
- data/app/javascript/poetry/core/helpers/escape.js +38 -0
- data/app/javascript/poetry/core/helpers/filter_rank.js +104 -0
- data/app/javascript/poetry/core/helpers/focus_guards.js +46 -0
- data/app/javascript/poetry/core/helpers/hotkey.js +43 -0
- data/app/javascript/poetry/core/helpers/id_integrity.js +65 -0
- data/app/javascript/poetry/core/helpers/incomplete_date.js +328 -0
- data/app/javascript/poetry/core/helpers/mask.js +283 -0
- data/app/javascript/poetry/core/helpers/portal.js +217 -0
- data/app/javascript/poetry/core/helpers/presence.js +210 -0
- data/app/javascript/poetry/core/helpers/registration_guard.js +76 -0
- data/app/javascript/poetry/core/helpers/scroll_lock.js +67 -0
- data/app/javascript/poetry/core/helpers/scroller_geometry.js +422 -0
- data/app/javascript/poetry/core/helpers/state.js +80 -0
- data/app/javascript/poetry/core/helpers/tabbable.js +56 -0
- data/app/javascript/poetry/core/helpers/turbo_cache.js +21 -0
- data/app/javascript/poetry/core/helpers/typeahead.js +85 -0
- data/app/javascript/poetry/core/hotkey_controller.js +55 -0
- data/app/javascript/poetry/core/hover_card_controller.js +431 -0
- data/app/javascript/poetry/core/index.js +218 -0
- data/app/javascript/poetry/core/mask_controller.js +500 -0
- data/app/javascript/poetry/core/menu_controller.js +1050 -0
- data/app/javascript/poetry/core/menubar_controller.js +329 -0
- data/app/javascript/poetry/core/message_scroller_controller.js +930 -0
- data/app/javascript/poetry/core/navigation_menu_controller.js +458 -0
- data/app/javascript/poetry/core/number_field_controller.js +404 -0
- data/app/javascript/poetry/core/optimistic_form_controller.js +56 -0
- data/app/javascript/poetry/core/otp_controller.js +219 -0
- data/app/javascript/poetry/core/popover_controller.js +300 -0
- data/app/javascript/poetry/core/popper_controller.js +439 -0
- data/app/javascript/poetry/core/pressed_controller.js +74 -0
- data/app/javascript/poetry/core/questionnaire_controller.js +468 -0
- data/app/javascript/poetry/core/radio_group_controller.js +197 -0
- data/app/javascript/poetry/core/resizable_controller.js +202 -0
- data/app/javascript/poetry/core/roving_focus_controller.js +215 -0
- data/app/javascript/poetry/core/scroll_spy_controller.js +84 -0
- data/app/javascript/poetry/core/search_field_controller.js +95 -0
- data/app/javascript/poetry/core/select_controller.js +924 -0
- data/app/javascript/poetry/core/sensitive_input_controller.js +195 -0
- data/app/javascript/poetry/core/sheet_controller.js +24 -0
- data/app/javascript/poetry/core/sidebar_controller.js +272 -0
- data/app/javascript/poetry/core/slider_controller.js +419 -0
- data/app/javascript/poetry/core/state_controller.js +82 -0
- data/app/javascript/poetry/core/table_selection_controller.js +153 -0
- data/app/javascript/poetry/core/tabs_controller.js +151 -0
- data/app/javascript/poetry/core/tag_group_controller.js +134 -0
- data/app/javascript/poetry/core/toast_controller.js +228 -0
- data/app/javascript/poetry/core/toast_trigger_controller.js +26 -0
- data/app/javascript/poetry/core/toaster_controller.js +197 -0
- data/app/javascript/poetry/core/toggle_group_controller.js +206 -0
- data/app/javascript/poetry/core/tooltip_controller.js +541 -0
- data/app/javascript/poetry/core/tree_controller.js +265 -0
- data/app/javascript/poetry/core/vendor/floating_ui_core.js +1043 -0
- data/app/javascript/poetry/core/vendor/floating_ui_dom.js +794 -0
- data/app/javascript/poetry/core/vendor/floating_ui_utils.js +141 -0
- data/app/javascript/poetry/core/vendor/floating_ui_utils_dom.js +170 -0
- data/app/views/poetry/core/preview.html.erb +13 -0
- data/config/component_registry.yml +20 -0
- data/config/controllers_manifest.json +1528 -0
- data/config/importmap.rb +11 -0
- data/config/state_vocabulary.json +31 -0
- data/lib/active_model/type/list.rb +33 -0
- data/lib/active_model/type/symbol.rb +36 -0
- data/lib/poetry/core/check/stable_identity.rb +104 -0
- data/lib/poetry/core/check.rb +1445 -0
- data/lib/poetry/core/config.rb +286 -0
- data/lib/poetry/core/contrib/wrapped_helper.rb +68 -0
- data/lib/poetry/core/css/bem_merger.rb +45 -0
- data/lib/poetry/core/css/bem_reference.rb +87 -0
- data/lib/poetry/core/css/override_scan.rb +110 -0
- data/lib/poetry/core/css/resolver.rb +161 -0
- data/lib/poetry/core/css/safelist.rb +41 -0
- data/lib/poetry/core/css/tailwind_merger.rb +102 -0
- data/lib/poetry/core/css/template_classes.rb +102 -0
- data/lib/poetry/core/css/theme_coverage.rb +72 -0
- data/lib/poetry/core/css/var_coverage.rb +75 -0
- data/lib/poetry/core/css/verifier.rb +106 -0
- data/lib/poetry/core/design_lint.rb +720 -0
- data/lib/poetry/core/design_md/import.rb +250 -0
- data/lib/poetry/core/design_md.rb +461 -0
- data/lib/poetry/core/engine.rb +71 -0
- data/lib/poetry/core/errors.rb +53 -0
- data/lib/poetry/core/html/attributes.rb +667 -0
- data/lib/poetry/core/icons.rb +157 -0
- data/lib/poetry/core/llms_text.rb +354 -0
- data/lib/poetry/core/page_architectures.rb +304 -0
- data/lib/poetry/core/part_contract.rb +285 -0
- data/lib/poetry/core/preview/abstract.rb +121 -0
- data/lib/poetry/core/preview/base.rb +227 -0
- data/lib/poetry/core/preview/sidecarable.rb +136 -0
- data/lib/poetry/core/preview/template.rb +186 -0
- data/lib/poetry/core/recipe_items.rb +82 -0
- data/lib/poetry/core/registry.rb +363 -0
- data/lib/poetry/core/registry_address.rb +115 -0
- data/lib/poetry/core/registry_client.rb +217 -0
- data/lib/poetry/core/registry_installer.rb +234 -0
- data/lib/poetry/core/registry_items.rb +184 -0
- data/lib/poetry/core/skill_text.rb +288 -0
- data/lib/poetry/core/stable_id.rb +110 -0
- data/lib/poetry/core/stimulus/builder.rb +395 -0
- data/lib/poetry/core/stimulus/declarations.rb +444 -0
- data/lib/poetry/core/stimulus/manifest.rb +68 -0
- data/lib/poetry/core/stimulus/merger.rb +131 -0
- data/lib/poetry/core/stimulus_contract.rb +263 -0
- data/lib/poetry/core/tag_helper.rb +10 -0
- data/lib/poetry/core/template_compile.rb +74 -0
- data/lib/poetry/core/token_import.rb +317 -0
- data/lib/poetry/core/tokens/color.rb +215 -0
- data/lib/poetry/core/tokens/contrast_gate.rb +120 -0
- data/lib/poetry/core/tokens/generator.rb +211 -0
- data/lib/poetry/core/tokens.rb +117 -0
- data/lib/poetry/core/version.rb +9 -0
- data/lib/poetry/core.rb +52 -0
- data/lib/poetry-core.rb +5 -0
- data/tokens/aliases.css +145 -0
- data/tokens/tailwind-theme.css +47 -0
- data/tokens/tokens.css +78 -0
- data/tokens/tokens.dtcg.json +779 -0
- data/vendor/floating-ui/LICENSE +20 -0
- data/vendor/floating-ui/VENDORED_VERSIONS +15 -0
- data/vendor/shadcn-tailwind/LICENSE +21 -0
- data/vendor/shadcn-tailwind/VENDORED_COMMIT +1 -0
- data/vendor/shadcn-tailwind/tailwind.css +629 -0
- data/vendor/tw-animate-css/LICENSE +21 -0
- data/vendor/tw-animate-css/VENDORED_VERSION +1 -0
- data/vendor/tw-animate-css/tw-animate.css +1 -0
- metadata +267 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { isImeKeydown } from "@poetry/controllers/helpers/escape"
|
|
3
|
+
import { setState } from "@poetry/controllers/helpers/state"
|
|
4
|
+
import { enterPresence, exitPresence, flushPendingExits } from "@poetry/controllers/helpers/presence"
|
|
5
|
+
import { onBeforeCache } from "@poetry/controllers/helpers/turbo_cache"
|
|
6
|
+
|
|
7
|
+
// The NavigationMenu coordinator (the viewport=false mode): a
|
|
8
|
+
// DISCLOSURE BAR, not a menu - Tab moves through triggers and links
|
|
9
|
+
// normally, arrows are convenience navigation, nothing traps. Each item's
|
|
10
|
+
// panel is its own popup positioned under the item; this controller owns
|
|
11
|
+
// what per-item popovers can't: ONE panel open at a time, hover intent
|
|
12
|
+
// (open/close delays so diagonal travel into a panel doesn't flicker),
|
|
13
|
+
// Esc + focus-out + outside-press closing, and the vocabulary writes
|
|
14
|
+
// (data-popup-open on the trigger - the chevron's rotation hook - and the
|
|
15
|
+
// presence-driven open/closed pair on the panel).
|
|
16
|
+
//
|
|
17
|
+
// THE VIEWPORT MODE: when the markup ships the shared
|
|
18
|
+
// positioner > popup > viewport shell (viewport: true), panels are
|
|
19
|
+
// lazily ADOPTED into the viewport on first activation (the Rails
|
|
20
|
+
// stand-in for a portal - server-rendered content stays in
|
|
21
|
+
// place until JS activates) and the composite MORPHS: the popper
|
|
22
|
+
// re-anchors to the active trigger (full floating-ui) while CSS
|
|
23
|
+
// transitions the positioner's insets, the popup's --popup-width/height
|
|
24
|
+
// pin old -> new across two frames so width/height transition, panels
|
|
25
|
+
// slide by data-activation-direction (new trigger vs old - travel
|
|
26
|
+
// direction drives the slide), and data-instant suppresses transitions
|
|
27
|
+
// on cold opens. Vars reset to auto after the animations finish (the
|
|
28
|
+
// auto-size reset via getAnimations().finished).
|
|
29
|
+
const TRIGGER_SELECTOR = '[data-slot="navigation-menu-trigger"]'
|
|
30
|
+
const PANEL_SELECTOR = '[data-slot="navigation-menu-content"]'
|
|
31
|
+
const ITEM_SELECTOR = '[data-slot="navigation-menu-item"]'
|
|
32
|
+
const POSITIONER_SELECTOR = '[data-slot="navigation-menu-positioner"]'
|
|
33
|
+
const POPUP_SELECTOR = '[data-slot="navigation-menu-popup"]'
|
|
34
|
+
const VIEWPORT_SELECTOR = '[data-slot="navigation-menu-viewport"]'
|
|
35
|
+
|
|
36
|
+
export default class NavigationMenuController extends Controller {
|
|
37
|
+
static values = {
|
|
38
|
+
openDelay: { type: Number, default: 50 },
|
|
39
|
+
closeDelay: { type: Number, default: 150 }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#openValue = null
|
|
43
|
+
#timer = null
|
|
44
|
+
#cancelExit = new Map() // value -> abandon-this-panel's-exit (per panel, not global)
|
|
45
|
+
#onOutsidePress = null
|
|
46
|
+
#unsubscribeBeforeCache = null
|
|
47
|
+
#sizeGeneration = 0
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Subscribes the before-cache close (the body comment holds the zombie
|
|
51
|
+
* rules).
|
|
52
|
+
*/
|
|
53
|
+
connect() {
|
|
54
|
+
// Close before Turbo snapshots: an open panel serialized into the
|
|
55
|
+
// cache restores with data-open (viewport-mode panels also serialize
|
|
56
|
+
// adopted into the shared viewport). The explicit flush completes the
|
|
57
|
+
// exit THIS close just started, regardless of listener order (the
|
|
58
|
+
// dismissable layer's pattern).
|
|
59
|
+
this.#unsubscribeBeforeCache = onBeforeCache(() => {
|
|
60
|
+
if (this.#openValue === null) return
|
|
61
|
+
this.#clearTimer()
|
|
62
|
+
this.#close()
|
|
63
|
+
flushPendingExits()
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Clears the timer and unwires the outside-press and before-cache
|
|
69
|
+
* listeners.
|
|
70
|
+
*/
|
|
71
|
+
disconnect() {
|
|
72
|
+
this.#clearTimer()
|
|
73
|
+
this.#unbindOutsidePress()
|
|
74
|
+
this.#unsubscribeBeforeCache?.()
|
|
75
|
+
this.#unsubscribeBeforeCache = null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Each trigger's click action - immediate, cancels hover intent.
|
|
80
|
+
*
|
|
81
|
+
* @param {MouseEvent} event
|
|
82
|
+
*/
|
|
83
|
+
toggle(event) {
|
|
84
|
+
const value = this.#valueFrom(event)
|
|
85
|
+
if (value === null) return
|
|
86
|
+
|
|
87
|
+
this.#clearTimer()
|
|
88
|
+
if (this.#openValue === value) this.#close()
|
|
89
|
+
else this.#open(value)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Each ITEM's pointerenter action (the panel lives inside its item, so
|
|
94
|
+
* moving into the panel never schedules a close): arms hover intent -
|
|
95
|
+
* instant switch while the bar is already open, the open delay on cold
|
|
96
|
+
* entry. Touch is click's job.
|
|
97
|
+
*
|
|
98
|
+
* @param {PointerEvent} event
|
|
99
|
+
*/
|
|
100
|
+
scheduleOpen(event) {
|
|
101
|
+
if (event.pointerType === "touch") return // touch is click's job
|
|
102
|
+
|
|
103
|
+
const value = this.#valueFrom(event)
|
|
104
|
+
if (value === null) return
|
|
105
|
+
if (value === this.#openValue) {
|
|
106
|
+
this.#clearTimer() // re-entering the open item cancels a pending close
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Instant switch while the bar is already open (the mega-menu feel);
|
|
111
|
+
// intent delay only on cold entry.
|
|
112
|
+
this.#schedule(() => this.#open(value), this.#openValue === null ? this.openDelayValue : 0)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Each ITEM's pointerleave action: arms the close grace.
|
|
117
|
+
*
|
|
118
|
+
* @param {PointerEvent} event
|
|
119
|
+
*/
|
|
120
|
+
scheduleClose(event) {
|
|
121
|
+
if (event.pointerType === "touch") return
|
|
122
|
+
if (this.#openValue === null) return
|
|
123
|
+
|
|
124
|
+
this.#schedule(() => this.#close(), this.closeDelayValue)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The shared positioner's pointerenter action - in viewport mode the
|
|
129
|
+
* panel no longer lives inside its item, so entering the popup must
|
|
130
|
+
* cancel a pending close.
|
|
131
|
+
*/
|
|
132
|
+
cancelClose() {
|
|
133
|
+
this.#clearTimer()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The root's keydown action: Escape closes and refocuses the trigger;
|
|
138
|
+
* ArrowLeft/Right move between the bar's stops (triggers and top-level
|
|
139
|
+
* links).
|
|
140
|
+
*
|
|
141
|
+
* @param {KeyboardEvent} event
|
|
142
|
+
*/
|
|
143
|
+
keydown(event) {
|
|
144
|
+
if (event.key === "Escape") {
|
|
145
|
+
if (isImeKeydown(event)) return
|
|
146
|
+
if (this.#openValue === null) return
|
|
147
|
+
|
|
148
|
+
const trigger = this.#triggerFor(this.#openValue)
|
|
149
|
+
this.#close()
|
|
150
|
+
trigger?.focus()
|
|
151
|
+
event.preventDefault()
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return
|
|
156
|
+
|
|
157
|
+
const stops = this.#arrowStops()
|
|
158
|
+
const index = stops.indexOf(event.target)
|
|
159
|
+
if (index === -1) return
|
|
160
|
+
|
|
161
|
+
const next = stops[index + (event.key === "ArrowRight" ? 1 : -1)]
|
|
162
|
+
if (!next) return
|
|
163
|
+
|
|
164
|
+
event.preventDefault()
|
|
165
|
+
next.focus()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The root's focusout action - a disclosure closes when focus leaves it
|
|
170
|
+
* entirely (never traps).
|
|
171
|
+
*
|
|
172
|
+
* @param {FocusEvent} event
|
|
173
|
+
*/
|
|
174
|
+
focusLeft(event) {
|
|
175
|
+
const next = event.relatedTarget
|
|
176
|
+
|
|
177
|
+
if (next instanceof Element && this.element.contains(next)) return
|
|
178
|
+
|
|
179
|
+
this.#clearTimer()
|
|
180
|
+
this.#close()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#open(value) {
|
|
184
|
+
if (this.#openValue === value) return
|
|
185
|
+
|
|
186
|
+
const previous = this.#openValue
|
|
187
|
+
if (this.#viewport() && previous !== null) this.#stampDirection(previous, value)
|
|
188
|
+
if (previous !== null) this.#hidePanel(previous)
|
|
189
|
+
|
|
190
|
+
this.#openValue = value
|
|
191
|
+
const trigger = this.#triggerFor(value)
|
|
192
|
+
const panel = this.#panelFor(value)
|
|
193
|
+
|
|
194
|
+
if (trigger) {
|
|
195
|
+
trigger.setAttribute("aria-expanded", "true")
|
|
196
|
+
setState(trigger, "popup-open")
|
|
197
|
+
setState(trigger, "open")
|
|
198
|
+
}
|
|
199
|
+
if (panel) {
|
|
200
|
+
// Re-opening THIS panel mid-close: abandon only its own exit (a
|
|
201
|
+
// sibling's pending exit must still run onRemove and hide it).
|
|
202
|
+
this.#cancelExit.get(value)?.()
|
|
203
|
+
this.#cancelExit.delete(value)
|
|
204
|
+
if (this.#viewport()) {
|
|
205
|
+
this.#showInViewport(panel, trigger, previous)
|
|
206
|
+
} else {
|
|
207
|
+
panel.hidden = false
|
|
208
|
+
enterPresence(panel)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
this.#bindOutsidePress()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#close() {
|
|
215
|
+
if (this.#openValue === null) return
|
|
216
|
+
|
|
217
|
+
this.#hidePanel(this.#openValue)
|
|
218
|
+
this.#openValue = null
|
|
219
|
+
this.#unbindOutsidePress()
|
|
220
|
+
this.#closeViewport()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// -- the morphing viewport -----------------------------------------------
|
|
224
|
+
|
|
225
|
+
#viewport() {
|
|
226
|
+
return this.element.querySelector(VIEWPORT_SELECTOR)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
#showInViewport(panel, trigger, previous) {
|
|
230
|
+
const viewport = this.#viewport()
|
|
231
|
+
const positioner = this.element.querySelector(POSITIONER_SELECTOR)
|
|
232
|
+
const popup = this.element.querySelector(POPUP_SELECTOR)
|
|
233
|
+
const cold = previous === null
|
|
234
|
+
|
|
235
|
+
// Lazy adoption: the server-rendered panel moves from its item into
|
|
236
|
+
// the shared viewport on FIRST activation (the portal's Rails
|
|
237
|
+
// stand-in - no-JS content stays in place until JS activates).
|
|
238
|
+
if (panel.parentElement !== viewport) {
|
|
239
|
+
panel.setAttribute("data-viewport-panel", "")
|
|
240
|
+
viewport.appendChild(panel)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// The size choreography: pin the popup
|
|
244
|
+
// to the OLD size, reveal the new panel, measure its natural size
|
|
245
|
+
// (panels are absolutely stacked, so they size intrinsically), then
|
|
246
|
+
// pin the NEW size a frame later so width/height transition.
|
|
247
|
+
const oldWidth = popup.offsetWidth
|
|
248
|
+
const oldHeight = popup.offsetHeight
|
|
249
|
+
|
|
250
|
+
panel.hidden = false
|
|
251
|
+
const nextWidth = panel.offsetWidth
|
|
252
|
+
const nextHeight = panel.offsetHeight
|
|
253
|
+
|
|
254
|
+
if (cold) {
|
|
255
|
+
this.#stampInstant(positioner, popup) // no transition on a cold open
|
|
256
|
+
this.#pinSize(positioner, popup, nextWidth, nextHeight)
|
|
257
|
+
this.#scheduleSizeReset(positioner, popup)
|
|
258
|
+
} else {
|
|
259
|
+
// Pin old, pin new a frame later (the width/height transition), and
|
|
260
|
+
// only THEN arm the settle reset - armed alongside the OLD pin it
|
|
261
|
+
// samples getAnimations before the transition exists, resets to auto
|
|
262
|
+
// synchronously, and the late new-size pin sticks forever (the D3
|
|
263
|
+
// Chrome-proof catch: the morph never ran).
|
|
264
|
+
this.#pinSize(positioner, popup, oldWidth, oldHeight)
|
|
265
|
+
this.#nextFrame(() => {
|
|
266
|
+
this.#pinSize(positioner, popup, nextWidth, nextHeight)
|
|
267
|
+
this.#nextFrame(() => this.#scheduleSizeReset(positioner, popup))
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
positioner.hidden = false
|
|
272
|
+
setState(popup, "open")
|
|
273
|
+
enterPresence(panel)
|
|
274
|
+
this.#anchorPopper(trigger)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#closeViewport() {
|
|
278
|
+
const positioner = this.element.querySelector(POSITIONER_SELECTOR)
|
|
279
|
+
const popup = this.element.querySelector(POPUP_SELECTOR)
|
|
280
|
+
if (!positioner || positioner.hidden) return
|
|
281
|
+
|
|
282
|
+
setState(popup, "closed")
|
|
283
|
+
positioner.hidden = true
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// The popper (full floating-ui) re-anchors to the active
|
|
287
|
+
// trigger; the positioner's inset transition gives the slide.
|
|
288
|
+
#anchorPopper(trigger) {
|
|
289
|
+
if (!trigger) return
|
|
290
|
+
|
|
291
|
+
const popper =
|
|
292
|
+
this.application.getControllerForElementAndIdentifier(this.element, "poetry--core--popper")
|
|
293
|
+
popper?.setAnchorElement(trigger)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// data-activation-direction: which way the activation traveled (the new
|
|
297
|
+
// trigger relative to the old); outgoing AND incoming panels wear it so
|
|
298
|
+
// the starting/ending-style slides read one direction.
|
|
299
|
+
#stampDirection(previousValue, nextValue) {
|
|
300
|
+
const previous = this.#triggerFor(previousValue)
|
|
301
|
+
const next = this.#triggerFor(nextValue)
|
|
302
|
+
if (!previous || !next) return
|
|
303
|
+
|
|
304
|
+
const delta = next.getBoundingClientRect().left - previous.getBoundingClientRect().left
|
|
305
|
+
if (delta === 0) return
|
|
306
|
+
|
|
307
|
+
const direction = delta > 0 ? "right" : "left"
|
|
308
|
+
for (const panel of [this.#panelFor(previousValue), this.#panelFor(nextValue)]) {
|
|
309
|
+
panel?.setAttribute("data-activation-direction", direction)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Pin the shared size vars (popup + positioner move together).
|
|
314
|
+
#pinSize(positioner, popup, width, height) {
|
|
315
|
+
popup.style.setProperty("--popup-width", `${width}px`)
|
|
316
|
+
popup.style.setProperty("--popup-height", `${height}px`)
|
|
317
|
+
positioner.style.setProperty("--positioner-width", `${width}px`)
|
|
318
|
+
positioner.style.setProperty("--positioner-height", `${height}px`)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// The auto-size reset: once the morph settles the vars return
|
|
322
|
+
// to auto, so later content growth isn't clipped. A newer activation
|
|
323
|
+
// cancels a stale reset (the generation counter).
|
|
324
|
+
#scheduleSizeReset(positioner, popup) {
|
|
325
|
+
const generation = ++this.#sizeGeneration
|
|
326
|
+
const reset = () => {
|
|
327
|
+
if (generation !== this.#sizeGeneration) return
|
|
328
|
+
|
|
329
|
+
this.#pinAuto(positioner, popup)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (typeof popup.getAnimations === "function") {
|
|
333
|
+
const animations = popup.getAnimations()
|
|
334
|
+
if (animations.length > 0) {
|
|
335
|
+
Promise.allSettled(animations.map((animation) => animation.finished)).then(reset)
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
reset()
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
#pinAuto(positioner, popup) {
|
|
343
|
+
popup.style.setProperty("--popup-width", "auto")
|
|
344
|
+
popup.style.setProperty("--popup-height", "auto")
|
|
345
|
+
positioner.style.setProperty("--positioner-width", "auto")
|
|
346
|
+
positioner.style.setProperty("--positioner-height", "auto")
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// data-instant suppresses the positioner/popup transitions for one
|
|
350
|
+
// painted frame (cold opens must not animate from nothing).
|
|
351
|
+
#stampInstant(positioner, popup) {
|
|
352
|
+
positioner.setAttribute("data-instant", "")
|
|
353
|
+
popup.setAttribute("data-instant", "")
|
|
354
|
+
this.#nextFrame(() => this.#nextFrame(() => {
|
|
355
|
+
positioner.removeAttribute("data-instant")
|
|
356
|
+
popup.removeAttribute("data-instant")
|
|
357
|
+
}))
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
#nextFrame(callback) {
|
|
361
|
+
if (typeof requestAnimationFrame === "function") requestAnimationFrame(callback)
|
|
362
|
+
else callback()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
#hidePanel(value) {
|
|
366
|
+
const trigger = this.#triggerFor(value)
|
|
367
|
+
const panel = this.#panelFor(value)
|
|
368
|
+
|
|
369
|
+
if (trigger) {
|
|
370
|
+
trigger.setAttribute("aria-expanded", "false")
|
|
371
|
+
setState(trigger, "popup-closed")
|
|
372
|
+
setState(trigger, "closed")
|
|
373
|
+
}
|
|
374
|
+
if (panel) {
|
|
375
|
+
this.#cancelExit.set(value, exitPresence(panel, {
|
|
376
|
+
onRemove: () => {
|
|
377
|
+
panel.hidden = true
|
|
378
|
+
this.#cancelExit.delete(value)
|
|
379
|
+
}
|
|
380
|
+
}))
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Outside press closes - bound only while open (no idle listener).
|
|
385
|
+
#bindOutsidePress() {
|
|
386
|
+
if (this.#onOutsidePress) return
|
|
387
|
+
|
|
388
|
+
this.#onOutsidePress = (event) => {
|
|
389
|
+
// The dismissable layer's press rules: a disconnected target says
|
|
390
|
+
// nothing about WHERE the press landed (the false-dismiss class);
|
|
391
|
+
// composedPath is fixed at dispatch, so a press on a node an inner
|
|
392
|
+
// handler removed still counts as inside; and the top layer
|
|
393
|
+
// (toasts) sits above every dismissal surface by fiat.
|
|
394
|
+
if (!(event.target instanceof Element) || !event.target.isConnected) return
|
|
395
|
+
if (event.composedPath().includes(this.element)) return
|
|
396
|
+
if (event.target.closest("[data-poetry-top-layer]")) return
|
|
397
|
+
|
|
398
|
+
this.#clearTimer()
|
|
399
|
+
this.#close()
|
|
400
|
+
}
|
|
401
|
+
document.addEventListener("pointerdown", this.#onOutsidePress)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
#unbindOutsidePress() {
|
|
405
|
+
if (!this.#onOutsidePress) return
|
|
406
|
+
|
|
407
|
+
document.removeEventListener("pointerdown", this.#onOutsidePress)
|
|
408
|
+
this.#onOutsidePress = null
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
#schedule(action, delay) {
|
|
412
|
+
this.#clearTimer()
|
|
413
|
+
this.#timer = window.setTimeout(() => {
|
|
414
|
+
this.#timer = null
|
|
415
|
+
action()
|
|
416
|
+
}, delay)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
#clearTimer() {
|
|
420
|
+
if (this.#timer === null) return
|
|
421
|
+
|
|
422
|
+
window.clearTimeout(this.#timer)
|
|
423
|
+
this.#timer = null
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
#valueFrom(event) {
|
|
427
|
+
const origin = event.target instanceof Element ? event.target : null
|
|
428
|
+
const item = origin?.closest(ITEM_SELECTOR)
|
|
429
|
+
|
|
430
|
+
return item?.dataset.value ?? null
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
#triggerFor(value) {
|
|
434
|
+
return this.#itemFor(value)?.querySelector(TRIGGER_SELECTOR) ?? null
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// An adopted panel (viewport mode) no longer lives inside its item -
|
|
438
|
+
// the trigger's aria-controls id finds it wherever it moved.
|
|
439
|
+
#panelFor(value) {
|
|
440
|
+
const inItem = this.#itemFor(value)?.querySelector(PANEL_SELECTOR)
|
|
441
|
+
if (inItem) return inItem
|
|
442
|
+
|
|
443
|
+
const id = this.#triggerFor(value)?.getAttribute("aria-controls")
|
|
444
|
+
return id ? document.getElementById(id) : null
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
#itemFor(value) {
|
|
448
|
+
return [...this.element.querySelectorAll(ITEM_SELECTOR)]
|
|
449
|
+
.find((item) => item.dataset.value === value) ?? null
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Arrow stops: every trigger and top-level link in the bar, DOM order.
|
|
453
|
+
#arrowStops() {
|
|
454
|
+
return [...this.element.querySelectorAll(
|
|
455
|
+
`${TRIGGER_SELECTOR}, [data-slot="navigation-menu-list"] > ${ITEM_SELECTOR} > a`
|
|
456
|
+
)]
|
|
457
|
+
}
|
|
458
|
+
}
|