studio-engine 0.93.0 → 0.94.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 +61 -0
- data/app/javascript/studio/alpine_shims.js +45 -0
- data/app/javascript/studio/alpine_stores.js +82 -0
- data/app/javascript/studio/application.js +37 -0
- data/app/javascript/studio/controllers/nav_collapse_controller.js +28 -0
- data/app/javascript/studio/head_chrome.js +79 -0
- data/app/javascript/studio/nav_collapse.js +238 -0
- data/app/javascript/studio/pinned_stack.js +306 -0
- data/app/javascript/studio/vendor/stimulus.js +5 -0
- data/app/views/layouts/_navbar.html.erb +14 -6
- data/app/views/layouts/studio/_head.html.erb +59 -567
- data/config/importmap.rb +14 -3
- data/lib/studio/engine.rb +26 -0
- data/lib/studio/version.rb +1 -1
- metadata +9 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// studio/pinned_stack: THE PINNED STACK. Publishes the live geometry of every layer
|
|
2
|
+
// of pinned page chrome as CSS custom properties, so anything fixed or sticky
|
|
3
|
+
// positions off it in CSS with no hardcoded px and no JS of its own.
|
|
4
|
+
//
|
|
5
|
+
// A layer joins by carrying `data-pin="<name>"`. Nothing else is required:
|
|
6
|
+
// no registration call, no declared stacking order, no consumer edit.
|
|
7
|
+
//
|
|
8
|
+
// WHAT A CONSUMER READS — prefer the first two; they are the reason this is a
|
|
9
|
+
// primitive rather than an arithmetic helper:
|
|
10
|
+
//
|
|
11
|
+
// --pin-stack-bottom the bottom of the WHOLE stack. What something sitting
|
|
12
|
+
// BENEATH all the pinned chrome positions off.
|
|
13
|
+
// --pin-<name>-top the bottom of everything ABOVE that layer. What a layer
|
|
14
|
+
// that is ITSELF in the stack positions off.
|
|
15
|
+
// --pin-<name>-h that layer's HEIGHT.
|
|
16
|
+
// --pin-<name>-bottom that layer's own BOTTOM EDGE, in viewport coordinates.
|
|
17
|
+
// --nav-h / --nav-bottom the <header>'s height and bottom edge, under their
|
|
18
|
+
// legacy names, from the same measurement.
|
|
19
|
+
//
|
|
20
|
+
// DO NOT COMPOSE THE STACK IN A CONSUMER. `top: max(var(--pin-nav-bottom),
|
|
21
|
+
// var(--pin-apps-bottom))` is what this replaced, and it fails twice: it does
|
|
22
|
+
// not scale, because a fourth layer means editing every consumer that ever
|
|
23
|
+
// wanted to sit under the stack; and it is not sound, because a max() over two
|
|
24
|
+
// custom properties is only meaningful if they were written in the same frame.
|
|
25
|
+
// --pin-stack-bottom is one value that cannot disagree with itself.
|
|
26
|
+
//
|
|
27
|
+
// --nav-h AND --nav-bottom ARE NOT THE SAME NUMBER. They are equal only when
|
|
28
|
+
// the header sits at the top of the viewport, which is why one was long
|
|
29
|
+
// mistaken for the other. An app that stacks chrome ABOVE the header — an
|
|
30
|
+
// environment banner rendered before the navbar, as mcritchie-industries does —
|
|
31
|
+
// pushes the header down, and a `fixed` overlay positioned at --nav-h then
|
|
32
|
+
// rides UP over the header by exactly the height of that chrome. Use --nav-h to
|
|
33
|
+
// SIZE something as tall as the header; use --nav-bottom to START something
|
|
34
|
+
// underneath it. Same distinction for --pin-<name>-h vs --pin-<name>-bottom.
|
|
35
|
+
//
|
|
36
|
+
// Started once per document by studio/application. The listeners it adds live
|
|
37
|
+
// for the document, and a Turbo visit republishes through them rather than
|
|
38
|
+
// starting it again.
|
|
39
|
+
|
|
40
|
+
// THE STACK, COMPOSED HERE RATHER THAN BY EVERY CONSUMER.
|
|
41
|
+
//
|
|
42
|
+
// Consumers used to write `top: max(var(--pin-nav-bottom), var(--pin-apps-bottom))`
|
|
43
|
+
// — every layer enumerated, in every consumer. That is two failures at once.
|
|
44
|
+
// It does not SCALE: a fourth pinned layer means editing every consumer that
|
|
45
|
+
// ever wanted to sit under the stack. And it is not SOUND: max() over two
|
|
46
|
+
// independently-written custom properties is only meaningful if they were
|
|
47
|
+
// written in the same frame, and any skew between them is expressed instantly
|
|
48
|
+
// as layout. Composing here means a consumer reads ONE value that cannot
|
|
49
|
+
// disagree with itself.
|
|
50
|
+
//
|
|
51
|
+
// --pin-stack-bottom the bottom of the WHOLE pinned stack. What something
|
|
52
|
+
// sitting beneath all the chrome positions off.
|
|
53
|
+
// --pin-<name>-top the bottom of everything ABOVE that layer. What a
|
|
54
|
+
// layer that is ITSELF in the stack positions off —
|
|
55
|
+
// --pin-stack-bottom would include the layer's own
|
|
56
|
+
// edge, so a layer positioned off it would chase itself
|
|
57
|
+
// down the page.
|
|
58
|
+
//
|
|
59
|
+
// ORDER is document order, which is the order the layers were declared and
|
|
60
|
+
// therefore the order they read in. `data-pin-order` overrides it for a layer
|
|
61
|
+
// whose DOM position does not match where it sits on screen; it is a plain
|
|
62
|
+
// number, lower is higher up, and it needs to exist on only the layer that
|
|
63
|
+
// disagrees rather than on all of them.
|
|
64
|
+
export function order(p) {
|
|
65
|
+
var raw = parseFloat(p.el.getAttribute('data-pin-order'));
|
|
66
|
+
return isNaN(raw) ? null : raw;
|
|
67
|
+
}
|
|
68
|
+
export function composed(pins) {
|
|
69
|
+
var i, j, stack = 0;
|
|
70
|
+
for (i = 0; i < pins.length; i++) {
|
|
71
|
+
if (pins[i].bottom > stack) stack = pins[i].bottom;
|
|
72
|
+
var above = 0;
|
|
73
|
+
for (j = 0; j < pins.length; j++) {
|
|
74
|
+
if (i === j) continue;
|
|
75
|
+
var oi = order(pins[i]), oj = order(pins[j]);
|
|
76
|
+
// Both ordered: compare the numbers. Otherwise fall back to document
|
|
77
|
+
// order, which querySelectorAll already returns them in.
|
|
78
|
+
var jIsAbove = (oi !== null && oj !== null) ? oj < oi : j < i;
|
|
79
|
+
if (jIsAbove && pins[j].bottom > above) above = pins[j].bottom;
|
|
80
|
+
}
|
|
81
|
+
pins[i].top = above;
|
|
82
|
+
}
|
|
83
|
+
return stack;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
var started = false
|
|
87
|
+
|
|
88
|
+
export function startPinnedStack() {
|
|
89
|
+
if (started || !window.ResizeObserver) return;
|
|
90
|
+
started = true;
|
|
91
|
+
var ro = null;
|
|
92
|
+
var queued = false;
|
|
93
|
+
// WHAT WAS LAST PUBLISHED, per layer name: { h, bottom, top }. It does two
|
|
94
|
+
// jobs. It is the record of which names this publisher has written, so a
|
|
95
|
+
// DEPARTED layer can be cleared. And it is what lets an UNCHANGED value skip
|
|
96
|
+
// its write — these are INHERITED custom properties on documentElement, so
|
|
97
|
+
// even a write of the same number invalidates style for the whole document,
|
|
98
|
+
// and a scroll frame touches this four times per layer.
|
|
99
|
+
var last = {};
|
|
100
|
+
var lastStack = null;
|
|
101
|
+
// SKIPPING AN UNCHANGED WRITE IS ONLY SAFE WHILE NOTHING ELSE TOUCHES THESE
|
|
102
|
+
// PROPERTIES. On a scroll frame that holds, and skipping is most of why this
|
|
103
|
+
// is cheap. On a STRUCTURAL change it does not: a layer arriving or leaving,
|
|
104
|
+
// a Turbo patch, a fresh document — anything there may have cleared a
|
|
105
|
+
// property out from under the cache, and a skip would then never write it
|
|
106
|
+
// again. So a structural change forces a full write, once.
|
|
107
|
+
var forceWrite = true;
|
|
108
|
+
// The nodes currently under observation, held only to know what to unobserve
|
|
109
|
+
// when one departs. The REGISTRY ITSELF is never cached; see readPins().
|
|
110
|
+
var observed = [];
|
|
111
|
+
|
|
112
|
+
// THE REGISTRY IS DERIVED, NEVER CACHED — and that is a correctness rule.
|
|
113
|
+
//
|
|
114
|
+
// This used to build a `pins` array once and hold each element by reference
|
|
115
|
+
// until something re-registered it. A Turbo Stream replaces a pinned layer's
|
|
116
|
+
// node WITHOUT a turbo:load, so between the patch and the re-scan the held
|
|
117
|
+
// reference pointed at the DETACHED predecessor — and a detached node's rect
|
|
118
|
+
// is all zeros, which is indistinguishable from a layer that is legitimately
|
|
119
|
+
// hidden. Measured on mcritchie-studio's /deployments: --pin-apps-bottom
|
|
120
|
+
// published 0px while the live strip stood at display:block, bottom 152px,
|
|
121
|
+
// and the board's lane headers slammed 99px up and back on every broadcast.
|
|
122
|
+
//
|
|
123
|
+
// Re-querying costs one querySelectorAll over a handful of nodes per publish.
|
|
124
|
+
// That is cheaper than the class of bug it removes, because it makes a stale
|
|
125
|
+
// reference IMPOSSIBLE rather than merely unlikely: there is no window between
|
|
126
|
+
// a DOM patch and a re-scan in which this can be pointed at the wrong node.
|
|
127
|
+
function readPins() {
|
|
128
|
+
var header = document.querySelector('header');
|
|
129
|
+
var nodes = document.querySelectorAll('[data-pin]');
|
|
130
|
+
var list = [];
|
|
131
|
+
var headerPinned = false;
|
|
132
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
133
|
+
var name = nodes[i].getAttribute('data-pin');
|
|
134
|
+
// An unnamed pin would write `--pin--h`, a valid property name and a
|
|
135
|
+
// silent nonsense one. Skip it rather than publish it.
|
|
136
|
+
if (!name) continue;
|
|
137
|
+
if (nodes[i] === header) headerPinned = true;
|
|
138
|
+
list.push(measure(nodes[i], name, nodes[i] === header));
|
|
139
|
+
}
|
|
140
|
+
// THE HOST-OWNED HEADER, and the back-compat promise that depends on it.
|
|
141
|
+
// A header that does not carry [data-pin] is not in the registry, so the
|
|
142
|
+
// legacy --nav-h / --nav-bottom — which studio/_sidebar_panel, turf's
|
|
143
|
+
// contest board and mcritchie-studio's heartbeat CSS all read — would
|
|
144
|
+
// never publish at all. Both live consumers own their header, so it is
|
|
145
|
+
// adopted as a pin even when it does not ask to be.
|
|
146
|
+
if (header && !headerPinned) list.push(measure(header, 'nav', true));
|
|
147
|
+
return list;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ONE READ PER ELEMENT PER PUBLISH, and every read before every write.
|
|
151
|
+
// A read after a write to the root forces a fresh layout, which is the
|
|
152
|
+
// thrash this publisher exists without. Measured by review at 6x CPU
|
|
153
|
+
// throttle over 175 frames: frames past 20ms 62 vs 42, median 17.7 vs
|
|
154
|
+
// 14.9ms, p90 34.2 vs 26.0ms; at 10x, 84/180 vs 4/180.
|
|
155
|
+
function measure(el, name, legacy) {
|
|
156
|
+
// A hidden layer needs no special case: display:none gives offsetHeight 0
|
|
157
|
+
// and an all-zero rect, so it measures 0 and drops out of the stack on its
|
|
158
|
+
// own. That is what lets a layer come and go without anyone declaring a
|
|
159
|
+
// stacking order, and it is load-bearing rather than incidental.
|
|
160
|
+
var r = el.getBoundingClientRect();
|
|
161
|
+
return { el: el, name: name, legacy: legacy, h: el.offsetHeight, bottom: Math.max(0, r.bottom) };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
function write(pins, stack) {
|
|
166
|
+
var style = document.documentElement.style;
|
|
167
|
+
var seen = {};
|
|
168
|
+
var i, p, prev;
|
|
169
|
+
// A ROSTER CHANGE IS STRUCTURAL TOO, and it is not always announced by an
|
|
170
|
+
// event: an x-show that adds the node, a layer rendered by something other
|
|
171
|
+
// than a stream. Compare the names against what was published last.
|
|
172
|
+
var force = forceWrite;
|
|
173
|
+
forceWrite = false;
|
|
174
|
+
if (!force) {
|
|
175
|
+
for (i = 0; i < pins.length; i++) { if (!last.hasOwnProperty(pins[i].name)) { force = true; break; } }
|
|
176
|
+
}
|
|
177
|
+
for (i = 0; i < pins.length; i++) {
|
|
178
|
+
p = pins[i];
|
|
179
|
+
seen[p.name] = true;
|
|
180
|
+
prev = last[p.name] || {};
|
|
181
|
+
if (force || p.h !== prev.h) {
|
|
182
|
+
style.setProperty('--pin-' + p.name + '-h', p.h + 'px');
|
|
183
|
+
if (p.legacy) style.setProperty('--nav-h', p.h + 'px');
|
|
184
|
+
}
|
|
185
|
+
if (force || p.bottom !== prev.bottom) {
|
|
186
|
+
style.setProperty('--pin-' + p.name + '-bottom', p.bottom + 'px');
|
|
187
|
+
if (p.legacy) style.setProperty('--nav-bottom', p.bottom + 'px');
|
|
188
|
+
}
|
|
189
|
+
if (force || p.top !== prev.top) style.setProperty('--pin-' + p.name + '-top', p.top + 'px');
|
|
190
|
+
last[p.name] = { h: p.h, bottom: p.bottom, top: p.top };
|
|
191
|
+
}
|
|
192
|
+
if (force || stack !== lastStack) {
|
|
193
|
+
style.setProperty('--pin-stack-bottom', stack + 'px');
|
|
194
|
+
lastStack = stack;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// CLEAR WHAT LEFT. A layer goes away three ways: display:none and x-show
|
|
198
|
+
// both keep the node, so it measures 0 and drops out of the stack on its
|
|
199
|
+
// own. REMOVAL does not — nothing is left to measure, and without this the
|
|
200
|
+
// last published value stands forever while the consumer sits at the height
|
|
201
|
+
// of a strip that is gone. Review measured a removed 300px strip holding
|
|
202
|
+
// --pin-apps-bottom at 300px against a real stack bottom of 160px.
|
|
203
|
+
for (var name in last) {
|
|
204
|
+
if (last.hasOwnProperty(name) && !seen[name]) {
|
|
205
|
+
style.removeProperty('--pin-' + name + '-h');
|
|
206
|
+
style.removeProperty('--pin-' + name + '-bottom');
|
|
207
|
+
style.removeProperty('--pin-' + name + '-top');
|
|
208
|
+
delete last[name];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function publishAll() {
|
|
214
|
+
var pins = readPins();
|
|
215
|
+
write(pins, composed(pins));
|
|
216
|
+
syncObserver(pins);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Observe whatever is pinned RIGHT NOW. observe() on an already-observed node
|
|
220
|
+
// is a no-op, so this only ever adds the new; unobserve() drops the departed
|
|
221
|
+
// so a replaced node cannot keep waking us from outside the document.
|
|
222
|
+
function syncObserver(pins) {
|
|
223
|
+
if (!pins.length) {
|
|
224
|
+
if (ro) { ro.disconnect(); ro = null; observed = []; }
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (!ro) ro = new ResizeObserver(onResize);
|
|
228
|
+
var i, next = [];
|
|
229
|
+
for (i = 0; i < pins.length; i++) { next.push(pins[i].el); ro.observe(pins[i].el); }
|
|
230
|
+
for (i = 0; i < observed.length; i++) {
|
|
231
|
+
if (next.indexOf(observed[i]) === -1) ro.unobserve(observed[i]);
|
|
232
|
+
}
|
|
233
|
+
observed = next;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// PUBLISH IN THE ResizeObserver CALLBACK, SYNCHRONOUSLY. This is the whole
|
|
237
|
+
// fix, and it is a statement about WHEN a frame does its work rather than
|
|
238
|
+
// about what this code computes.
|
|
239
|
+
//
|
|
240
|
+
// A frame runs: rAF callbacks -> style/layout -> ResizeObserver callbacks ->
|
|
241
|
+
// paint. This publisher used to be woken by the observer and then DEFER the
|
|
242
|
+
// write to requestAnimationFrame, which lands at the top of the NEXT frame —
|
|
243
|
+
// so the frame in which a layer actually appeared or disappeared painted with
|
|
244
|
+
// the previous frame's number, every time. The RO callback runs after layout
|
|
245
|
+
// and before paint, which is exactly the last moment a write can still land
|
|
246
|
+
// in the frame that caused it.
|
|
247
|
+
//
|
|
248
|
+
// A/B measured in isolation, toggling a pinned layer's display and sampling
|
|
249
|
+
// what the changing frame paints: rAF-deferred wrong on 8/8 changing frames,
|
|
250
|
+
// RO-synchronous wrong on 0/8.
|
|
251
|
+
//
|
|
252
|
+
// This cannot loop. The values written here move CONSUMERS, and a consumer is
|
|
253
|
+
// not a pin — nothing this writes resizes anything it observes. A pinned layer
|
|
254
|
+
// that positions off --pin-<name>-top MOVES without RESIZING, and the observer
|
|
255
|
+
// fires on size, so it settles in one pass rather than ringing.
|
|
256
|
+
// The one way a SYNCHRONOUS publish could misbehave: a consumer that both
|
|
257
|
+
// reads a published value and is itself a pin, sized off what it reads
|
|
258
|
+
// (`height: calc(100vh - var(--pin-apps-top))`). That resizes an observed
|
|
259
|
+
// node from inside the observer's own callback, and left alone the browser
|
|
260
|
+
// reports "ResizeObserver loop completed with undelivered notifications" and
|
|
261
|
+
// drops the frame. So re-entry is counted, and past a couple of settling
|
|
262
|
+
// passes this hands the rest to the frame callback: the pathological page
|
|
263
|
+
// degrades to the OLD one-frame-late behaviour instead of wedging, and the
|
|
264
|
+
// ordinary page never reaches the branch. Reset on the next frame, so the
|
|
265
|
+
// count measures one frame's settling rather than the session's.
|
|
266
|
+
var depth = 0, resetQueued = false;
|
|
267
|
+
function onResize() {
|
|
268
|
+
if (depth >= 3) { schedule(); return; }
|
|
269
|
+
depth++;
|
|
270
|
+
if (!resetQueued) {
|
|
271
|
+
resetQueued = true;
|
|
272
|
+
window.requestAnimationFrame(function () { resetQueued = false; depth = 0; });
|
|
273
|
+
}
|
|
274
|
+
publishAll();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// SCROLL STILL NEEDS THE FRAME CALLBACK. A scroll changes a sticky header's
|
|
278
|
+
// bottom EDGE without changing its size, so the observer never sees it and
|
|
279
|
+
// there is nothing to be synchronous with — rAF is the right clock, and it
|
|
280
|
+
// coalesces the burst iOS momentum fires far above 60Hz.
|
|
281
|
+
function schedule() {
|
|
282
|
+
if (queued) return;
|
|
283
|
+
queued = true;
|
|
284
|
+
window.requestAnimationFrame(function () { queued = false; publishAll(); });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function invalidate() { forceWrite = true; publishAll(); }
|
|
288
|
+
document.addEventListener('DOMContentLoaded', invalidate);
|
|
289
|
+
document.addEventListener('turbo:load', invalidate);
|
|
290
|
+
// A Turbo Stream replaces nodes WITHOUT a turbo:load. The registry is derived
|
|
291
|
+
// per publish, so a stale reference is no longer possible either way — but the
|
|
292
|
+
// OBSERVER still has to be moved onto the incoming node, and the stack has to
|
|
293
|
+
// be recomposed for a layer that arrived or left. Both happen in publishAll.
|
|
294
|
+
document.addEventListener('turbo:before-stream-render', function () {
|
|
295
|
+
window.requestAnimationFrame(invalidate);
|
|
296
|
+
});
|
|
297
|
+
// Registered ONCE at start, not inside a re-scan, so a Turbo nav
|
|
298
|
+
// re-publishes instead of stacking another listener per visit.
|
|
299
|
+
window.addEventListener('scroll', schedule, { passive: true });
|
|
300
|
+
window.addEventListener('resize', schedule, { passive: true });
|
|
301
|
+
|
|
302
|
+
// A deferred module runs after the body is parsed, so the layers are already
|
|
303
|
+
// there: publish now rather than wait for DOMContentLoaded, which also
|
|
304
|
+
// covers a host that imports this after that event has fired.
|
|
305
|
+
invalidate();
|
|
306
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Stimulus 3.2.2, VENDORED from stimulus-rails 1.3.4 (app/assets/javascripts/stimulus.min.js),
|
|
2
|
+
// with its Sprockets link directives and source-map comment removed. Pinned as
|
|
3
|
+
// "@hotwired/stimulus" (config/importmap.rb); a host that pins the same name wins.
|
|
4
|
+
// Bumping is a deliberate act: replace the file and change the version here.
|
|
5
|
+
class e{constructor(e,t,s){this.eventTarget=e,this.eventName=t,this.eventOptions=s,this.unorderedBindings=new Set}connect(){this.eventTarget.addEventListener(this.eventName,this,this.eventOptions)}disconnect(){this.eventTarget.removeEventListener(this.eventName,this,this.eventOptions)}bindingConnected(e){this.unorderedBindings.add(e)}bindingDisconnected(e){this.unorderedBindings.delete(e)}handleEvent(e){const t=function(e){if("immediatePropagationStopped"in e)return e;{const{stopImmediatePropagation:t}=e;return Object.assign(e,{immediatePropagationStopped:!1,stopImmediatePropagation(){this.immediatePropagationStopped=!0,t.call(this)}})}}(e);for(const e of this.bindings){if(t.immediatePropagationStopped)break;e.handleEvent(t)}}hasBindings(){return this.unorderedBindings.size>0}get bindings(){return Array.from(this.unorderedBindings).sort(((e,t)=>{const s=e.index,r=t.index;return s<r?-1:s>r?1:0}))}}class t{constructor(e){this.application=e,this.eventListenerMaps=new Map,this.started=!1}start(){this.started||(this.started=!0,this.eventListeners.forEach((e=>e.connect())))}stop(){this.started&&(this.started=!1,this.eventListeners.forEach((e=>e.disconnect())))}get eventListeners(){return Array.from(this.eventListenerMaps.values()).reduce(((e,t)=>e.concat(Array.from(t.values()))),[])}bindingConnected(e){this.fetchEventListenerForBinding(e).bindingConnected(e)}bindingDisconnected(e,t=!1){this.fetchEventListenerForBinding(e).bindingDisconnected(e),t&&this.clearEventListenersForBinding(e)}handleError(e,t,s={}){this.application.handleError(e,`Error ${t}`,s)}clearEventListenersForBinding(e){const t=this.fetchEventListenerForBinding(e);t.hasBindings()||(t.disconnect(),this.removeMappedEventListenerFor(e))}removeMappedEventListenerFor(e){const{eventTarget:t,eventName:s,eventOptions:r}=e,n=this.fetchEventListenerMapForEventTarget(t),i=this.cacheKey(s,r);n.delete(i),0==n.size&&this.eventListenerMaps.delete(t)}fetchEventListenerForBinding(e){const{eventTarget:t,eventName:s,eventOptions:r}=e;return this.fetchEventListener(t,s,r)}fetchEventListener(e,t,s){const r=this.fetchEventListenerMapForEventTarget(e),n=this.cacheKey(t,s);let i=r.get(n);return i||(i=this.createEventListener(e,t,s),r.set(n,i)),i}createEventListener(t,s,r){const n=new e(t,s,r);return this.started&&n.connect(),n}fetchEventListenerMapForEventTarget(e){let t=this.eventListenerMaps.get(e);return t||(t=new Map,this.eventListenerMaps.set(e,t)),t}cacheKey(e,t){const s=[e];return Object.keys(t).sort().forEach((e=>{s.push(`${t[e]?"":"!"}${e}`)})),s.join(":")}}const s={stop:({event:e,value:t})=>(t&&e.stopPropagation(),!0),prevent:({event:e,value:t})=>(t&&e.preventDefault(),!0),self:({event:e,value:t,element:s})=>!t||s===e.target},r=/^(?:(?:([^.]+?)\+)?(.+?)(?:\.(.+?))?(?:@(window|document))?->)?(.+?)(?:#([^:]+?))(?::(.+))?$/;function n(e){return"window"==e?window:"document"==e?document:void 0}function i(e){return e.replace(/(?:[_-])([a-z0-9])/g,((e,t)=>t.toUpperCase()))}function o(e){return i(e.replace(/--/g,"-").replace(/__/g,"_"))}function a(e){return e.charAt(0).toUpperCase()+e.slice(1)}function c(e){return e.replace(/([A-Z])/g,((e,t)=>`-${t.toLowerCase()}`))}function l(e){return null!=e}function h(e,t){return Object.prototype.hasOwnProperty.call(e,t)}const u=["meta","ctrl","alt","shift"];const d={a:()=>"click",button:()=>"click",form:()=>"submit",details:()=>"toggle",input:e=>"submit"==e.getAttribute("type")?"click":"input",select:()=>"change",textarea:()=>"input"};function m(e){throw new Error(e)}function g(e){try{return JSON.parse(e)}catch(t){return e}}class p{constructor(e,t){this.context=e,this.action=t}get index(){return this.action.index}get eventTarget(){return this.action.eventTarget}get eventOptions(){return this.action.eventOptions}get identifier(){return this.context.identifier}handleEvent(e){const t=this.prepareActionEvent(e);this.willBeInvokedByEvent(e)&&this.applyEventModifiers(t)&&this.invokeWithEvent(t)}get eventName(){return this.action.eventName}get method(){const e=this.controller[this.methodName];if("function"==typeof e)return e;throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`)}applyEventModifiers(e){const{element:t}=this.action,{actionDescriptorFilters:s}=this.context.application,{controller:r}=this.context;let n=!0;for(const[i,o]of Object.entries(this.eventOptions))if(i in s){const a=s[i];n=n&&a({name:i,value:o,event:e,element:t,controller:r})}return n}prepareActionEvent(e){return Object.assign(e,{params:this.action.params})}invokeWithEvent(e){const{target:t,currentTarget:s}=e;try{this.method.call(this.controller,e),this.context.logDebugActivity(this.methodName,{event:e,target:t,currentTarget:s,action:this.methodName})}catch(t){const{identifier:s,controller:r,element:n,index:i}=this,o={identifier:s,controller:r,element:n,index:i,event:e};this.context.handleError(t,`invoking action "${this.action}"`,o)}}willBeInvokedByEvent(e){const t=e.target;return!(e instanceof KeyboardEvent&&this.action.shouldIgnoreKeyboardEvent(e))&&(!(e instanceof MouseEvent&&this.action.shouldIgnoreMouseEvent(e))&&(this.element===t||(t instanceof Element&&this.element.contains(t)?this.scope.containsElement(t):this.scope.containsElement(this.action.element))))}get controller(){return this.context.controller}get methodName(){return this.action.methodName}get element(){return this.scope.element}get scope(){return this.context.scope}}class f{constructor(e,t){this.mutationObserverInit={attributes:!0,childList:!0,subtree:!0},this.element=e,this.started=!1,this.delegate=t,this.elements=new Set,this.mutationObserver=new MutationObserver((e=>this.processMutations(e)))}start(){this.started||(this.started=!0,this.mutationObserver.observe(this.element,this.mutationObserverInit),this.refresh())}pause(e){this.started&&(this.mutationObserver.disconnect(),this.started=!1),e(),this.started||(this.mutationObserver.observe(this.element,this.mutationObserverInit),this.started=!0)}stop(){this.started&&(this.mutationObserver.takeRecords(),this.mutationObserver.disconnect(),this.started=!1)}refresh(){if(this.started){const e=new Set(this.matchElementsInTree());for(const t of Array.from(this.elements))e.has(t)||this.removeElement(t);for(const t of Array.from(e))this.addElement(t)}}processMutations(e){if(this.started)for(const t of e)this.processMutation(t)}processMutation(e){"attributes"==e.type?this.processAttributeChange(e.target,e.attributeName):"childList"==e.type&&(this.processRemovedNodes(e.removedNodes),this.processAddedNodes(e.addedNodes))}processAttributeChange(e,t){this.elements.has(e)?this.delegate.elementAttributeChanged&&this.matchElement(e)?this.delegate.elementAttributeChanged(e,t):this.removeElement(e):this.matchElement(e)&&this.addElement(e)}processRemovedNodes(e){for(const t of Array.from(e)){const e=this.elementFromNode(t);e&&this.processTree(e,this.removeElement)}}processAddedNodes(e){for(const t of Array.from(e)){const e=this.elementFromNode(t);e&&this.elementIsActive(e)&&this.processTree(e,this.addElement)}}matchElement(e){return this.delegate.matchElement(e)}matchElementsInTree(e=this.element){return this.delegate.matchElementsInTree(e)}processTree(e,t){for(const s of this.matchElementsInTree(e))t.call(this,s)}elementFromNode(e){if(e.nodeType==Node.ELEMENT_NODE)return e}elementIsActive(e){return e.isConnected==this.element.isConnected&&this.element.contains(e)}addElement(e){this.elements.has(e)||this.elementIsActive(e)&&(this.elements.add(e),this.delegate.elementMatched&&this.delegate.elementMatched(e))}removeElement(e){this.elements.has(e)&&(this.elements.delete(e),this.delegate.elementUnmatched&&this.delegate.elementUnmatched(e))}}class b{constructor(e,t,s){this.attributeName=t,this.delegate=s,this.elementObserver=new f(e,this)}get element(){return this.elementObserver.element}get selector(){return`[${this.attributeName}]`}start(){this.elementObserver.start()}pause(e){this.elementObserver.pause(e)}stop(){this.elementObserver.stop()}refresh(){this.elementObserver.refresh()}get started(){return this.elementObserver.started}matchElement(e){return e.hasAttribute(this.attributeName)}matchElementsInTree(e){const t=this.matchElement(e)?[e]:[],s=Array.from(e.querySelectorAll(this.selector));return t.concat(s)}elementMatched(e){this.delegate.elementMatchedAttribute&&this.delegate.elementMatchedAttribute(e,this.attributeName)}elementUnmatched(e){this.delegate.elementUnmatchedAttribute&&this.delegate.elementUnmatchedAttribute(e,this.attributeName)}elementAttributeChanged(e,t){this.delegate.elementAttributeValueChanged&&this.attributeName==t&&this.delegate.elementAttributeValueChanged(e,t)}}function v(e,t,s){O(e,t).add(s)}function y(e,t,s){O(e,t).delete(s),A(e,t)}function O(e,t){let s=e.get(t);return s||(s=new Set,e.set(t,s)),s}function A(e,t){const s=e.get(t);null!=s&&0==s.size&&e.delete(t)}class E{constructor(){this.valuesByKey=new Map}get keys(){return Array.from(this.valuesByKey.keys())}get values(){return Array.from(this.valuesByKey.values()).reduce(((e,t)=>e.concat(Array.from(t))),[])}get size(){return Array.from(this.valuesByKey.values()).reduce(((e,t)=>e+t.size),0)}add(e,t){v(this.valuesByKey,e,t)}delete(e,t){y(this.valuesByKey,e,t)}has(e,t){const s=this.valuesByKey.get(e);return null!=s&&s.has(t)}hasKey(e){return this.valuesByKey.has(e)}hasValue(e){return Array.from(this.valuesByKey.values()).some((t=>t.has(e)))}getValuesForKey(e){const t=this.valuesByKey.get(e);return t?Array.from(t):[]}getKeysForValue(e){return Array.from(this.valuesByKey).filter((([t,s])=>s.has(e))).map((([e,t])=>e))}}class w extends E{constructor(){super(),this.keysByValue=new Map}get values(){return Array.from(this.keysByValue.keys())}add(e,t){super.add(e,t),v(this.keysByValue,t,e)}delete(e,t){super.delete(e,t),y(this.keysByValue,t,e)}hasValue(e){return this.keysByValue.has(e)}getKeysForValue(e){const t=this.keysByValue.get(e);return t?Array.from(t):[]}}class M{constructor(e,t,s,r){this._selector=t,this.details=r,this.elementObserver=new f(e,this),this.delegate=s,this.matchesByElement=new E}get started(){return this.elementObserver.started}get selector(){return this._selector}set selector(e){this._selector=e,this.refresh()}start(){this.elementObserver.start()}pause(e){this.elementObserver.pause(e)}stop(){this.elementObserver.stop()}refresh(){this.elementObserver.refresh()}get element(){return this.elementObserver.element}matchElement(e){const{selector:t}=this;if(t){const s=e.matches(t);return this.delegate.selectorMatchElement?s&&this.delegate.selectorMatchElement(e,this.details):s}return!1}matchElementsInTree(e){const{selector:t}=this;if(t){const s=this.matchElement(e)?[e]:[],r=Array.from(e.querySelectorAll(t)).filter((e=>this.matchElement(e)));return s.concat(r)}return[]}elementMatched(e){const{selector:t}=this;t&&this.selectorMatched(e,t)}elementUnmatched(e){const t=this.matchesByElement.getKeysForValue(e);for(const s of t)this.selectorUnmatched(e,s)}elementAttributeChanged(e,t){const{selector:s}=this;if(s){const t=this.matchElement(e),r=this.matchesByElement.has(s,e);t&&!r?this.selectorMatched(e,s):!t&&r&&this.selectorUnmatched(e,s)}}selectorMatched(e,t){this.delegate.selectorMatched(e,t,this.details),this.matchesByElement.add(t,e)}selectorUnmatched(e,t){this.delegate.selectorUnmatched(e,t,this.details),this.matchesByElement.delete(t,e)}}class k{constructor(e,t){this.element=e,this.delegate=t,this.started=!1,this.stringMap=new Map,this.mutationObserver=new MutationObserver((e=>this.processMutations(e)))}start(){this.started||(this.started=!0,this.mutationObserver.observe(this.element,{attributes:!0,attributeOldValue:!0}),this.refresh())}stop(){this.started&&(this.mutationObserver.takeRecords(),this.mutationObserver.disconnect(),this.started=!1)}refresh(){if(this.started)for(const e of this.knownAttributeNames)this.refreshAttribute(e,null)}processMutations(e){if(this.started)for(const t of e)this.processMutation(t)}processMutation(e){const t=e.attributeName;t&&this.refreshAttribute(t,e.oldValue)}refreshAttribute(e,t){const s=this.delegate.getStringMapKeyForAttribute(e);if(null!=s){this.stringMap.has(e)||this.stringMapKeyAdded(s,e);const r=this.element.getAttribute(e);if(this.stringMap.get(e)!=r&&this.stringMapValueChanged(r,s,t),null==r){const t=this.stringMap.get(e);this.stringMap.delete(e),t&&this.stringMapKeyRemoved(s,e,t)}else this.stringMap.set(e,r)}}stringMapKeyAdded(e,t){this.delegate.stringMapKeyAdded&&this.delegate.stringMapKeyAdded(e,t)}stringMapValueChanged(e,t,s){this.delegate.stringMapValueChanged&&this.delegate.stringMapValueChanged(e,t,s)}stringMapKeyRemoved(e,t,s){this.delegate.stringMapKeyRemoved&&this.delegate.stringMapKeyRemoved(e,t,s)}get knownAttributeNames(){return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)))}get currentAttributeNames(){return Array.from(this.element.attributes).map((e=>e.name))}get recordedAttributeNames(){return Array.from(this.stringMap.keys())}}class N{constructor(e,t,s){this.attributeObserver=new b(e,t,this),this.delegate=s,this.tokensByElement=new E}get started(){return this.attributeObserver.started}start(){this.attributeObserver.start()}pause(e){this.attributeObserver.pause(e)}stop(){this.attributeObserver.stop()}refresh(){this.attributeObserver.refresh()}get element(){return this.attributeObserver.element}get attributeName(){return this.attributeObserver.attributeName}elementMatchedAttribute(e){this.tokensMatched(this.readTokensForElement(e))}elementAttributeValueChanged(e){const[t,s]=this.refreshTokensForElement(e);this.tokensUnmatched(t),this.tokensMatched(s)}elementUnmatchedAttribute(e){this.tokensUnmatched(this.tokensByElement.getValuesForKey(e))}tokensMatched(e){e.forEach((e=>this.tokenMatched(e)))}tokensUnmatched(e){e.forEach((e=>this.tokenUnmatched(e)))}tokenMatched(e){this.delegate.tokenMatched(e),this.tokensByElement.add(e.element,e)}tokenUnmatched(e){this.delegate.tokenUnmatched(e),this.tokensByElement.delete(e.element,e)}refreshTokensForElement(e){const t=this.tokensByElement.getValuesForKey(e),s=this.readTokensForElement(e),r=function(e,t){const s=Math.max(e.length,t.length);return Array.from({length:s},((s,r)=>[e[r],t[r]]))}(t,s).findIndex((([e,t])=>{return r=t,!((s=e)&&r&&s.index==r.index&&s.content==r.content);var s,r}));return-1==r?[[],[]]:[t.slice(r),s.slice(r)]}readTokensForElement(e){const t=this.attributeName;return function(e,t,s){return e.trim().split(/\s+/).filter((e=>e.length)).map(((e,r)=>({element:t,attributeName:s,content:e,index:r})))}(e.getAttribute(t)||"",e,t)}}class F{constructor(e,t,s){this.tokenListObserver=new N(e,t,this),this.delegate=s,this.parseResultsByToken=new WeakMap,this.valuesByTokenByElement=new WeakMap}get started(){return this.tokenListObserver.started}start(){this.tokenListObserver.start()}stop(){this.tokenListObserver.stop()}refresh(){this.tokenListObserver.refresh()}get element(){return this.tokenListObserver.element}get attributeName(){return this.tokenListObserver.attributeName}tokenMatched(e){const{element:t}=e,{value:s}=this.fetchParseResultForToken(e);s&&(this.fetchValuesByTokenForElement(t).set(e,s),this.delegate.elementMatchedValue(t,s))}tokenUnmatched(e){const{element:t}=e,{value:s}=this.fetchParseResultForToken(e);s&&(this.fetchValuesByTokenForElement(t).delete(e),this.delegate.elementUnmatchedValue(t,s))}fetchParseResultForToken(e){let t=this.parseResultsByToken.get(e);return t||(t=this.parseToken(e),this.parseResultsByToken.set(e,t)),t}fetchValuesByTokenForElement(e){let t=this.valuesByTokenByElement.get(e);return t||(t=new Map,this.valuesByTokenByElement.set(e,t)),t}parseToken(e){try{return{value:this.delegate.parseValueForToken(e)}}catch(e){return{error:e}}}}class B{constructor(e,t){this.context=e,this.delegate=t,this.bindingsByAction=new Map}start(){this.valueListObserver||(this.valueListObserver=new F(this.element,this.actionAttribute,this),this.valueListObserver.start())}stop(){this.valueListObserver&&(this.valueListObserver.stop(),delete this.valueListObserver,this.disconnectAllActions())}get element(){return this.context.element}get identifier(){return this.context.identifier}get actionAttribute(){return this.schema.actionAttribute}get schema(){return this.context.schema}get bindings(){return Array.from(this.bindingsByAction.values())}connectAction(e){const t=new p(this.context,e);this.bindingsByAction.set(e,t),this.delegate.bindingConnected(t)}disconnectAction(e){const t=this.bindingsByAction.get(e);t&&(this.bindingsByAction.delete(e),this.delegate.bindingDisconnected(t))}disconnectAllActions(){this.bindings.forEach((e=>this.delegate.bindingDisconnected(e,!0))),this.bindingsByAction.clear()}parseValueForToken(e){const t=class{constructor(e,t,s,r){this.element=e,this.index=t,this.eventTarget=s.eventTarget||e,this.eventName=s.eventName||function(e){const t=e.tagName.toLowerCase();if(t in d)return d[t](e)}(e)||m("missing event name"),this.eventOptions=s.eventOptions||{},this.identifier=s.identifier||m("missing identifier"),this.methodName=s.methodName||m("missing method name"),this.keyFilter=s.keyFilter||"",this.schema=r}static forToken(e,t){return new this(e.element,e.index,function(e){const t=e.trim().match(r)||[];let s=t[2],i=t[3];return i&&!["keydown","keyup","keypress"].includes(s)&&(s+=`.${i}`,i=""),{eventTarget:n(t[4]),eventName:s,eventOptions:t[7]?(o=t[7],o.split(":").reduce(((e,t)=>Object.assign(e,{[t.replace(/^!/,"")]:!/^!/.test(t)})),{})):{},identifier:t[5],methodName:t[6],keyFilter:t[1]||i};var o}(e.content),t)}toString(){const e=this.keyFilter?`.${this.keyFilter}`:"",t=this.eventTargetName?`@${this.eventTargetName}`:"";return`${this.eventName}${e}${t}->${this.identifier}#${this.methodName}`}shouldIgnoreKeyboardEvent(e){if(!this.keyFilter)return!1;const t=this.keyFilter.split("+");if(this.keyFilterDissatisfied(e,t))return!0;const s=t.filter((e=>!u.includes(e)))[0];return!!s&&(h(this.keyMappings,s)||m(`contains unknown key filter: ${this.keyFilter}`),this.keyMappings[s].toLowerCase()!==e.key.toLowerCase())}shouldIgnoreMouseEvent(e){if(!this.keyFilter)return!1;const t=[this.keyFilter];return!!this.keyFilterDissatisfied(e,t)}get params(){const e={},t=new RegExp(`^data-${this.identifier}-(.+)-param$`,"i");for(const{name:s,value:r}of Array.from(this.element.attributes)){const n=s.match(t),o=n&&n[1];o&&(e[i(o)]=g(r))}return e}get eventTargetName(){return(e=this.eventTarget)==window?"window":e==document?"document":void 0;var e}get keyMappings(){return this.schema.keyMappings}keyFilterDissatisfied(e,t){const[s,r,n,i]=u.map((e=>t.includes(e)));return e.metaKey!==s||e.ctrlKey!==r||e.altKey!==n||e.shiftKey!==i}}.forToken(e,this.schema);if(t.identifier==this.identifier)return t}elementMatchedValue(e,t){this.connectAction(t)}elementUnmatchedValue(e,t){this.disconnectAction(t)}}class C{constructor(e,t){this.context=e,this.receiver=t,this.stringMapObserver=new k(this.element,this),this.valueDescriptorMap=this.controller.valueDescriptorMap}start(){this.stringMapObserver.start(),this.invokeChangedCallbacksForDefaultValues()}stop(){this.stringMapObserver.stop()}get element(){return this.context.element}get controller(){return this.context.controller}getStringMapKeyForAttribute(e){if(e in this.valueDescriptorMap)return this.valueDescriptorMap[e].name}stringMapKeyAdded(e,t){const s=this.valueDescriptorMap[t];this.hasValue(e)||this.invokeChangedCallback(e,s.writer(this.receiver[e]),s.writer(s.defaultValue))}stringMapValueChanged(e,t,s){const r=this.valueDescriptorNameMap[t];null!==e&&(null===s&&(s=r.writer(r.defaultValue)),this.invokeChangedCallback(t,e,s))}stringMapKeyRemoved(e,t,s){const r=this.valueDescriptorNameMap[e];this.hasValue(e)?this.invokeChangedCallback(e,r.writer(this.receiver[e]),s):this.invokeChangedCallback(e,r.writer(r.defaultValue),s)}invokeChangedCallbacksForDefaultValues(){for(const{key:e,name:t,defaultValue:s,writer:r}of this.valueDescriptors)null==s||this.controller.data.has(e)||this.invokeChangedCallback(t,r(s),void 0)}invokeChangedCallback(e,t,s){const r=`${e}Changed`,n=this.receiver[r];if("function"==typeof n){const r=this.valueDescriptorNameMap[e];try{const e=r.reader(t);let i=s;s&&(i=r.reader(s)),n.call(this.receiver,e,i)}catch(e){throw e instanceof TypeError&&(e.message=`Stimulus Value "${this.context.identifier}.${r.name}" - ${e.message}`),e}}}get valueDescriptors(){const{valueDescriptorMap:e}=this;return Object.keys(e).map((t=>e[t]))}get valueDescriptorNameMap(){const e={};return Object.keys(this.valueDescriptorMap).forEach((t=>{const s=this.valueDescriptorMap[t];e[s.name]=s})),e}hasValue(e){const t=`has${a(this.valueDescriptorNameMap[e].name)}`;return this.receiver[t]}}class ${constructor(e,t){this.context=e,this.delegate=t,this.targetsByName=new E}start(){this.tokenListObserver||(this.tokenListObserver=new N(this.element,this.attributeName,this),this.tokenListObserver.start())}stop(){this.tokenListObserver&&(this.disconnectAllTargets(),this.tokenListObserver.stop(),delete this.tokenListObserver)}tokenMatched({element:e,content:t}){this.scope.containsElement(e)&&this.connectTarget(e,t)}tokenUnmatched({element:e,content:t}){this.disconnectTarget(e,t)}connectTarget(e,t){var s;this.targetsByName.has(t,e)||(this.targetsByName.add(t,e),null===(s=this.tokenListObserver)||void 0===s||s.pause((()=>this.delegate.targetConnected(e,t))))}disconnectTarget(e,t){var s;this.targetsByName.has(t,e)&&(this.targetsByName.delete(t,e),null===(s=this.tokenListObserver)||void 0===s||s.pause((()=>this.delegate.targetDisconnected(e,t))))}disconnectAllTargets(){for(const e of this.targetsByName.keys)for(const t of this.targetsByName.getValuesForKey(e))this.disconnectTarget(t,e)}get attributeName(){return`data-${this.context.identifier}-target`}get element(){return this.context.element}get scope(){return this.context.scope}}function T(e,t){const s=x(e);return Array.from(s.reduce(((e,s)=>(function(e,t){const s=e[t];return Array.isArray(s)?s:[]}(s,t).forEach((t=>e.add(t))),e)),new Set))}function S(e,t){return x(e).reduce(((e,s)=>(e.push(...function(e,t){const s=e[t];return s?Object.keys(s).map((e=>[e,s[e]])):[]}(s,t)),e)),[])}function x(e){const t=[];for(;e;)t.push(e),e=Object.getPrototypeOf(e);return t.reverse()}class D{constructor(e,t){this.started=!1,this.context=e,this.delegate=t,this.outletsByName=new E,this.outletElementsByName=new E,this.selectorObserverMap=new Map,this.attributeObserverMap=new Map}start(){this.started||(this.outletDefinitions.forEach((e=>{this.setupSelectorObserverForOutlet(e),this.setupAttributeObserverForOutlet(e)})),this.started=!0,this.dependentContexts.forEach((e=>e.refresh())))}refresh(){this.selectorObserverMap.forEach((e=>e.refresh())),this.attributeObserverMap.forEach((e=>e.refresh()))}stop(){this.started&&(this.started=!1,this.disconnectAllOutlets(),this.stopSelectorObservers(),this.stopAttributeObservers())}stopSelectorObservers(){this.selectorObserverMap.size>0&&(this.selectorObserverMap.forEach((e=>e.stop())),this.selectorObserverMap.clear())}stopAttributeObservers(){this.attributeObserverMap.size>0&&(this.attributeObserverMap.forEach((e=>e.stop())),this.attributeObserverMap.clear())}selectorMatched(e,t,{outletName:s}){const r=this.getOutlet(e,s);r&&this.connectOutlet(r,e,s)}selectorUnmatched(e,t,{outletName:s}){const r=this.getOutletFromMap(e,s);r&&this.disconnectOutlet(r,e,s)}selectorMatchElement(e,{outletName:t}){const s=this.selector(t),r=this.hasOutlet(e,t),n=e.matches(`[${this.schema.controllerAttribute}~=${t}]`);return!!s&&(r&&n&&e.matches(s))}elementMatchedAttribute(e,t){const s=this.getOutletNameFromOutletAttributeName(t);s&&this.updateSelectorObserverForOutlet(s)}elementAttributeValueChanged(e,t){const s=this.getOutletNameFromOutletAttributeName(t);s&&this.updateSelectorObserverForOutlet(s)}elementUnmatchedAttribute(e,t){const s=this.getOutletNameFromOutletAttributeName(t);s&&this.updateSelectorObserverForOutlet(s)}connectOutlet(e,t,s){var r;this.outletElementsByName.has(s,t)||(this.outletsByName.add(s,e),this.outletElementsByName.add(s,t),null===(r=this.selectorObserverMap.get(s))||void 0===r||r.pause((()=>this.delegate.outletConnected(e,t,s))))}disconnectOutlet(e,t,s){var r;this.outletElementsByName.has(s,t)&&(this.outletsByName.delete(s,e),this.outletElementsByName.delete(s,t),null===(r=this.selectorObserverMap.get(s))||void 0===r||r.pause((()=>this.delegate.outletDisconnected(e,t,s))))}disconnectAllOutlets(){for(const e of this.outletElementsByName.keys)for(const t of this.outletElementsByName.getValuesForKey(e))for(const s of this.outletsByName.getValuesForKey(e))this.disconnectOutlet(s,t,e)}updateSelectorObserverForOutlet(e){const t=this.selectorObserverMap.get(e);t&&(t.selector=this.selector(e))}setupSelectorObserverForOutlet(e){const t=this.selector(e),s=new M(document.body,t,this,{outletName:e});this.selectorObserverMap.set(e,s),s.start()}setupAttributeObserverForOutlet(e){const t=this.attributeNameForOutletName(e),s=new b(this.scope.element,t,this);this.attributeObserverMap.set(e,s),s.start()}selector(e){return this.scope.outlets.getSelectorForOutletName(e)}attributeNameForOutletName(e){return this.scope.schema.outletAttributeForScope(this.identifier,e)}getOutletNameFromOutletAttributeName(e){return this.outletDefinitions.find((t=>this.attributeNameForOutletName(t)===e))}get outletDependencies(){const e=new E;return this.router.modules.forEach((t=>{T(t.definition.controllerConstructor,"outlets").forEach((s=>e.add(s,t.identifier)))})),e}get outletDefinitions(){return this.outletDependencies.getKeysForValue(this.identifier)}get dependentControllerIdentifiers(){return this.outletDependencies.getValuesForKey(this.identifier)}get dependentContexts(){const e=this.dependentControllerIdentifiers;return this.router.contexts.filter((t=>e.includes(t.identifier)))}hasOutlet(e,t){return!!this.getOutlet(e,t)||!!this.getOutletFromMap(e,t)}getOutlet(e,t){return this.application.getControllerForElementAndIdentifier(e,t)}getOutletFromMap(e,t){return this.outletsByName.getValuesForKey(t).find((t=>t.element===e))}get scope(){return this.context.scope}get schema(){return this.context.schema}get identifier(){return this.context.identifier}get application(){return this.context.application}get router(){return this.application.router}}class L{constructor(e,t){this.logDebugActivity=(e,t={})=>{const{identifier:s,controller:r,element:n}=this;t=Object.assign({identifier:s,controller:r,element:n},t),this.application.logDebugActivity(this.identifier,e,t)},this.module=e,this.scope=t,this.controller=new e.controllerConstructor(this),this.bindingObserver=new B(this,this.dispatcher),this.valueObserver=new C(this,this.controller),this.targetObserver=new $(this,this),this.outletObserver=new D(this,this);try{this.controller.initialize(),this.logDebugActivity("initialize")}catch(e){this.handleError(e,"initializing controller")}}connect(){this.bindingObserver.start(),this.valueObserver.start(),this.targetObserver.start(),this.outletObserver.start();try{this.controller.connect(),this.logDebugActivity("connect")}catch(e){this.handleError(e,"connecting controller")}}refresh(){this.outletObserver.refresh()}disconnect(){try{this.controller.disconnect(),this.logDebugActivity("disconnect")}catch(e){this.handleError(e,"disconnecting controller")}this.outletObserver.stop(),this.targetObserver.stop(),this.valueObserver.stop(),this.bindingObserver.stop()}get application(){return this.module.application}get identifier(){return this.module.identifier}get schema(){return this.application.schema}get dispatcher(){return this.application.dispatcher}get element(){return this.scope.element}get parentElement(){return this.element.parentElement}handleError(e,t,s={}){const{identifier:r,controller:n,element:i}=this;s=Object.assign({identifier:r,controller:n,element:i},s),this.application.handleError(e,`Error ${t}`,s)}targetConnected(e,t){this.invokeControllerMethod(`${t}TargetConnected`,e)}targetDisconnected(e,t){this.invokeControllerMethod(`${t}TargetDisconnected`,e)}outletConnected(e,t,s){this.invokeControllerMethod(`${o(s)}OutletConnected`,e,t)}outletDisconnected(e,t,s){this.invokeControllerMethod(`${o(s)}OutletDisconnected`,e,t)}invokeControllerMethod(e,...t){const s=this.controller;"function"==typeof s[e]&&s[e](...t)}}function V(e){return function(e,t){const s=I(e),r=function(e,t){return K(t).reduce(((s,r)=>{const n=function(e,t,s){const r=Object.getOwnPropertyDescriptor(e,s);if(!r||!("value"in r)){const e=Object.getOwnPropertyDescriptor(t,s).value;return r&&(e.get=r.get||e.get,e.set=r.set||e.set),e}}(e,t,r);return n&&Object.assign(s,{[r]:n}),s}),{})}(e.prototype,t);return Object.defineProperties(s.prototype,r),s}(e,function(e){return T(e,"blessings").reduce(((t,s)=>{const r=s(e);for(const e in r){const s=t[e]||{};t[e]=Object.assign(s,r[e])}return t}),{})}(e))}const K="function"==typeof Object.getOwnPropertySymbols?e=>[...Object.getOwnPropertyNames(e),...Object.getOwnPropertySymbols(e)]:Object.getOwnPropertyNames,I=(()=>{function e(e){function t(){return Reflect.construct(e,arguments,new.target)}return t.prototype=Object.create(e.prototype,{constructor:{value:t}}),Reflect.setPrototypeOf(t,e),t}try{return function(){const t=e((function(){this.a.call(this)}));t.prototype.a=function(){},new t}(),e}catch(e){return e=>class extends e{}}})();class j{constructor(e,t){this.application=e,this.definition=function(e){return{identifier:e.identifier,controllerConstructor:V(e.controllerConstructor)}}(t),this.contextsByScope=new WeakMap,this.connectedContexts=new Set}get identifier(){return this.definition.identifier}get controllerConstructor(){return this.definition.controllerConstructor}get contexts(){return Array.from(this.connectedContexts)}connectContextForScope(e){const t=this.fetchContextForScope(e);this.connectedContexts.add(t),t.connect()}disconnectContextForScope(e){const t=this.contextsByScope.get(e);t&&(this.connectedContexts.delete(t),t.disconnect())}fetchContextForScope(e){let t=this.contextsByScope.get(e);return t||(t=new L(this,e),this.contextsByScope.set(e,t)),t}}class U{constructor(e){this.scope=e}has(e){return this.data.has(this.getDataKey(e))}get(e){return this.getAll(e)[0]}getAll(e){const t=this.data.get(this.getDataKey(e))||"";return t.match(/[^\s]+/g)||[]}getAttributeName(e){return this.data.getAttributeNameForKey(this.getDataKey(e))}getDataKey(e){return`${e}-class`}get data(){return this.scope.data}}class P{constructor(e){this.scope=e}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get(e){const t=this.getAttributeNameForKey(e);return this.element.getAttribute(t)}set(e,t){const s=this.getAttributeNameForKey(e);return this.element.setAttribute(s,t),this.get(e)}has(e){const t=this.getAttributeNameForKey(e);return this.element.hasAttribute(t)}delete(e){if(this.has(e)){const t=this.getAttributeNameForKey(e);return this.element.removeAttribute(t),!0}return!1}getAttributeNameForKey(e){return`data-${this.identifier}-${c(e)}`}}class R{constructor(e){this.warnedKeysByObject=new WeakMap,this.logger=e}warn(e,t,s){let r=this.warnedKeysByObject.get(e);r||(r=new Set,this.warnedKeysByObject.set(e,r)),r.has(t)||(r.add(t),this.logger.warn(s,e))}}function z(e,t){return`[${e}~="${t}"]`}class _{constructor(e){this.scope=e}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get schema(){return this.scope.schema}has(e){return null!=this.find(e)}find(...e){return e.reduce(((e,t)=>e||this.findTarget(t)||this.findLegacyTarget(t)),void 0)}findAll(...e){return e.reduce(((e,t)=>[...e,...this.findAllTargets(t),...this.findAllLegacyTargets(t)]),[])}findTarget(e){const t=this.getSelectorForTargetName(e);return this.scope.findElement(t)}findAllTargets(e){const t=this.getSelectorForTargetName(e);return this.scope.findAllElements(t)}getSelectorForTargetName(e){return z(this.schema.targetAttributeForScope(this.identifier),e)}findLegacyTarget(e){const t=this.getLegacySelectorForTargetName(e);return this.deprecate(this.scope.findElement(t),e)}findAllLegacyTargets(e){const t=this.getLegacySelectorForTargetName(e);return this.scope.findAllElements(t).map((t=>this.deprecate(t,e)))}getLegacySelectorForTargetName(e){const t=`${this.identifier}.${e}`;return z(this.schema.targetAttribute,t)}deprecate(e,t){if(e){const{identifier:s}=this,r=this.schema.targetAttribute,n=this.schema.targetAttributeForScope(s);this.guide.warn(e,`target:${t}`,`Please replace ${r}="${s}.${t}" with ${n}="${t}". The ${r} attribute is deprecated and will be removed in a future version of Stimulus.`)}return e}get guide(){return this.scope.guide}}class q{constructor(e,t){this.scope=e,this.controllerElement=t}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get schema(){return this.scope.schema}has(e){return null!=this.find(e)}find(...e){return e.reduce(((e,t)=>e||this.findOutlet(t)),void 0)}findAll(...e){return e.reduce(((e,t)=>[...e,...this.findAllOutlets(t)]),[])}getSelectorForOutletName(e){const t=this.schema.outletAttributeForScope(this.identifier,e);return this.controllerElement.getAttribute(t)}findOutlet(e){const t=this.getSelectorForOutletName(e);if(t)return this.findElement(t,e)}findAllOutlets(e){const t=this.getSelectorForOutletName(e);return t?this.findAllElements(t,e):[]}findElement(e,t){return this.scope.queryElements(e).filter((s=>this.matchesElement(s,e,t)))[0]}findAllElements(e,t){return this.scope.queryElements(e).filter((s=>this.matchesElement(s,e,t)))}matchesElement(e,t,s){const r=e.getAttribute(this.scope.schema.controllerAttribute)||"";return e.matches(t)&&r.split(" ").includes(s)}}class W{constructor(e,t,s,r){this.targets=new _(this),this.classes=new U(this),this.data=new P(this),this.containsElement=e=>e.closest(this.controllerSelector)===this.element,this.schema=e,this.element=t,this.identifier=s,this.guide=new R(r),this.outlets=new q(this.documentScope,t)}findElement(e){return this.element.matches(e)?this.element:this.queryElements(e).find(this.containsElement)}findAllElements(e){return[...this.element.matches(e)?[this.element]:[],...this.queryElements(e).filter(this.containsElement)]}queryElements(e){return Array.from(this.element.querySelectorAll(e))}get controllerSelector(){return z(this.schema.controllerAttribute,this.identifier)}get isDocumentScope(){return this.element===document.documentElement}get documentScope(){return this.isDocumentScope?this:new W(this.schema,document.documentElement,this.identifier,this.guide.logger)}}class J{constructor(e,t,s){this.element=e,this.schema=t,this.delegate=s,this.valueListObserver=new F(this.element,this.controllerAttribute,this),this.scopesByIdentifierByElement=new WeakMap,this.scopeReferenceCounts=new WeakMap}start(){this.valueListObserver.start()}stop(){this.valueListObserver.stop()}get controllerAttribute(){return this.schema.controllerAttribute}parseValueForToken(e){const{element:t,content:s}=e;return this.parseValueForElementAndIdentifier(t,s)}parseValueForElementAndIdentifier(e,t){const s=this.fetchScopesByIdentifierForElement(e);let r=s.get(t);return r||(r=this.delegate.createScopeForElementAndIdentifier(e,t),s.set(t,r)),r}elementMatchedValue(e,t){const s=(this.scopeReferenceCounts.get(t)||0)+1;this.scopeReferenceCounts.set(t,s),1==s&&this.delegate.scopeConnected(t)}elementUnmatchedValue(e,t){const s=this.scopeReferenceCounts.get(t);s&&(this.scopeReferenceCounts.set(t,s-1),1==s&&this.delegate.scopeDisconnected(t))}fetchScopesByIdentifierForElement(e){let t=this.scopesByIdentifierByElement.get(e);return t||(t=new Map,this.scopesByIdentifierByElement.set(e,t)),t}}class H{constructor(e){this.application=e,this.scopeObserver=new J(this.element,this.schema,this),this.scopesByIdentifier=new E,this.modulesByIdentifier=new Map}get element(){return this.application.element}get schema(){return this.application.schema}get logger(){return this.application.logger}get controllerAttribute(){return this.schema.controllerAttribute}get modules(){return Array.from(this.modulesByIdentifier.values())}get contexts(){return this.modules.reduce(((e,t)=>e.concat(t.contexts)),[])}start(){this.scopeObserver.start()}stop(){this.scopeObserver.stop()}loadDefinition(e){this.unloadIdentifier(e.identifier);const t=new j(this.application,e);this.connectModule(t);const s=e.controllerConstructor.afterLoad;s&&s.call(e.controllerConstructor,e.identifier,this.application)}unloadIdentifier(e){const t=this.modulesByIdentifier.get(e);t&&this.disconnectModule(t)}getContextForElementAndIdentifier(e,t){const s=this.modulesByIdentifier.get(t);if(s)return s.contexts.find((t=>t.element==e))}proposeToConnectScopeForElementAndIdentifier(e,t){const s=this.scopeObserver.parseValueForElementAndIdentifier(e,t);s?this.scopeObserver.elementMatchedValue(s.element,s):console.error(`Couldn't find or create scope for identifier: "${t}" and element:`,e)}handleError(e,t,s){this.application.handleError(e,t,s)}createScopeForElementAndIdentifier(e,t){return new W(this.schema,e,t,this.logger)}scopeConnected(e){this.scopesByIdentifier.add(e.identifier,e);const t=this.modulesByIdentifier.get(e.identifier);t&&t.connectContextForScope(e)}scopeDisconnected(e){this.scopesByIdentifier.delete(e.identifier,e);const t=this.modulesByIdentifier.get(e.identifier);t&&t.disconnectContextForScope(e)}connectModule(e){this.modulesByIdentifier.set(e.identifier,e);this.scopesByIdentifier.getValuesForKey(e.identifier).forEach((t=>e.connectContextForScope(t)))}disconnectModule(e){this.modulesByIdentifier.delete(e.identifier);this.scopesByIdentifier.getValuesForKey(e.identifier).forEach((t=>e.disconnectContextForScope(t)))}}const Z={controllerAttribute:"data-controller",actionAttribute:"data-action",targetAttribute:"data-target",targetAttributeForScope:e=>`data-${e}-target`,outletAttributeForScope:(e,t)=>`data-${e}-${t}-outlet`,keyMappings:Object.assign(Object.assign({enter:"Enter",tab:"Tab",esc:"Escape",space:" ",up:"ArrowUp",down:"ArrowDown",left:"ArrowLeft",right:"ArrowRight",home:"Home",end:"End",page_up:"PageUp",page_down:"PageDown"},G("abcdefghijklmnopqrstuvwxyz".split("").map((e=>[e,e])))),G("0123456789".split("").map((e=>[e,e]))))};function G(e){return e.reduce(((e,[t,s])=>Object.assign(Object.assign({},e),{[t]:s})),{})}class Q{constructor(e=document.documentElement,r=Z){this.logger=console,this.debug=!1,this.logDebugActivity=(e,t,s={})=>{this.debug&&this.logFormattedMessage(e,t,s)},this.element=e,this.schema=r,this.dispatcher=new t(this),this.router=new H(this),this.actionDescriptorFilters=Object.assign({},s)}static start(e,t){const s=new this(e,t);return s.start(),s}async start(){await new Promise((e=>{"loading"==document.readyState?document.addEventListener("DOMContentLoaded",(()=>e())):e()})),this.logDebugActivity("application","starting"),this.dispatcher.start(),this.router.start(),this.logDebugActivity("application","start")}stop(){this.logDebugActivity("application","stopping"),this.dispatcher.stop(),this.router.stop(),this.logDebugActivity("application","stop")}register(e,t){this.load({identifier:e,controllerConstructor:t})}registerActionOption(e,t){this.actionDescriptorFilters[e]=t}load(e,...t){(Array.isArray(e)?e:[e,...t]).forEach((e=>{e.controllerConstructor.shouldLoad&&this.router.loadDefinition(e)}))}unload(e,...t){(Array.isArray(e)?e:[e,...t]).forEach((e=>this.router.unloadIdentifier(e)))}get controllers(){return this.router.contexts.map((e=>e.controller))}getControllerForElementAndIdentifier(e,t){const s=this.router.getContextForElementAndIdentifier(e,t);return s?s.controller:null}handleError(e,t,s){var r;this.logger.error("%s\n\n%o\n\n%o",t,e,s),null===(r=window.onerror)||void 0===r||r.call(window,t,"",0,0,e)}logFormattedMessage(e,t,s={}){s=Object.assign({application:this},s),this.logger.groupCollapsed(`${e} #${t}`),this.logger.log("details:",Object.assign({},s)),this.logger.groupEnd()}}function X(e,t,s){return e.application.getControllerForElementAndIdentifier(t,s)}function Y(e,t,s){let r=X(e,t,s);return r||(e.application.router.proposeToConnectScopeForElementAndIdentifier(t,s),r=X(e,t,s),r||void 0)}function ee([e,t],s){return function(e){const{token:t,typeDefinition:s}=e,r=`${c(t)}-value`,n=function(e){const{controller:t,token:s,typeDefinition:r}=e,n=function(e){const{controller:t,token:s,typeObject:r}=e,n=l(r.type),i=l(r.default),o=n&&i,a=n&&!i,c=!n&&i,h=te(r.type),u=se(e.typeObject.default);if(a)return h;if(c)return u;if(h!==u){throw new Error(`The specified default value for the Stimulus Value "${t?`${t}.${s}`:s}" must match the defined type "${h}". The provided default value of "${r.default}" is of type "${u}".`)}if(o)return h}({controller:t,token:s,typeObject:r}),i=se(r),o=te(r),a=n||i||o;if(a)return a;throw new Error(`Unknown value type "${t?`${t}.${r}`:s}" for "${s}" value`)}(e);return{type:n,key:r,name:i(r),get defaultValue(){return function(e){const t=te(e);if(t)return re[t];const s=h(e,"default"),r=h(e,"type"),n=e;if(s)return n.default;if(r){const{type:e}=n,t=te(e);if(t)return re[t]}return e}(s)},get hasCustomDefaultValue(){return void 0!==se(s)},reader:ne[n],writer:ie[n]||ie.default}}({controller:s,token:e,typeDefinition:t})}function te(e){switch(e){case Array:return"array";case Boolean:return"boolean";case Number:return"number";case Object:return"object";case String:return"string"}}function se(e){switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":return"string"}return Array.isArray(e)?"array":"[object Object]"===Object.prototype.toString.call(e)?"object":void 0}const re={get array(){return[]},boolean:!1,number:0,get object(){return{}},string:""},ne={array(e){const t=JSON.parse(e);if(!Array.isArray(t))throw new TypeError(`expected value of type "array" but instead got value "${e}" of type "${se(t)}"`);return t},boolean:e=>!("0"==e||"false"==String(e).toLowerCase()),number:e=>Number(e.replace(/_/g,"")),object(e){const t=JSON.parse(e);if(null===t||"object"!=typeof t||Array.isArray(t))throw new TypeError(`expected value of type "object" but instead got value "${e}" of type "${se(t)}"`);return t},string:e=>e},ie={default:function(e){return`${e}`},array:oe,object:oe};function oe(e){return JSON.stringify(e)}class ae{constructor(e){this.context=e}static get shouldLoad(){return!0}static afterLoad(e,t){}get application(){return this.context.application}get scope(){return this.context.scope}get element(){return this.scope.element}get identifier(){return this.scope.identifier}get targets(){return this.scope.targets}get outlets(){return this.scope.outlets}get classes(){return this.scope.classes}get data(){return this.scope.data}initialize(){}connect(){}disconnect(){}dispatch(e,{target:t=this.element,detail:s={},prefix:r=this.identifier,bubbles:n=!0,cancelable:i=!0}={}){const o=new CustomEvent(r?`${r}:${e}`:e,{detail:s,bubbles:n,cancelable:i});return t.dispatchEvent(o),o}}ae.blessings=[function(e){return T(e,"classes").reduce(((e,t)=>{return Object.assign(e,{[`${s=t}Class`]:{get(){const{classes:e}=this;if(e.has(s))return e.get(s);{const t=e.getAttributeName(s);throw new Error(`Missing attribute "${t}"`)}}},[`${s}Classes`]:{get(){return this.classes.getAll(s)}},[`has${a(s)}Class`]:{get(){return this.classes.has(s)}}});var s}),{})},function(e){return T(e,"targets").reduce(((e,t)=>{return Object.assign(e,{[`${s=t}Target`]:{get(){const e=this.targets.find(s);if(e)return e;throw new Error(`Missing target element "${s}" for "${this.identifier}" controller`)}},[`${s}Targets`]:{get(){return this.targets.findAll(s)}},[`has${a(s)}Target`]:{get(){return this.targets.has(s)}}});var s}),{})},function(e){const t=S(e,"values"),s={valueDescriptorMap:{get(){return t.reduce(((e,t)=>{const s=ee(t,this.identifier),r=this.data.getAttributeNameForKey(s.key);return Object.assign(e,{[r]:s})}),{})}}};return t.reduce(((e,t)=>Object.assign(e,function(e,t){const s=ee(e,t),{key:r,name:n,reader:i,writer:o}=s;return{[n]:{get(){const e=this.data.get(r);return null!==e?i(e):s.defaultValue},set(e){void 0===e?this.data.delete(r):this.data.set(r,o(e))}},[`has${a(n)}`]:{get(){return this.data.has(r)||s.hasCustomDefaultValue}}}}(t))),s)},function(e){return T(e,"outlets").reduce(((e,t)=>Object.assign(e,function(e){const t=o(e);return{[`${t}Outlet`]:{get(){const t=this.outlets.find(e),s=this.outlets.getSelectorForOutletName(e);if(t){const s=Y(this,t,e);if(s)return s;throw new Error(`The provided outlet element is missing an outlet controller "${e}" instance for host controller "${this.identifier}"`)}throw new Error(`Missing outlet element "${e}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${s}".`)}},[`${t}Outlets`]:{get(){const t=this.outlets.findAll(e);return t.length>0?t.map((t=>{const s=Y(this,t,e);if(s)return s;console.warn(`The provided outlet element is missing an outlet controller "${e}" instance for host controller "${this.identifier}"`,t)})).filter((e=>e)):[]}},[`${t}OutletElement`]:{get(){const t=this.outlets.find(e),s=this.outlets.getSelectorForOutletName(e);if(t)return t;throw new Error(`Missing outlet element "${e}" for host controller "${this.identifier}". Stimulus couldn't find a matching outlet element using selector "${s}".`)}},[`${t}OutletElements`]:{get(){return this.outlets.findAll(e)}},[`has${a(t)}Outlet`]:{get(){return this.outlets.has(e)}}}}(t))),{})}],ae.targets=[],ae.outlets=[],ae.values={};export{Q as Application,b as AttributeObserver,L as Context,ae as Controller,f as ElementObserver,w as IndexedMultimap,E as Multimap,M as SelectorObserver,k as StringMapObserver,N as TokenListObserver,F as ValueListObserver,v as add,Z as defaultSchema,y as del,O as fetch,A as prune};
|
|
@@ -40,20 +40,27 @@
|
|
|
40
40
|
logo_path = Studio.logo_for("Navbar Logo")
|
|
41
41
|
%>
|
|
42
42
|
|
|
43
|
-
<%# nav-shell is the --nav-p scope:
|
|
43
|
+
<%# nav-shell is the --nav-p scope: the engine's nav-collapse controller (studio/nav_collapse) writes
|
|
44
44
|
the collapse progress, 0 expanded .. 1 collapsed, on THIS element once per
|
|
45
45
|
animation frame, and the band table below derives the row padding, logo size
|
|
46
46
|
and title sizes from it with calc(). Nothing on the collapse path carries a
|
|
47
47
|
time-based transition — the head's comment carries the measurements that
|
|
48
48
|
bought that. transition-shadow stays: box-shadow paints, it never reflows,
|
|
49
49
|
so it cannot move content under the reader.
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
The controller toggles the scrolled classes (the shadow) when the scroll
|
|
51
|
+
passes its hysteresis.
|
|
52
|
+
Preview keeps nav-shell (the calc()s have to resolve) but no controller — the
|
|
53
|
+
/navbar review page drives --nav-p from its own Scrolled toggle, and its
|
|
54
|
+
:class reads that toggle's `scrolled`. %>
|
|
52
55
|
<%# data-pin names this header as a layer of the pinned stack, so
|
|
53
56
|
layouts/studio/_head publishes --pin-nav-h and --pin-nav-bottom for it and
|
|
54
57
|
anything below can position off it in pure CSS. The legacy --nav-h /
|
|
55
58
|
--nav-bottom keep publishing unchanged; this is additive. %>
|
|
56
|
-
|
|
59
|
+
<%# The bare x-data is the Alpine scope the header's descendants still bind
|
|
60
|
+
through ($store.devMode on the user nav, the sidebar trigger's @click):
|
|
61
|
+
Alpine initializes directives only inside a component, and the header was
|
|
62
|
+
that component while navCollapse() lived on it. %>
|
|
63
|
+
<header data-pin="nav"<% unless is_preview %> x-data data-studio-controller="nav-collapse" data-nav-collapse-scrolled-class="shadow-lg border-b border-subtle is-scrolled"<% end %>
|
|
57
64
|
<%# top-0, a STATIC value, and never a custom property. Any bars render as
|
|
58
65
|
this header's sibling in normal flow (studio/banners/_stack), so they
|
|
59
66
|
already occupy their own height above it and there is nothing to
|
|
@@ -63,7 +70,7 @@
|
|
|
63
70
|
during a view transition composited at two different tops. Only CSS
|
|
64
71
|
can set this top now, so none of that is reachable. %>
|
|
65
72
|
class="nav-shell <%= is_preview ? 'bg-page' : "#{'vt-pinned-header ' if pin_header}sticky top-0 z-50 bg-page transition-shadow duration-300" %>"
|
|
66
|
-
:class="scrolled && 'shadow-lg border-b border-subtle is-scrolled'"
|
|
73
|
+
<%= %(:class="scrolled && 'shadow-lg border-b border-subtle is-scrolled'").html_safe if is_preview %>>
|
|
67
74
|
<style>
|
|
68
75
|
/* THE PHONE STEP IS A SHARE OF THE VIEWPORT, NOT A CONSTANT, and that is the
|
|
69
76
|
whole width fix. 14rem is 224px — 57% of a 390px screen — handed to a
|
|
@@ -109,7 +116,8 @@
|
|
|
109
116
|
was left to hand-write its own — the way four independent copies happened
|
|
110
117
|
in the first place. It ships from engine.css now (every consuming app
|
|
111
118
|
imports it), so a forking app OPTS IN with `nav-shell` +
|
|
112
|
-
x-data="navCollapse()"
|
|
119
|
+
data-studio-controller="nav-collapse" (or x-data="navCollapse()",
|
|
120
|
+
the Alpine shim) and overrides only the endpoints that differ.
|
|
113
121
|
|
|
114
122
|
THE OPT-IN IS NOT THE WHOLE JOB — this is the partial a forker actually
|
|
115
123
|
reads, so the caveat belongs here too rather than only beside the band
|