stimeo-ui 0.10.0 → 0.11.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 +75 -0
- data/dist/cable/index.js +226 -43
- data/dist/controllers/pointer_drag_controller.js +64 -18
- data/dist/controllers/portal_controller.js +32 -11
- data/dist/controllers/preview_guard_controller.js +180 -20
- data/dist/controllers/roving_controller.js +127 -14
- data/dist/controllers/stick_to_bottom_controller.js +149 -28
- data/dist/index.js +448 -140
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +2 -2
data/dist/index.js
CHANGED
|
@@ -16289,6 +16289,7 @@ var PersistController = class extends Controller {
|
|
|
16289
16289
|
this.dispatch("error", { detail: { key, operation, reason } });
|
|
16290
16290
|
}
|
|
16291
16291
|
};
|
|
16292
|
+
var NATIVE_KEY_OWNERS = "input, textarea, select, button, a[href], summary, [contenteditable]";
|
|
16292
16293
|
var PointerDragController = class _PointerDragController extends Controller {
|
|
16293
16294
|
static targets = ["handle"];
|
|
16294
16295
|
static values = {
|
|
@@ -16298,6 +16299,7 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
16298
16299
|
disabled: { type: Boolean, default: false },
|
|
16299
16300
|
follow: { type: Boolean, default: false }
|
|
16300
16301
|
};
|
|
16302
|
+
static actions = ["reset"];
|
|
16301
16303
|
static events = ["start", "move", "end", "cancel"];
|
|
16302
16304
|
/** Marker attribute recording that this controller set the touch-action. */
|
|
16303
16305
|
static #TOUCH_ACTION_MARKER = "data-pointer-drag-touch-action";
|
|
@@ -16331,23 +16333,49 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
16331
16333
|
}
|
|
16332
16334
|
this.#teardown();
|
|
16333
16335
|
}
|
|
16334
|
-
/**
|
|
16336
|
+
/**
|
|
16337
|
+
* Returns the element to its origin: drops the committed follow offset and the
|
|
16338
|
+
* inline `translate` that carries it.
|
|
16339
|
+
*
|
|
16340
|
+
* In follow mode the inline `translate` belongs to this controller, and the
|
|
16341
|
+
* committed offset lives in a field the DOM cannot reach — so a consumer that
|
|
16342
|
+
* wants the element back at the start, or that has written a position of its
|
|
16343
|
+
* own, needs this to make the two agree again. An in-flight drag is cancelled
|
|
16344
|
+
* first, or its deltas would land on top of the offset just cleared.
|
|
16345
|
+
*/
|
|
16346
|
+
reset() {
|
|
16347
|
+
const live = this.#pointer?.started ? this.#pointer.pointerType : this.#keyboard ? "keyboard" : null;
|
|
16348
|
+
if (this.#pointer || this.#keyboard) this.#teardownSessions();
|
|
16349
|
+
if (live) this.#dispatchCancel(live);
|
|
16350
|
+
this.#followBase = { x: 0, y: 0 };
|
|
16351
|
+
this.#applyFollow(0, 0);
|
|
16352
|
+
}
|
|
16335
16353
|
handleTargetConnected(handle) {
|
|
16354
|
+
if (handle !== this.element) this.#restoreHandle(this.element);
|
|
16336
16355
|
this.#prepareHandle(handle);
|
|
16337
16356
|
}
|
|
16338
16357
|
handleTargetDisconnected(handle) {
|
|
16339
|
-
if (!this.element.contains(handle)) {
|
|
16340
|
-
if (this.#pointer?.handle === handle) this.#endPointerSession();
|
|
16341
|
-
if (this.#keyboard?.handle === handle) this.#clearKeyboardSession();
|
|
16342
|
-
}
|
|
16343
16358
|
this.#restoreHandle(handle);
|
|
16359
|
+
if (this.element.contains(handle)) return;
|
|
16360
|
+
const interrupted = this.#pointer?.handle === handle && this.#pointer.started ? this.#pointer.pointerType : this.#keyboard?.handle === handle ? "keyboard" : null;
|
|
16361
|
+
if (this.#pointer?.handle === handle) this.#endPointerSession();
|
|
16362
|
+
if (this.#keyboard?.handle === handle) this.#clearKeyboardSession();
|
|
16363
|
+
if (!this.hasHandleTarget) this.#prepareHandle(this.element);
|
|
16364
|
+
if (interrupted) this.#dispatchCancel(interrupted);
|
|
16344
16365
|
}
|
|
16345
|
-
/**
|
|
16346
|
-
|
|
16366
|
+
/**
|
|
16367
|
+
* Re-derives the handles' touch-action when the axis changes.
|
|
16368
|
+
*
|
|
16369
|
+
* Two layers, like the `tabindex` loan: the marker says the value was once
|
|
16370
|
+
* ours, and the current inline value says it still is. A consumer that wrote
|
|
16371
|
+
* its own `touch-action` after connect keeps it.
|
|
16372
|
+
*/
|
|
16373
|
+
axisValueChanged(_value, previousValue) {
|
|
16374
|
+
const lent = this.#touchActionFor(previousValue ?? this.axisValue);
|
|
16347
16375
|
for (const handle of this.#handles()) {
|
|
16348
|
-
if (handle.hasAttribute(_PointerDragController.#TOUCH_ACTION_MARKER))
|
|
16349
|
-
|
|
16350
|
-
|
|
16376
|
+
if (!handle.hasAttribute(_PointerDragController.#TOUCH_ACTION_MARKER)) continue;
|
|
16377
|
+
if (handle.style.touchAction !== lent) continue;
|
|
16378
|
+
handle.style.touchAction = this.#touchActionForAxis();
|
|
16351
16379
|
}
|
|
16352
16380
|
}
|
|
16353
16381
|
/** Cancels any in-flight session when the controller is disabled mid-drag. */
|
|
@@ -16428,9 +16456,10 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
16428
16456
|
const handle = this.#handleFor(event.target);
|
|
16429
16457
|
if (!handle) return;
|
|
16430
16458
|
if (event.defaultPrevented) return;
|
|
16459
|
+
if (event.isComposing) return;
|
|
16460
|
+
if (this.#ownsNativeKeys(event.target, handle)) return;
|
|
16431
16461
|
if (isReservedArrowChord(event)) return;
|
|
16432
16462
|
if (event.key === "Escape") {
|
|
16433
|
-
if (event.isComposing) return;
|
|
16434
16463
|
if (this.#pointer?.started) {
|
|
16435
16464
|
const { pointerType } = this.#pointer;
|
|
16436
16465
|
this.#teardownSessions();
|
|
@@ -16539,6 +16568,7 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
16539
16568
|
const interrupted = this.#pointer?.started ? this.#pointer.pointerType : this.#keyboard ? "keyboard" : null;
|
|
16540
16569
|
this.#teardownSessions();
|
|
16541
16570
|
for (const handle of this.#handles()) this.#restoreHandle(handle);
|
|
16571
|
+
this.#followReset();
|
|
16542
16572
|
if (interrupted && this.element.isConnected) this.#dispatchCancel(interrupted);
|
|
16543
16573
|
}
|
|
16544
16574
|
/**
|
|
@@ -16608,8 +16638,12 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
16608
16638
|
}
|
|
16609
16639
|
/** `touch-action` that lets the page keep panning on the locked axis only. */
|
|
16610
16640
|
#touchActionForAxis() {
|
|
16611
|
-
|
|
16612
|
-
|
|
16641
|
+
return this.#touchActionFor(this.axisValue);
|
|
16642
|
+
}
|
|
16643
|
+
/** The `touch-action` an `axis` declaration lends, for any axis value. */
|
|
16644
|
+
#touchActionFor(axis) {
|
|
16645
|
+
if (axis === "x") return "pan-y";
|
|
16646
|
+
if (axis === "y") return "pan-x";
|
|
16613
16647
|
return "none";
|
|
16614
16648
|
}
|
|
16615
16649
|
/**
|
|
@@ -16620,8 +16654,10 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
16620
16654
|
* are marker-owned so `#restoreHandle` reverts them symmetrically on teardown.
|
|
16621
16655
|
*/
|
|
16622
16656
|
#prepareHandle(handle) {
|
|
16623
|
-
|
|
16624
|
-
|
|
16657
|
+
const lent = this.#touchActionForAxis();
|
|
16658
|
+
const marked = handle.hasAttribute(_PointerDragController.#TOUCH_ACTION_MARKER);
|
|
16659
|
+
if (handle.style.touchAction === "" || marked && handle.style.touchAction === lent) {
|
|
16660
|
+
handle.style.touchAction = lent;
|
|
16625
16661
|
handle.setAttribute(_PointerDragController.#TOUCH_ACTION_MARKER, "true");
|
|
16626
16662
|
}
|
|
16627
16663
|
if (handle.tabIndex < 0 && !handle.hasAttribute("tabindex")) {
|
|
@@ -16629,17 +16665,27 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
16629
16665
|
handle.setAttribute(_PointerDragController.#TABINDEX_MARKER, "true");
|
|
16630
16666
|
}
|
|
16631
16667
|
}
|
|
16668
|
+
/** Whether the key belongs to a native control or editing surface in the handle. */
|
|
16669
|
+
#ownsNativeKeys(target, handle) {
|
|
16670
|
+
const owner = target?.closest(NATIVE_KEY_OWNERS) ?? null;
|
|
16671
|
+
return owner !== null && owner !== handle && handle.contains(owner);
|
|
16672
|
+
}
|
|
16632
16673
|
/** Reverts the marker-owned touch-action + tabindex (authored values untouched). */
|
|
16633
16674
|
#restoreHandle(handle) {
|
|
16634
16675
|
if (handle.hasAttribute(_PointerDragController.#TOUCH_ACTION_MARKER)) {
|
|
16635
|
-
handle.style.touchAction
|
|
16676
|
+
if (handle.style.touchAction === this.#touchActionForAxis()) {
|
|
16677
|
+
handle.style.touchAction = "";
|
|
16678
|
+
}
|
|
16636
16679
|
handle.removeAttribute(_PointerDragController.#TOUCH_ACTION_MARKER);
|
|
16637
16680
|
}
|
|
16638
16681
|
if (handle.hasAttribute(_PointerDragController.#TABINDEX_MARKER)) {
|
|
16639
|
-
handle.
|
|
16640
|
-
if (
|
|
16682
|
+
const ours = handle.getAttribute("tabindex") === "0";
|
|
16683
|
+
if (ours && document.activeElement !== handle) {
|
|
16641
16684
|
handle.removeAttribute("tabindex");
|
|
16685
|
+
handle.removeAttribute(_PointerDragController.#TABINDEX_MARKER);
|
|
16686
|
+
return;
|
|
16642
16687
|
}
|
|
16688
|
+
if (!ours) handle.removeAttribute(_PointerDragController.#TABINDEX_MARKER);
|
|
16643
16689
|
}
|
|
16644
16690
|
}
|
|
16645
16691
|
};
|
|
@@ -16752,15 +16798,42 @@ var PortalController = class extends Controller {
|
|
|
16752
16798
|
static events = ["mount", "unmount"];
|
|
16753
16799
|
/** Decides whether a `disconnect()` is an in-page move or a real detach. */
|
|
16754
16800
|
#gate = new DetachGate();
|
|
16801
|
+
/** The `to` declaration after validation; the default when it cannot be parsed. */
|
|
16802
|
+
#toSelector = "body";
|
|
16803
|
+
/**
|
|
16804
|
+
* Validates the destination declaration once, keeping only a selector the engine can
|
|
16805
|
+
* read.
|
|
16806
|
+
*
|
|
16807
|
+
* A selector reads back as an ordinary string, so a malformed one survives until it is
|
|
16808
|
+
* handed to the DOM — where it would take the whole teleport down and leave the part
|
|
16809
|
+
* silently doing nothing. Falling back to the default puts the node somewhere visible
|
|
16810
|
+
* instead, which reads as a mistake and can be traced back to the declaration.
|
|
16811
|
+
*/
|
|
16812
|
+
toValueChanged() {
|
|
16813
|
+
const selector = this.toValue;
|
|
16814
|
+
if (selector.length > 0) {
|
|
16815
|
+
try {
|
|
16816
|
+
this.element.matches(selector);
|
|
16817
|
+
this.#toSelector = selector;
|
|
16818
|
+
return;
|
|
16819
|
+
} catch {
|
|
16820
|
+
}
|
|
16821
|
+
}
|
|
16822
|
+
this.#toSelector = "body";
|
|
16823
|
+
}
|
|
16755
16824
|
connect() {
|
|
16756
16825
|
this.#gate.cancel();
|
|
16757
|
-
|
|
16826
|
+
const existing = portalState.get(this.element);
|
|
16827
|
+
if (existing) {
|
|
16828
|
+
existing.owner = this;
|
|
16829
|
+
return;
|
|
16830
|
+
}
|
|
16758
16831
|
const node = this.hasContentTarget ? this.contentTarget : this.element;
|
|
16759
16832
|
const destination = this.#destination();
|
|
16760
16833
|
if (!destination || destination === node || node.contains(destination)) return;
|
|
16761
16834
|
const placeholder = document.createComment("stimeo--portal");
|
|
16762
16835
|
node.parentNode?.insertBefore(placeholder, node);
|
|
16763
|
-
portalState.set(this.element, { node, placeholder });
|
|
16836
|
+
portalState.set(this.element, { node, placeholder, owner: this });
|
|
16764
16837
|
if (this.positionValue === "prepend") {
|
|
16765
16838
|
destination.prepend(node);
|
|
16766
16839
|
} else {
|
|
@@ -16779,7 +16852,7 @@ var PortalController = class extends Controller {
|
|
|
16779
16852
|
}
|
|
16780
16853
|
this.#gate.disconnected(this, () => {
|
|
16781
16854
|
const current = portalState.get(this.element);
|
|
16782
|
-
if (current) this.#restore(current);
|
|
16855
|
+
if (current?.owner === this) this.#restore(current);
|
|
16783
16856
|
});
|
|
16784
16857
|
}
|
|
16785
16858
|
/** Returns the node to its placeholder (or removes it) and clears the bookkeeping. */
|
|
@@ -16795,30 +16868,84 @@ var PortalController = class extends Controller {
|
|
|
16795
16868
|
placeholder.remove();
|
|
16796
16869
|
this.dispatch("unmount", { detail: {} });
|
|
16797
16870
|
}
|
|
16798
|
-
/** Resolves the destination
|
|
16871
|
+
/** Resolves the destination from the validated `to` selector. */
|
|
16799
16872
|
#destination() {
|
|
16800
|
-
|
|
16801
|
-
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
|
|
16873
|
+
return document.querySelector(this.#toSelector);
|
|
16874
|
+
}
|
|
16875
|
+
};
|
|
16876
|
+
|
|
16877
|
+
// src/utils/style_property_lease.ts
|
|
16878
|
+
var StylePropertyLease = class {
|
|
16879
|
+
#property;
|
|
16880
|
+
#records = /* @__PURE__ */ new Map();
|
|
16881
|
+
/** @param property - The CSS property whose temporary values this lease owns. */
|
|
16882
|
+
constructor(property) {
|
|
16883
|
+
this.#property = property;
|
|
16884
|
+
}
|
|
16885
|
+
/** Writes or removes the leased declaration while preserving its authored value. */
|
|
16886
|
+
write(element, value, priority = "") {
|
|
16887
|
+
const existing = this.#records.get(element);
|
|
16888
|
+
if (existing) {
|
|
16889
|
+
existing.writtenValue = value;
|
|
16890
|
+
existing.writtenPriority = value === null ? "" : priority;
|
|
16891
|
+
} else {
|
|
16892
|
+
this.#records.set(element, {
|
|
16893
|
+
originalValue: element.style.getPropertyValue(this.#property),
|
|
16894
|
+
originalPriority: element.style.getPropertyPriority(this.#property),
|
|
16895
|
+
writtenValue: value,
|
|
16896
|
+
writtenPriority: value === null ? "" : priority
|
|
16897
|
+
});
|
|
16806
16898
|
}
|
|
16899
|
+
this.#reflect(element, value, priority);
|
|
16900
|
+
}
|
|
16901
|
+
/** Returns one lease without overwriting a later consumer declaration. */
|
|
16902
|
+
return(element) {
|
|
16903
|
+
const record = this.#records.get(element);
|
|
16904
|
+
if (!record) return;
|
|
16905
|
+
this.#records.delete(element);
|
|
16906
|
+
const style = element.style;
|
|
16907
|
+
const stillOwned = style.getPropertyValue(this.#property) === (record.writtenValue ?? "") && style.getPropertyPriority(this.#property) === record.writtenPriority;
|
|
16908
|
+
if (stillOwned) {
|
|
16909
|
+
this.#reflect(element, record.originalValue, record.originalPriority);
|
|
16910
|
+
}
|
|
16911
|
+
}
|
|
16912
|
+
/** Returns every outstanding declaration lease. */
|
|
16913
|
+
returnAll() {
|
|
16914
|
+
for (const element of Array.from(this.#records.keys())) this.return(element);
|
|
16915
|
+
}
|
|
16916
|
+
/** Reflects only a real declaration transition. */
|
|
16917
|
+
#reflect(element, value, priority) {
|
|
16918
|
+
const style = element.style;
|
|
16919
|
+
const nextValue = value ?? "";
|
|
16920
|
+
const nextPriority = value === null ? "" : priority;
|
|
16921
|
+
if (style.getPropertyValue(this.#property) === nextValue && style.getPropertyPriority(this.#property) === nextPriority) {
|
|
16922
|
+
return;
|
|
16923
|
+
}
|
|
16924
|
+
if (value === null) style.removeProperty(this.#property);
|
|
16925
|
+
else style.setProperty(this.#property, value, priority);
|
|
16807
16926
|
}
|
|
16808
16927
|
};
|
|
16928
|
+
|
|
16929
|
+
// src/controllers/preview_guard_controller.ts
|
|
16809
16930
|
var PreviewGuardController = class extends Controller {
|
|
16810
16931
|
static values = {
|
|
16811
|
-
placeholder: { type: String, default: "" }
|
|
16812
|
-
mode: { type: String, default: "hide" }
|
|
16932
|
+
placeholder: { type: String, default: "" }
|
|
16813
16933
|
};
|
|
16814
16934
|
static events = ["hide", "show"];
|
|
16935
|
+
#visibility = new StylePropertyLease("visibility");
|
|
16936
|
+
#beforeCache = new BeforeCacheReset(() => this.#restore());
|
|
16815
16937
|
#observer = null;
|
|
16938
|
+
#connected = false;
|
|
16816
16939
|
#hidden = false;
|
|
16817
|
-
/**
|
|
16818
|
-
#
|
|
16819
|
-
/**
|
|
16820
|
-
#
|
|
16940
|
+
/** Child nodes a placeholder displaced; non-null marks that content — not visibility — was swapped. */
|
|
16941
|
+
#savedNodes = null;
|
|
16942
|
+
/** The descendant that held focus when the guard went up, so show can hand it back. */
|
|
16943
|
+
#focused = null;
|
|
16821
16944
|
connect() {
|
|
16945
|
+
this.#connected = true;
|
|
16946
|
+
if (this.#hidden) this.#reguard();
|
|
16947
|
+
else this.element.removeAttribute("data-preview-hidden");
|
|
16948
|
+
this.#beforeCache.activate();
|
|
16822
16949
|
if (typeof MutationObserver !== "undefined") {
|
|
16823
16950
|
this.#observer = new MutationObserver(() => this.#sync());
|
|
16824
16951
|
this.#observer.observe(document.documentElement, {
|
|
@@ -16829,44 +16956,115 @@ var PreviewGuardController = class extends Controller {
|
|
|
16829
16956
|
this.#sync();
|
|
16830
16957
|
}
|
|
16831
16958
|
disconnect() {
|
|
16959
|
+
this.#connected = false;
|
|
16960
|
+
this.#beforeCache.deactivate();
|
|
16832
16961
|
this.#observer?.disconnect();
|
|
16833
16962
|
this.#observer = null;
|
|
16834
|
-
this.#restore();
|
|
16835
16963
|
}
|
|
16836
|
-
/**
|
|
16964
|
+
/**
|
|
16965
|
+
* Re-guards to match a `placeholder` changed at runtime — the value is the content on
|
|
16966
|
+
* display while the guard is up, so a morph that swaps it must not leave the old one.
|
|
16967
|
+
*/
|
|
16968
|
+
placeholderValueChanged() {
|
|
16969
|
+
if (!this.#connected || !this.#hidden) return;
|
|
16970
|
+
this.#reguard();
|
|
16971
|
+
}
|
|
16972
|
+
/**
|
|
16973
|
+
* Reflects the current `data-turbo-preview` state onto the element.
|
|
16974
|
+
*
|
|
16975
|
+
* @stimeoRenderRoot
|
|
16976
|
+
*/
|
|
16837
16977
|
#sync() {
|
|
16838
16978
|
const previewing = document.documentElement.hasAttribute("data-turbo-preview");
|
|
16839
16979
|
if (previewing && !this.#hidden) this.#hide();
|
|
16840
16980
|
else if (!previewing && this.#hidden) this.#show();
|
|
16841
16981
|
}
|
|
16842
16982
|
#hide() {
|
|
16983
|
+
this.#focused = this.#focusedInside();
|
|
16984
|
+
this.#applyGuard();
|
|
16985
|
+
this.dispatch("hide", { detail: {} });
|
|
16986
|
+
}
|
|
16987
|
+
/** Puts the guard up in the form the current `placeholder` calls for. */
|
|
16988
|
+
#applyGuard() {
|
|
16843
16989
|
this.#hidden = true;
|
|
16844
|
-
if (this.
|
|
16845
|
-
this.#
|
|
16846
|
-
this.element.textContent = this.placeholderValue;
|
|
16990
|
+
if (this.placeholderValue === "") {
|
|
16991
|
+
this.#visibility.write(this.element, "hidden");
|
|
16847
16992
|
} else {
|
|
16848
|
-
this.#
|
|
16849
|
-
this.element.
|
|
16993
|
+
this.#savedNodes = document.createDocumentFragment();
|
|
16994
|
+
this.#savedNodes.append(...this.element.childNodes);
|
|
16995
|
+
this.element.textContent = this.placeholderValue;
|
|
16850
16996
|
}
|
|
16851
16997
|
this.element.setAttribute("data-preview-hidden", "true");
|
|
16852
|
-
|
|
16998
|
+
}
|
|
16999
|
+
/**
|
|
17000
|
+
* Re-forms a guard that is already up so it matches the current `placeholder`.
|
|
17001
|
+
*
|
|
17002
|
+
* No event: `hide` reports that the guard went up, and it has not come down. Swapping
|
|
17003
|
+
* one stand-in text for another writes only that text — putting the held content back
|
|
17004
|
+
* first would reconnect the whole subtree for an instant. Only a change of *form* —
|
|
17005
|
+
* to or from the empty placeholder — has to revert, and the focus the guard is holding
|
|
17006
|
+
* carries across it rather than being handed back and taken again.
|
|
17007
|
+
*/
|
|
17008
|
+
#reguard() {
|
|
17009
|
+
if (this.#savedNodes && this.placeholderValue !== "") {
|
|
17010
|
+
this.element.textContent = this.placeholderValue;
|
|
17011
|
+
return;
|
|
17012
|
+
}
|
|
17013
|
+
const held = this.#focused;
|
|
17014
|
+
this.#revert();
|
|
17015
|
+
this.#applyGuard();
|
|
17016
|
+
this.#focused = held;
|
|
16853
17017
|
}
|
|
16854
17018
|
#show() {
|
|
16855
17019
|
this.#restore();
|
|
16856
17020
|
this.dispatch("show", { detail: {} });
|
|
16857
17021
|
}
|
|
16858
|
-
/** Reverts the guard
|
|
17022
|
+
/** Reverts the guard and hands focus back. Used by show and by the snapshot rewind. */
|
|
16859
17023
|
#restore() {
|
|
16860
|
-
|
|
17024
|
+
this.#revert();
|
|
17025
|
+
this.#refocus();
|
|
17026
|
+
}
|
|
17027
|
+
/**
|
|
17028
|
+
* Puts the element back the way the guard found it.
|
|
17029
|
+
*
|
|
17030
|
+
* Every step is a no-op on an element this controller never guarded: the lease returns
|
|
17031
|
+
* only declarations it recorded, and the hook is removed whether or not it is there.
|
|
17032
|
+
*/
|
|
17033
|
+
#revert() {
|
|
16861
17034
|
this.#hidden = false;
|
|
16862
|
-
if (this.#
|
|
16863
|
-
this.element.textContent =
|
|
16864
|
-
this.#
|
|
17035
|
+
if (this.#savedNodes) {
|
|
17036
|
+
this.element.textContent = "";
|
|
17037
|
+
this.element.append(this.#savedNodes);
|
|
17038
|
+
this.#savedNodes = null;
|
|
16865
17039
|
} else {
|
|
16866
|
-
this.element
|
|
17040
|
+
this.#visibility.return(this.element);
|
|
16867
17041
|
}
|
|
16868
17042
|
this.element.removeAttribute("data-preview-hidden");
|
|
16869
17043
|
}
|
|
17044
|
+
/**
|
|
17045
|
+
* The focused element the guard is about to make unfocusable, if any.
|
|
17046
|
+
*
|
|
17047
|
+
* The element itself counts as well as its descendants: guarding takes focus either way
|
|
17048
|
+
* — a `visibility: hidden` subtree cannot hold it, and a placeholder displaces the nodes
|
|
17049
|
+
* outright — so the browser drops focus to `<body>` the moment the guard goes up.
|
|
17050
|
+
*/
|
|
17051
|
+
#focusedInside() {
|
|
17052
|
+
const active = document.activeElement;
|
|
17053
|
+
return active instanceof HTMLElement && this.element.contains(active) ? active : null;
|
|
17054
|
+
}
|
|
17055
|
+
/**
|
|
17056
|
+
* Hands focus back to the element the guard took it from.
|
|
17057
|
+
*
|
|
17058
|
+
* Only when that element is still in the document and focus has not moved on since —
|
|
17059
|
+
* anything else is the user's or another controller's, and putting it back would be
|
|
17060
|
+
* taking it.
|
|
17061
|
+
*/
|
|
17062
|
+
#refocus() {
|
|
17063
|
+
const target = this.#focused;
|
|
17064
|
+
this.#focused = null;
|
|
17065
|
+
if (!target?.isConnected || document.activeElement !== document.body) return;
|
|
17066
|
+
target.focus();
|
|
17067
|
+
}
|
|
16870
17068
|
};
|
|
16871
17069
|
var OWNED_VALUE_TEXT2 = "data-stimeo--progress-owns-valuetext";
|
|
16872
17070
|
var ProgressController = class extends Controller {
|
|
@@ -18668,6 +18866,7 @@ var ResizableController = class extends Controller {
|
|
|
18668
18866
|
return this.hasSeparatorTarget && this.separatorTarget.getAttribute("aria-orientation") === "vertical";
|
|
18669
18867
|
}
|
|
18670
18868
|
};
|
|
18869
|
+
var STATE_ATTRIBUTES2 = ["disabled", "hidden"];
|
|
18671
18870
|
var RovingController = class extends Controller {
|
|
18672
18871
|
static targets = ["item"];
|
|
18673
18872
|
static values = {
|
|
@@ -18677,12 +18876,14 @@ var RovingController = class extends Controller {
|
|
|
18677
18876
|
};
|
|
18678
18877
|
static events = ["change"];
|
|
18679
18878
|
#roving = new RovingTabindex(() => this.itemTargets);
|
|
18680
|
-
#reconcile = new MicrotaskCoalescer(() => this.#ensureTabStop());
|
|
18879
|
+
#reconcile = new MicrotaskCoalescer(() => this.#ensureTabStop(true));
|
|
18681
18880
|
#connected = false;
|
|
18881
|
+
#observer = null;
|
|
18682
18882
|
connect() {
|
|
18683
|
-
this.#ensureTabStop();
|
|
18883
|
+
this.#ensureTabStop(false);
|
|
18684
18884
|
this.element.addEventListener("keydown", this.#onKeydown);
|
|
18685
18885
|
this.element.addEventListener("focusin", this.#onFocusin);
|
|
18886
|
+
this.#watchState();
|
|
18686
18887
|
this.#connected = true;
|
|
18687
18888
|
this.#reconcile.activate();
|
|
18688
18889
|
}
|
|
@@ -18691,6 +18892,8 @@ var RovingController = class extends Controller {
|
|
|
18691
18892
|
this.#reconcile.cancel();
|
|
18692
18893
|
this.element.removeEventListener("keydown", this.#onKeydown);
|
|
18693
18894
|
this.element.removeEventListener("focusin", this.#onFocusin);
|
|
18895
|
+
this.#observer?.disconnect();
|
|
18896
|
+
this.#observer = null;
|
|
18694
18897
|
}
|
|
18695
18898
|
/** Drops a runtime-added item from the Tab sequence before batch reconciliation. */
|
|
18696
18899
|
itemTargetConnected(item) {
|
|
@@ -18706,10 +18909,9 @@ var RovingController = class extends Controller {
|
|
|
18706
18909
|
#onKeydown = (event) => {
|
|
18707
18910
|
if (event.defaultPrevented) return;
|
|
18708
18911
|
if (isReservedArrowChord(event)) return;
|
|
18709
|
-
|
|
18912
|
+
if (event.isComposing) return;
|
|
18710
18913
|
const current = this.#indexOf(event.target);
|
|
18711
18914
|
if (current === -1) return;
|
|
18712
|
-
const length = items.length;
|
|
18713
18915
|
const wrap = this.wrapValue ? "wrap" : "clamp";
|
|
18714
18916
|
const orientation = this.orientationValue;
|
|
18715
18917
|
const horizontal = orientation === "horizontal" || orientation === "both";
|
|
@@ -18719,16 +18921,16 @@ var RovingController = class extends Controller {
|
|
|
18719
18921
|
const backwardKey = rtl ? "ArrowRight" : "ArrowLeft";
|
|
18720
18922
|
let next;
|
|
18721
18923
|
if (horizontal && event.key === forwardKey || vertical && event.key === "ArrowDown") {
|
|
18722
|
-
next =
|
|
18924
|
+
next = this.#step(current, 1, wrap);
|
|
18723
18925
|
} else if (horizontal && event.key === backwardKey || vertical && event.key === "ArrowUp") {
|
|
18724
|
-
next =
|
|
18725
|
-
} else if (this.homeEndValue && event.key === "Home") {
|
|
18726
|
-
|
|
18727
|
-
|
|
18728
|
-
next = length - 1;
|
|
18926
|
+
next = this.#step(current, -1, wrap);
|
|
18927
|
+
} else if (this.homeEndValue && (event.key === "Home" || event.key === "End")) {
|
|
18928
|
+
if (hasModifierChord(event)) return;
|
|
18929
|
+
next = event.key === "Home" ? this.#firstReachable() : this.#lastReachable();
|
|
18729
18930
|
} else {
|
|
18730
18931
|
return;
|
|
18731
18932
|
}
|
|
18933
|
+
if (next === -1) return;
|
|
18732
18934
|
event.preventDefault();
|
|
18733
18935
|
this.#activate(next, true);
|
|
18734
18936
|
};
|
|
@@ -18739,7 +18941,8 @@ var RovingController = class extends Controller {
|
|
|
18739
18941
|
*/
|
|
18740
18942
|
#onFocusin = (event) => {
|
|
18741
18943
|
const index = this.#indexOf(event.target);
|
|
18742
|
-
if (index
|
|
18944
|
+
if (index === -1 || !this.#reachable(index)) return;
|
|
18945
|
+
this.#activate(index, false);
|
|
18743
18946
|
};
|
|
18744
18947
|
/** Resolves the item index owning an event target (the item or a descendant). */
|
|
18745
18948
|
#indexOf(target) {
|
|
@@ -18755,66 +18958,106 @@ var RovingController = class extends Controller {
|
|
|
18755
18958
|
this.dispatch("change", { detail: { index, item: this.itemTargets[index] } });
|
|
18756
18959
|
}
|
|
18757
18960
|
}
|
|
18758
|
-
/**
|
|
18759
|
-
|
|
18961
|
+
/**
|
|
18962
|
+
* Keeps the Tab stop on a reachable item.
|
|
18963
|
+
*
|
|
18964
|
+
* `followFocus` is on for re-establishment only: an item added and focused in
|
|
18965
|
+
* the same task has already claimed the stop through `focusin`, and the batch
|
|
18966
|
+
* that follows must not hand it back to the first item. `connect()` passes it
|
|
18967
|
+
* off so the authored DOM decides the initial stop. With nothing reachable the
|
|
18968
|
+
* DOM is left as it is — a group inside a collapsed region gets its stop back
|
|
18969
|
+
* when the region opens, instead of losing it for good.
|
|
18970
|
+
*/
|
|
18971
|
+
#ensureTabStop(followFocus) {
|
|
18972
|
+
const items = this.itemTargets;
|
|
18973
|
+
const focused = followFocus ? items.findIndex((item, i) => item === document.activeElement && this.#reachable(i)) : -1;
|
|
18760
18974
|
const active = this.#roving.activeIndex;
|
|
18761
|
-
|
|
18762
|
-
|
|
18763
|
-
|
|
18764
|
-
|
|
18765
|
-
// src/utils/style_property_lease.ts
|
|
18766
|
-
var StylePropertyLease = class {
|
|
18767
|
-
#property;
|
|
18768
|
-
#records = /* @__PURE__ */ new Map();
|
|
18769
|
-
/** @param property - The CSS property whose temporary values this lease owns. */
|
|
18770
|
-
constructor(property) {
|
|
18771
|
-
this.#property = property;
|
|
18975
|
+
const kept = active !== -1 && this.#reachable(active) ? active : -1;
|
|
18976
|
+
const index = focused !== -1 ? focused : kept !== -1 ? kept : this.#firstReachable();
|
|
18977
|
+
if (index === -1) return;
|
|
18978
|
+
this.#roving.setActive(index);
|
|
18772
18979
|
}
|
|
18773
|
-
/**
|
|
18774
|
-
|
|
18775
|
-
|
|
18776
|
-
|
|
18777
|
-
|
|
18778
|
-
|
|
18779
|
-
|
|
18780
|
-
|
|
18781
|
-
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
|
|
18785
|
-
|
|
18980
|
+
/**
|
|
18981
|
+
* Resolves the next reachable index in `delta`'s direction, honouring `wrap`.
|
|
18982
|
+
*
|
|
18983
|
+
* An arrow on the widget's own axis is the widget's to consume even when the
|
|
18984
|
+
* position does not change, so a clamped end resolves to the current item
|
|
18985
|
+
* rather than to nothing. An unreachable origin escapes to the first reachable
|
|
18986
|
+
* item instead of sitting in a dead end, and `-1` is left for the one case that
|
|
18987
|
+
* really is not ours: no reachable item anywhere.
|
|
18988
|
+
*/
|
|
18989
|
+
#step(current, delta, wrap) {
|
|
18990
|
+
const length = this.itemTargets.length;
|
|
18991
|
+
let index = current;
|
|
18992
|
+
for (let taken = 0; taken < length; taken += 1) {
|
|
18993
|
+
const candidate = rovingMove(index, length, delta, wrap);
|
|
18994
|
+
if (candidate === index) break;
|
|
18995
|
+
index = candidate;
|
|
18996
|
+
if (this.#reachable(index)) return index;
|
|
18997
|
+
}
|
|
18998
|
+
return this.#reachable(current) ? current : this.#firstReachable();
|
|
18999
|
+
}
|
|
19000
|
+
/** Index of the first reachable item, or `-1`. */
|
|
19001
|
+
#firstReachable() {
|
|
19002
|
+
return this.itemTargets.findIndex((_item, i) => this.#reachable(i));
|
|
19003
|
+
}
|
|
19004
|
+
/** Index of the last reachable item, or `-1`. */
|
|
19005
|
+
#lastReachable() {
|
|
19006
|
+
for (let i = this.itemTargets.length - 1; i >= 0; i -= 1) {
|
|
19007
|
+
if (this.#reachable(i)) return i;
|
|
18786
19008
|
}
|
|
18787
|
-
|
|
19009
|
+
return -1;
|
|
18788
19010
|
}
|
|
18789
|
-
/**
|
|
18790
|
-
|
|
18791
|
-
|
|
18792
|
-
|
|
18793
|
-
|
|
18794
|
-
|
|
18795
|
-
|
|
18796
|
-
|
|
18797
|
-
|
|
18798
|
-
|
|
19011
|
+
/**
|
|
19012
|
+
* Whether the item at `index` can hold the Tab stop and take focus.
|
|
19013
|
+
*
|
|
19014
|
+
* `aria-disabled` is deliberately not consulted: it keeps an item reachable and
|
|
19015
|
+
* only suppresses activation, which this part does not own.
|
|
19016
|
+
*/
|
|
19017
|
+
#reachable(index) {
|
|
19018
|
+
const item = this.itemTargets[index];
|
|
19019
|
+
if (!item) return false;
|
|
19020
|
+
if (this.#hidden(item)) return false;
|
|
19021
|
+
if (!("disabled" in item)) return true;
|
|
19022
|
+
if (item.disabled) return false;
|
|
19023
|
+
return !inheritsFieldsetDisabled(item);
|
|
18799
19024
|
}
|
|
18800
|
-
/**
|
|
18801
|
-
|
|
18802
|
-
|
|
19025
|
+
/**
|
|
19026
|
+
* Whether `item`, or anything between it and the container, is `hidden`.
|
|
19027
|
+
*
|
|
19028
|
+
* The walk stops at the container on purpose: a group inside a hidden region is
|
|
19029
|
+
* already out of the page's Tab order, and calling every item unreachable there
|
|
19030
|
+
* would drop the stop with nothing left to restore it — the ancestor lies
|
|
19031
|
+
* outside the subtree whose state attributes are watched.
|
|
19032
|
+
*/
|
|
19033
|
+
#hidden(item) {
|
|
19034
|
+
let node = item;
|
|
19035
|
+
while (node && node !== this.element) {
|
|
19036
|
+
if (node.hasAttribute("hidden")) return true;
|
|
19037
|
+
node = node.parentElement;
|
|
19038
|
+
}
|
|
19039
|
+
return false;
|
|
18803
19040
|
}
|
|
18804
|
-
/**
|
|
18805
|
-
|
|
18806
|
-
|
|
18807
|
-
|
|
18808
|
-
|
|
18809
|
-
|
|
18810
|
-
|
|
19041
|
+
/**
|
|
19042
|
+
* Watches the attributes that decide reachability, so disabling the item that
|
|
19043
|
+
* holds the Tab stop hands it to another one instead of taking the whole set
|
|
19044
|
+
* out of the Tab sequence. An enclosing `fieldset` disables items from outside
|
|
19045
|
+
* the observed subtree, so each one's own `disabled` is watched too.
|
|
19046
|
+
*/
|
|
19047
|
+
#watchState() {
|
|
19048
|
+
if (typeof MutationObserver === "undefined") return;
|
|
19049
|
+
const observer = new MutationObserver(() => this.#reconcile.schedule());
|
|
19050
|
+
observer.observe(this.element, {
|
|
19051
|
+
subtree: true,
|
|
19052
|
+
attributes: true,
|
|
19053
|
+
attributeFilter: STATE_ATTRIBUTES2
|
|
19054
|
+
});
|
|
19055
|
+
for (let fieldset = this.element.parentElement?.closest("fieldset") ?? null; fieldset; fieldset = fieldset.parentElement?.closest("fieldset") ?? null) {
|
|
19056
|
+
observer.observe(fieldset, { attributes: true, attributeFilter: ["disabled"] });
|
|
18811
19057
|
}
|
|
18812
|
-
|
|
18813
|
-
else style.setProperty(this.#property, value, priority);
|
|
19058
|
+
this.#observer = observer;
|
|
18814
19059
|
}
|
|
18815
19060
|
};
|
|
18816
|
-
|
|
18817
|
-
// src/controllers/scroll_area_controller.ts
|
|
18818
19061
|
var EDGE_EPSILON = 1;
|
|
18819
19062
|
var ScrollAreaController = class extends Controller {
|
|
18820
19063
|
static targets = ["viewport"];
|
|
@@ -21283,6 +21526,7 @@ var StepperController = class extends Controller {
|
|
|
21283
21526
|
return Math.min(last, Math.max(0, Math.trunc(index)));
|
|
21284
21527
|
}
|
|
21285
21528
|
};
|
|
21529
|
+
var DEFAULT_THRESHOLD = 80;
|
|
21286
21530
|
var countElements = (nodes) => {
|
|
21287
21531
|
let n = 0;
|
|
21288
21532
|
for (const node of nodes) if (node.nodeType === Node.ELEMENT_NODE) n += 1;
|
|
@@ -21291,34 +21535,53 @@ var countElements = (nodes) => {
|
|
|
21291
21535
|
var StickToBottomController = class extends Controller {
|
|
21292
21536
|
static targets = ["content"];
|
|
21293
21537
|
static values = {
|
|
21294
|
-
threshold: { type: Number, default:
|
|
21538
|
+
threshold: { type: Number, default: DEFAULT_THRESHOLD },
|
|
21295
21539
|
behavior: { type: String, default: "auto" },
|
|
21296
21540
|
pinOnConnect: { type: Boolean, default: false }
|
|
21297
21541
|
};
|
|
21298
21542
|
static actions = ["scrollToBottom"];
|
|
21299
21543
|
static events = ["pin", "new"];
|
|
21300
21544
|
#observer = null;
|
|
21301
|
-
/**
|
|
21302
|
-
#
|
|
21545
|
+
/** The element the append observer currently holds, so a swap can be detected. */
|
|
21546
|
+
#watched = null;
|
|
21547
|
+
/** Watches for the box a container connected without one is still waiting on. */
|
|
21548
|
+
#layout = new LayoutObserver(() => this.#onLaidOut());
|
|
21549
|
+
#awaitingLayout = false;
|
|
21550
|
+
#connected = false;
|
|
21303
21551
|
#pinned = false;
|
|
21304
21552
|
#onScroll = () => this.#updatePinned();
|
|
21305
21553
|
connect() {
|
|
21554
|
+
this.#connected = true;
|
|
21306
21555
|
if (this.pinOnConnectValue && this.#measurable()) this.#scrollToBottom("instant");
|
|
21556
|
+
this.element.removeAttribute("data-has-new");
|
|
21307
21557
|
this.#pinned = this.#isPinned();
|
|
21308
21558
|
this.#reflectPinned();
|
|
21309
21559
|
this.element.addEventListener("scroll", this.#onScroll, { passive: true });
|
|
21310
|
-
|
|
21311
|
-
|
|
21312
|
-
this.#observer.observe(this.#watched(), { childList: true });
|
|
21313
|
-
}
|
|
21314
|
-
if (this.pinOnConnectValue && !this.#measurable()) this.#pinWhenLaidOut();
|
|
21560
|
+
this.#syncWatched();
|
|
21561
|
+
if (!this.#measurable()) this.#waitForLayout();
|
|
21315
21562
|
}
|
|
21316
21563
|
disconnect() {
|
|
21564
|
+
this.#connected = false;
|
|
21317
21565
|
this.element.removeEventListener("scroll", this.#onScroll);
|
|
21318
|
-
this.#
|
|
21319
|
-
this.#observer = null;
|
|
21566
|
+
this.#stopWatching();
|
|
21320
21567
|
this.#stopWaitingForLayout();
|
|
21321
21568
|
}
|
|
21569
|
+
/** Moves the append watch onto a `content` target that arrived at runtime. */
|
|
21570
|
+
contentTargetConnected() {
|
|
21571
|
+
this.#syncWatched();
|
|
21572
|
+
}
|
|
21573
|
+
/** Moves the append watch off a `content` target that left, back onto the container. */
|
|
21574
|
+
contentTargetDisconnected() {
|
|
21575
|
+
this.#syncWatched();
|
|
21576
|
+
}
|
|
21577
|
+
/**
|
|
21578
|
+
* Re-derives pinned when the distance that counts as the bottom is changed at runtime
|
|
21579
|
+
* (a morph that swaps the attribute on a retained element).
|
|
21580
|
+
*/
|
|
21581
|
+
thresholdValueChanged() {
|
|
21582
|
+
if (!this.#connected) return;
|
|
21583
|
+
this.#updatePinned();
|
|
21584
|
+
}
|
|
21322
21585
|
/**
|
|
21323
21586
|
* Jumps to the bottom and re-pins (wired to a "new messages" button).
|
|
21324
21587
|
*
|
|
@@ -21347,7 +21610,11 @@ var StickToBottomController = class extends Controller {
|
|
|
21347
21610
|
this.dispatch("new", { detail: { count: added } });
|
|
21348
21611
|
}
|
|
21349
21612
|
}
|
|
21350
|
-
/**
|
|
21613
|
+
/**
|
|
21614
|
+
* Recomputes pinned from the scroll position and reflects it on a transition.
|
|
21615
|
+
*
|
|
21616
|
+
* @stimeoRenderRoot
|
|
21617
|
+
*/
|
|
21351
21618
|
#updatePinned() {
|
|
21352
21619
|
const pinned = this.#isPinned();
|
|
21353
21620
|
if (pinned === this.#pinned) return;
|
|
@@ -21364,9 +21631,25 @@ var StickToBottomController = class extends Controller {
|
|
|
21364
21631
|
this.element.removeAttribute("data-pinned");
|
|
21365
21632
|
}
|
|
21366
21633
|
}
|
|
21634
|
+
/** Whether the container currently sits within `threshold` of its bottom. */
|
|
21367
21635
|
#isPinned() {
|
|
21636
|
+
if (!this.#measurable()) return false;
|
|
21368
21637
|
const el = this.element;
|
|
21369
|
-
return el.scrollHeight - el.clientHeight - el.scrollTop <= this
|
|
21638
|
+
return el.scrollHeight - el.clientHeight - el.scrollTop <= this.#threshold;
|
|
21639
|
+
}
|
|
21640
|
+
/**
|
|
21641
|
+
* The distance from the bottom that counts as pinned: a finite, non-negative number of
|
|
21642
|
+
* pixels. Anything else names no distance the container can be at, and settles the
|
|
21643
|
+
* comparison the same way at every scroll position, so it falls back to the default.
|
|
21644
|
+
* `Number` reads `"abc"` as `NaN` and every comparison against it is false; a negative
|
|
21645
|
+
* distance sits below the closest the container ever gets; `Infinity` is never
|
|
21646
|
+
* exceeded. The first two stop following and flag every append as new, and the last
|
|
21647
|
+
* never stops following — it takes the reading position the flag exists to protect.
|
|
21648
|
+
* Zero is a real declaration: it pins at the exact bottom only.
|
|
21649
|
+
*/
|
|
21650
|
+
get #threshold() {
|
|
21651
|
+
const declared = this.thresholdValue;
|
|
21652
|
+
return Number.isFinite(declared) && declared >= 0 ? declared : DEFAULT_THRESHOLD;
|
|
21370
21653
|
}
|
|
21371
21654
|
/**
|
|
21372
21655
|
* Whether the container has a box to scroll and to measure. One that is not rendered
|
|
@@ -21377,23 +21660,25 @@ var StickToBottomController = class extends Controller {
|
|
|
21377
21660
|
return this.element.clientHeight > 0;
|
|
21378
21661
|
}
|
|
21379
21662
|
/**
|
|
21380
|
-
* Holds the
|
|
21381
|
-
*
|
|
21663
|
+
* Holds the pinned decision until the container is laid out — otherwise the panel opens
|
|
21664
|
+
* at the top while the state claims the bottom, and the appends that arrived meanwhile
|
|
21665
|
+
* were followed into a box that could not move rather than flagged.
|
|
21382
21666
|
*/
|
|
21383
|
-
#
|
|
21384
|
-
|
|
21385
|
-
this.#layout = new ResizeObserver(() => {
|
|
21386
|
-
if (!this.#measurable()) return;
|
|
21387
|
-
this.#stopWaitingForLayout();
|
|
21388
|
-
this.#scrollToBottom("instant");
|
|
21389
|
-
this.#updatePinned();
|
|
21390
|
-
});
|
|
21667
|
+
#waitForLayout() {
|
|
21668
|
+
this.#awaitingLayout = true;
|
|
21391
21669
|
this.#layout.observe(this.element);
|
|
21392
21670
|
}
|
|
21393
|
-
/**
|
|
21671
|
+
/** Runs the held decision once the container has the box it was waiting for. */
|
|
21672
|
+
#onLaidOut() {
|
|
21673
|
+
if (!this.#awaitingLayout || !this.#measurable()) return;
|
|
21674
|
+
this.#stopWaitingForLayout();
|
|
21675
|
+
if (this.pinOnConnectValue) this.#scrollToBottom("instant");
|
|
21676
|
+
this.#updatePinned();
|
|
21677
|
+
}
|
|
21678
|
+
/** Releases the layout watch, whether or not the held decision ever ran. */
|
|
21394
21679
|
#stopWaitingForLayout() {
|
|
21395
|
-
this.#
|
|
21396
|
-
this.#layout
|
|
21680
|
+
this.#awaitingLayout = false;
|
|
21681
|
+
this.#layout.disconnect();
|
|
21397
21682
|
}
|
|
21398
21683
|
/**
|
|
21399
21684
|
* Scrolls to the bottom, clamped by the engine to the maximum scroll offset — which is
|
|
@@ -21409,9 +21694,32 @@ var StickToBottomController = class extends Controller {
|
|
|
21409
21694
|
this.element.scrollTop = top;
|
|
21410
21695
|
}
|
|
21411
21696
|
}
|
|
21412
|
-
/**
|
|
21413
|
-
|
|
21414
|
-
|
|
21697
|
+
/**
|
|
21698
|
+
* Points the append watch at the current `content` target, or at the container when
|
|
21699
|
+
* there is none. Re-resolved whenever that target changes, so a swap does not leave the
|
|
21700
|
+
* observer holding a detached node whose appends nobody sees.
|
|
21701
|
+
*
|
|
21702
|
+
* Stimulus runs the target callbacks outside the connected window too — before
|
|
21703
|
+
* `connect()` for a target already in the DOM, and after `disconnect()` while the
|
|
21704
|
+
* element is torn down — where this would arm an observer nothing releases. Re-syncing
|
|
21705
|
+
* to the target already held is left alone, so an arrival still in flight is not
|
|
21706
|
+
* dropped with the observer that was about to deliver it.
|
|
21707
|
+
*/
|
|
21708
|
+
#syncWatched() {
|
|
21709
|
+
if (!this.#connected) return;
|
|
21710
|
+
const next = this.hasContentTarget ? this.contentTarget : this.element;
|
|
21711
|
+
if (next === this.#watched) return;
|
|
21712
|
+
this.#stopWatching();
|
|
21713
|
+
this.#watched = next;
|
|
21714
|
+
if (typeof MutationObserver === "undefined") return;
|
|
21715
|
+
this.#observer = new MutationObserver((mutations) => this.#onMutations(mutations));
|
|
21716
|
+
this.#observer.observe(next, { childList: true });
|
|
21717
|
+
}
|
|
21718
|
+
/** Releases the append watch and the element it held. */
|
|
21719
|
+
#stopWatching() {
|
|
21720
|
+
this.#observer?.disconnect();
|
|
21721
|
+
this.#observer = null;
|
|
21722
|
+
this.#watched = null;
|
|
21415
21723
|
}
|
|
21416
21724
|
/**
|
|
21417
21725
|
* The behavior a follow-scroll runs with. `"auto"` is **not** a request to arrive at
|
|
@@ -23673,7 +23981,7 @@ var ToggleGroupController = class extends Controller {
|
|
|
23673
23981
|
return item.getAttribute("data-value") ?? "";
|
|
23674
23982
|
}
|
|
23675
23983
|
};
|
|
23676
|
-
var
|
|
23984
|
+
var STATE_ATTRIBUTES3 = ["disabled", "hidden"];
|
|
23677
23985
|
var ToolbarController = class extends Controller {
|
|
23678
23986
|
static targets = ["control"];
|
|
23679
23987
|
static values = {
|
|
@@ -23702,7 +24010,7 @@ var ToolbarController = class extends Controller {
|
|
|
23702
24010
|
subtree: true,
|
|
23703
24011
|
childList: true,
|
|
23704
24012
|
attributes: true,
|
|
23705
|
-
attributeFilter:
|
|
24013
|
+
attributeFilter: STATE_ATTRIBUTES3
|
|
23706
24014
|
});
|
|
23707
24015
|
for (let fieldset = this.element.parentElement?.closest("fieldset") ?? null; fieldset; fieldset = fieldset.parentElement?.closest("fieldset") ?? null) {
|
|
23708
24016
|
observer.observe(fieldset, { attributes: true, attributeFilter: ["disabled"] });
|