foldkit-primitives 0.10.2 → 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.
@@ -545,6 +545,1049 @@ const pointedLinks = () => {
545
545
  })).pipe(Stream.changesWith((a, b) => toString(a) === toString(b)));
546
546
  };
547
547
  //#endregion
548
- export { Autofocus, ClipboardMessage, ClipboardReadMessage, DRAG_THRESHOLD, DragCancelled, DragDropped, DragPlace, DragStarted, DragZone, DraggedOver, EditAsked, EditableText, FocusScope, Focused, FollowTabStop, HideOutside, Input, InputMask, KeepInView, Measure, Move, MoveEnded, MoveStarted, Moved, PointerDrag, ScriptMessage, ScrollLock, ShareData, ShareMessage, TargetHovered, TargetPressed, Targets, TextCancelled, TextCommitted, TextEdited, applyMask, boxOf, copyText, keepScroll, loadScript, measured, pointedLinks, readText, share, tabbableWithin, targetOf, textOf, zoneOf };
548
+ //#region src/dom/fluid-hover.ts
549
+ /** The custom properties `FluidHover` writes on its container for a highlight named `name`. */
550
+ const fluidHovered = (name) => ({
551
+ x: `--fk-${name}-x`,
552
+ y: `--fk-${name}-y`,
553
+ w: `--fk-${name}-w`,
554
+ h: `--fk-${name}-h`,
555
+ /** `1` while an item is highlighted, `0` once the pointer and focus have left. */
556
+ shown: `--fk-${name}-shown`,
557
+ /**
558
+ * `0` for the frame the highlight appears in, then `1`: a highlight's
559
+ * movement scales its duration by it, so it appears on its item rather
560
+ * than sliding in from where it last was.
561
+ */
562
+ moving: `--fk-${name}-moving`
563
+ });
564
+ /**
565
+ * The attribute `FluidHover` sets on its container, to the highlight's name,
566
+ * while it runs: a stylist draws the highlight, and drops each item's own
567
+ * hover, only under it.
568
+ */
569
+ const FLUID_HOVER_ATTRIBUTE = "data-fluid-hover";
570
+ /** The attribute that names, inside an item, the control a press in a gap reaches. */
571
+ const FLUID_PRESS_ATTRIBUTE = "data-fluid-press";
572
+ /** The attribute on the item the highlight is shown on, for what CSS draws beside it. */
573
+ const FLUID_LIT_ATTRIBUTE = "data-fluid-lit";
574
+ const Axis = Schema.Literals([
575
+ "x",
576
+ "y",
577
+ "xy"
578
+ ]);
579
+ /** How far a point is from a rectangle along each axis: zero inside it. */
580
+ const gap = (low, high, at) => Math.max(low - at, 0, at - high);
581
+ const away = {
582
+ x: (dx, dy) => dx * 1e6 + dy,
583
+ y: (dx, dy) => dy * 1e6 + dx,
584
+ xy: Math.hypot
585
+ };
586
+ const distance = (rect, x, y, axis) => away[axis](gap(rect.left, rect.right, x), gap(rect.top, rect.bottom, y));
587
+ const isDisabled = (item) => item.matches("[aria-disabled=\"true\"], [data-disabled]:not([data-disabled=\"false\"]), :disabled");
588
+ /**
589
+ * One highlight that follows the pointer across a container's items, as a
590
+ * Mount, with no Message and no Model: which item the pointer is nearest is
591
+ * presentation, known to the DOM and to no transition.
592
+ *
593
+ * The item nearest the pointer is the one highlighted, so the pointer in a gap
594
+ * between two items still points at one, and the highlight moves from item to
595
+ * item instead of blinking off between them. On the `y` axis nearness is read
596
+ * vertically (a list), on `x` horizontally (a strip), on `xy` both (a grid). A
597
+ * disabled item (`aria-disabled="true"`, `data-disabled`, `:disabled`) is never
598
+ * highlighted. An item reached by the keyboard (`:focus-visible`) is
599
+ * highlighted too; a touch highlights nothing, since there is no pointer to
600
+ * follow before the press.
601
+ *
602
+ * The container gets `--fk-<name>-x`, `-y`, `-w` and `-h` in pixels, relative
603
+ * to its scroll box like `Measure`'s, and `-shown` and `-moving` (see
604
+ * `fluidHovered`); a view draws the highlight as an absolutely placed child
605
+ * that reads them. With `gapClick`, a click in a gap presses the highlighted
606
+ * item: its descendant marked `data-fluid-press`, or the item itself; with
607
+ * `gapReach` too, only a click within that many pixels of it, as a grid of
608
+ * cards with wide gutters wants (Fluid Functionalism's uses 16). A popup
609
+ * measured before it has a layout box keeps its last complete place, and
610
+ * tries again for three frames, rather than drawing the highlight at zero.
611
+ * The item the highlight is shown on carries `data-fluid-lit`, so a style
612
+ * can change what sits beside it (a table hides the lines around the lit
613
+ * row).
614
+ *
615
+ * `follows` names the item the highlight sits on while the pointer is not
616
+ * moving it: a menu marks the row its keys reach (`[data-active]`), and the
617
+ * highlight goes there whenever that mark moves, so the keys and the pointer
618
+ * light the same one. `from` names the item a fresh highlight grows out of,
619
+ * as a tab's hover leaves the chosen tab. A highlight always fades where it
620
+ * is: once the pointer leaves, nothing should move. It stays only for an item
621
+ * the keyboard reached (`:focus-visible`), not one a click focused.
622
+ */
623
+ const FluidHover = Mount.defineStream("FluidHover", {
624
+ messages: [Schema.Never],
625
+ args: {
626
+ /** The highlight's name, which names the custom properties. */
627
+ name: Schema.String,
628
+ /** The selector of an item within the container. */
629
+ item: Schema.String,
630
+ axis: Axis,
631
+ gapClick: Schema.Boolean,
632
+ /** How far from the highlighted item, in pixels, a click in a gap still presses it. */
633
+ gapReach: Schema.optional(Schema.Number),
634
+ /** The selector of the item the highlight sits on while the pointer is not moving it. */
635
+ follows: Schema.optional(Schema.String),
636
+ /** The selector of the item a fresh highlight grows out of. */
637
+ from: Schema.optional(Schema.String)
638
+ },
639
+ execute: ({ element, name, item: selector, axis, gapClick, gapReach, follows, from }) => Stream.callback(() => Effect.acquireRelease(Effect.sync(() => {
640
+ const container = element;
641
+ const property = fluidHovered(name);
642
+ let active = null;
643
+ let followed = null;
644
+ let pointer = null;
645
+ let frame;
646
+ const origin = () => from === void 0 ? null : container.querySelector(from);
647
+ let retries = 0;
648
+ const place = (at = active) => {
649
+ if (at === null) return;
650
+ const box = container.getBoundingClientRect();
651
+ const rect = at.getBoundingClientRect();
652
+ if (rect.width === 0 && rect.height === 0) {
653
+ if (retries < 3) {
654
+ retries += 1;
655
+ requestAnimationFrame(() => place(at));
656
+ }
657
+ return;
658
+ }
659
+ retries = 0;
660
+ const x = rect.left - box.left - container.clientLeft + container.scrollLeft;
661
+ const y = rect.top - box.top - container.clientTop + container.scrollTop;
662
+ container.style.setProperty(property.x, `${x}px`);
663
+ container.style.setProperty(property.y, `${y}px`);
664
+ container.style.setProperty(property.w, `${rect.width}px`);
665
+ container.style.setProperty(property.h, `${rect.height}px`);
666
+ };
667
+ /** Marks `item` as the lit one, and nothing else. */
668
+ const light = (item) => {
669
+ container.querySelectorAll(`[${FLUID_LIT_ATTRIBUTE}]`).forEach((each) => each !== item && each.removeAttribute("data-fluid-lit"));
670
+ item?.setAttribute(FLUID_LIT_ATTRIBUTE, "");
671
+ };
672
+ const show = (next) => {
673
+ const appearing = container.style.getPropertyValue(property.shown) !== "1";
674
+ active = next;
675
+ light(next);
676
+ const grows = appearing ? origin() : null;
677
+ if (appearing) container.style.setProperty(property.moving, "0");
678
+ if (grows === null) {
679
+ place();
680
+ container.style.setProperty(property.shown, "1");
681
+ } else {
682
+ place(grows);
683
+ requestAnimationFrame(() => {
684
+ if (active !== next) return;
685
+ place();
686
+ container.style.setProperty(property.shown, "1");
687
+ });
688
+ }
689
+ if (appearing) requestAnimationFrame(() => container.style.setProperty(property.moving, "1"));
690
+ };
691
+ const hide = () => {
692
+ container.style.setProperty(property.shown, "0");
693
+ light(null);
694
+ };
695
+ /** Whether the keyboard has an item focused, which the highlight then stays on. */
696
+ const keyed = () => {
697
+ const focused = document.activeElement;
698
+ return focused !== null && container.contains(focused) && focused.matches(":focus-visible");
699
+ };
700
+ const nearest = (x, y) => {
701
+ let best = null;
702
+ let bestDistance = Infinity;
703
+ for (const each of Array.from(container.querySelectorAll(selector))) {
704
+ if (isDisabled(each)) continue;
705
+ const far = distance(each.getBoundingClientRect(), x, y, axis);
706
+ if (far < bestDistance) {
707
+ best = each;
708
+ bestDistance = far;
709
+ }
710
+ }
711
+ return best;
712
+ };
713
+ const follow = () => {
714
+ frame = void 0;
715
+ if (pointer === null) return;
716
+ const next = nearest(pointer.x, pointer.y);
717
+ if (next === null) hide();
718
+ else if (next !== active || container.style.getPropertyValue(property.shown) !== "1") show(next);
719
+ };
720
+ const move = (event) => {
721
+ if (event.pointerType === "touch") return;
722
+ pointer = {
723
+ x: event.clientX,
724
+ y: event.clientY
725
+ };
726
+ if (frame === void 0) frame = requestAnimationFrame(follow);
727
+ };
728
+ const leave = () => {
729
+ pointer = null;
730
+ if (!keyed()) hide();
731
+ };
732
+ const focus = (event) => {
733
+ if (!(event.target instanceof Element) || !event.target.matches(":focus-visible")) return;
734
+ const owner = event.target.closest(selector);
735
+ if (owner !== null && container.contains(owner) && !isDisabled(owner)) show(owner);
736
+ };
737
+ const blur = (event) => {
738
+ const to = event.relatedTarget;
739
+ if (pointer === null && !(to instanceof Node && container.contains(to))) hide();
740
+ };
741
+ const click = (event) => {
742
+ if (!gapClick || active === null) return;
743
+ if (container.style.getPropertyValue(property.shown) !== "1") return;
744
+ const target = event.target;
745
+ if (!(target instanceof Element) || target.closest(selector) !== null) return;
746
+ const reach = distance(active.getBoundingClientRect(), event.clientX, event.clientY, "xy");
747
+ if (gapReach !== void 0 && reach > gapReach) return;
748
+ const press = active.querySelector(`[data-fluid-press]`) ?? active;
749
+ if (press instanceof HTMLElement) press.click();
750
+ };
751
+ const soon = () => {
752
+ if (active !== null) requestAnimationFrame(() => place());
753
+ };
754
+ /** The marked item moved: the highlight goes with it, or fades if it is gone. */
755
+ const marked = () => {
756
+ if (follows === void 0) return;
757
+ const next = container.querySelector(follows);
758
+ if (next === followed) return;
759
+ followed = next;
760
+ if (next !== null && !isDisabled(next)) show(next);
761
+ else if (pointer === null && !keyed()) hide();
762
+ };
763
+ const sizes = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(soon);
764
+ const marks = follows === void 0 ? void 0 : new MutationObserver(marked);
765
+ sizes?.observe(container);
766
+ marks?.observe(container, {
767
+ subtree: true,
768
+ childList: true,
769
+ attributes: true
770
+ });
771
+ container.addEventListener("pointermove", move, { passive: true });
772
+ container.addEventListener("pointerleave", leave);
773
+ container.addEventListener("focusin", focus);
774
+ container.addEventListener("focusout", blur);
775
+ container.addEventListener("click", click);
776
+ container.addEventListener("scroll", soon, { passive: true });
777
+ window.addEventListener("resize", soon);
778
+ container.style.setProperty(property.shown, "0");
779
+ container.setAttribute(FLUID_HOVER_ATTRIBUTE, name);
780
+ marked();
781
+ return () => {
782
+ if (frame !== void 0) cancelAnimationFrame(frame);
783
+ sizes?.disconnect();
784
+ marks?.disconnect();
785
+ container.removeAttribute(FLUID_HOVER_ATTRIBUTE);
786
+ light(null);
787
+ container.removeEventListener("pointermove", move);
788
+ container.removeEventListener("pointerleave", leave);
789
+ container.removeEventListener("focusin", focus);
790
+ container.removeEventListener("focusout", blur);
791
+ container.removeEventListener("click", click);
792
+ container.removeEventListener("scroll", soon);
793
+ window.removeEventListener("resize", soon);
794
+ };
795
+ }), (stop) => Effect.sync(stop)))
796
+ });
797
+ //#endregion
798
+ //#region src/dom/scroll-bars.ts
799
+ /**
800
+ * The attribute that marks a bar beside a scroller `ScrollBars` runs on, set
801
+ * to the axis it shows: `block` (the up-and-down bar) or `inline`.
802
+ */
803
+ const SCROLL_BAR_ATTRIBUTE = "data-scroll-bar";
804
+ /**
805
+ * The attribute `ScrollBars` sets on its scroller while it draws the bars, so a
806
+ * style hides the native bar only then: with no Mount running (a coarse
807
+ * pointer, a page not yet hydrated) the browser's bar stays.
808
+ */
809
+ const SCROLL_OVERLAID_ATTRIBUTE = "data-scroll-overlaid";
810
+ /**
811
+ * The custom properties `ScrollBars` writes on each bar, in pixels, for its
812
+ * thumb to read: how far along the bar the thumb starts, and how long it is.
813
+ * Physical: an inline bar's offset is from its left edge in either direction.
814
+ */
815
+ const scrollThumb = {
816
+ offset: "--fk-scroll-thumb-offset",
817
+ size: "--fk-scroll-thumb-size"
818
+ };
819
+ /** The shortest a thumb is drawn, however long the content, so it stays a target. */
820
+ const SHORTEST = 20;
821
+ const FINE_POINTER = "(hover: hover) and (pointer: fine)";
822
+ /**
823
+ * An overlay scrollbar as a Mount, with no Message and no Model: where a
824
+ * scroller is scrolled is the browser's, and the bar only shows it.
825
+ *
826
+ * It runs on the scroller (or on an element holding it, named by `scroller`,
827
+ * when the scroller carries another Mount) and drives the bars drawn beside
828
+ * it: the scroller's parent's children marked `data-scroll-bar` (`block` or
829
+ * `inline`), which a style lays over the scroller's edge. Each bar gets `data-scrollable` while its axis has
830
+ * more to scroll, `data-shown` while the scroller scrolls or the pointer moves
831
+ * over it, until `idle` milliseconds pass with neither (or longer, while the
832
+ * pointer rests on a bar), and `data-dragging` while its thumb is dragged; and
833
+ * `--fk-scroll-thumb-offset` and `-size` (see `scrollThumb`). Pressing a bar
834
+ * off its thumb moves the thumb there, then drags it.
835
+ *
836
+ * It does nothing where the primary pointer is coarse or cannot hover, decided
837
+ * when it mounts: touch keeps the browser's bar and its physics. Otherwise the
838
+ * scroller gets `data-scroll-overlaid`, for a style to hide the native bar.
839
+ * How the bar looks, rests, widens and fades is the style's.
840
+ */
841
+ const ScrollBars = Mount.defineStream("ScrollBars", {
842
+ messages: [Schema.Never],
843
+ args: {
844
+ /** How long, in milliseconds, the bars stay after the last scroll or move. Default 600. */
845
+ idle: Schema.optional(Schema.Number),
846
+ /**
847
+ * The scroller, as a selector within the element, when it cannot hold this
848
+ * Mount itself (another component's, with a Mount of its own); looked for
849
+ * once, when it mounts. Default: the element is the scroller.
850
+ */
851
+ scroller: Schema.optional(Schema.String)
852
+ },
853
+ execute: ({ element, idle = 600, scroller: within }) => Stream.callback(() => Effect.acquireRelease(Effect.sync(() => {
854
+ if (typeof matchMedia !== "function" || !matchMedia(FINE_POINTER).matches) return () => {};
855
+ const found = within === void 0 ? element : element.querySelector(within);
856
+ if (!(found instanceof HTMLElement)) return () => {};
857
+ const scroller = found;
858
+ const area = scroller.parentElement;
859
+ if (area === null) return () => {};
860
+ const isBar = (child) => child instanceof HTMLElement && child.hasAttribute("data-scroll-bar");
861
+ /** Looked for at each use, so a redraw that replaced a bar is followed. */
862
+ const bars = () => Array.from(area.children).filter(isBar);
863
+ const axisOf = (bar) => bar.getAttribute("data-scroll-bar") === "inline" ? "inline" : "block";
864
+ /** The scroller along one axis, with the start measured from the top or the left. */
865
+ const extent = (axis) => {
866
+ if (axis === "block") return {
867
+ view: scroller.clientHeight,
868
+ content: scroller.scrollHeight,
869
+ start: scroller.scrollTop
870
+ };
871
+ const room = scroller.scrollWidth - scroller.clientWidth;
872
+ const rtl = getComputedStyle(scroller).direction === "rtl";
873
+ return {
874
+ view: scroller.clientWidth,
875
+ content: scroller.scrollWidth,
876
+ start: rtl ? room + scroller.scrollLeft : scroller.scrollLeft
877
+ };
878
+ };
879
+ const scrollTo = (axis, start) => {
880
+ if (axis === "block") {
881
+ scroller.scrollTop = start;
882
+ return;
883
+ }
884
+ const room = scroller.scrollWidth - scroller.clientWidth;
885
+ const rtl = getComputedStyle(scroller).direction === "rtl";
886
+ scroller.scrollLeft = rtl ? start - room : start;
887
+ };
888
+ const trackOf = (bar, axis) => axis === "block" ? bar.clientHeight : bar.clientWidth;
889
+ /** Where the thumb is along its bar, or nothing while the axis has nothing to scroll. */
890
+ const thumbOf = (bar, axis) => {
891
+ const { view, content, start } = extent(axis);
892
+ const track = trackOf(bar, axis);
893
+ if (content - view < 1 || track <= 0) return null;
894
+ const size = Math.min(track, Math.max(SHORTEST, track * view / content));
895
+ return {
896
+ track,
897
+ size,
898
+ offset: (track - size) * start / (content - view),
899
+ room: content - view
900
+ };
901
+ };
902
+ const set = (bar, name, value) => {
903
+ if (bar.style.getPropertyValue(name) !== value) bar.style.setProperty(name, value);
904
+ };
905
+ const mark = (bar, name, on) => {
906
+ if (bar.hasAttribute(name) !== on) bar.toggleAttribute(name, on);
907
+ };
908
+ let frame;
909
+ const update = () => {
910
+ if (frame !== void 0) cancelAnimationFrame(frame);
911
+ frame = void 0;
912
+ for (const bar of bars()) {
913
+ const axis = axisOf(bar);
914
+ const { view, content } = extent(axis);
915
+ mark(bar, "data-scrollable", content - view >= 1);
916
+ const thumb = thumbOf(bar, axis);
917
+ if (thumb === null) continue;
918
+ set(bar, scrollThumb.offset, `${thumb.offset}px`);
919
+ set(bar, scrollThumb.size, `${thumb.size}px`);
920
+ }
921
+ };
922
+ const soon = () => {
923
+ if (frame === void 0) frame = requestAnimationFrame(update);
924
+ };
925
+ let hiding;
926
+ let resting = null;
927
+ /** Ends the drag under way, if one is. */
928
+ let dragging = null;
929
+ const hide = () => {
930
+ hiding = void 0;
931
+ if (resting !== null || dragging !== null) return;
932
+ for (const bar of bars()) mark(bar, "data-shown", false);
933
+ };
934
+ const show = () => {
935
+ for (const bar of bars()) mark(bar, "data-shown", true);
936
+ if (hiding !== void 0) clearTimeout(hiding);
937
+ hiding = setTimeout(hide, idle);
938
+ };
939
+ const scrolled = () => {
940
+ update();
941
+ show();
942
+ };
943
+ const moved = (event) => {
944
+ if (event.pointerType !== "touch") show();
945
+ };
946
+ const entered = (event) => {
947
+ if (event.pointerType === "touch") return;
948
+ area.addEventListener("pointermove", moved, { passive: true });
949
+ show();
950
+ };
951
+ const left = () => {
952
+ area.removeEventListener("pointermove", moved);
953
+ resting = null;
954
+ if (hiding === void 0) hiding = setTimeout(hide, idle);
955
+ };
956
+ const over = (event) => {
957
+ const target = event.target;
958
+ resting = target instanceof Element ? bars().find((bar) => bar.contains(target)) ?? null : null;
959
+ };
960
+ const pressed = (event) => {
961
+ if (event.button !== 0 || event.pointerType === "touch" || dragging !== null) return;
962
+ const target = event.target;
963
+ if (!(target instanceof Element)) return;
964
+ const bar = bars().find((each) => each.contains(target));
965
+ if (bar === void 0) return;
966
+ const axis = axisOf(bar);
967
+ const thumb = thumbOf(bar, axis);
968
+ if (thumb === null) return;
969
+ event.preventDefault();
970
+ const along = (at) => {
971
+ const box = bar.getBoundingClientRect();
972
+ return axis === "block" ? at.clientY - box.top - bar.clientTop : at.clientX - box.left - bar.clientLeft;
973
+ };
974
+ const at = along(event);
975
+ const onThumb = at >= thumb.offset && at <= thumb.offset + thumb.size;
976
+ const grab = onThumb ? at - thumb.offset : thumb.size / 2;
977
+ const follow = (to) => {
978
+ const current = thumbOf(bar, axis);
979
+ if (current === null) return;
980
+ const offset = Math.min(Math.max(along(to) - grab, 0), current.track - current.size);
981
+ scrollTo(axis, offset / (current.track - current.size) * current.room);
982
+ };
983
+ const id = event.pointerId;
984
+ const move = (to) => {
985
+ if (to.pointerId === id) follow(to);
986
+ };
987
+ const end = (to) => {
988
+ if (to.pointerId === id) stop();
989
+ };
990
+ const owner = bar.ownerDocument;
991
+ const stop = () => {
992
+ owner.removeEventListener("pointermove", move);
993
+ owner.removeEventListener("pointerup", end);
994
+ owner.removeEventListener("pointercancel", end);
995
+ mark(bar, "data-dragging", false);
996
+ dragging = null;
997
+ show();
998
+ };
999
+ owner.addEventListener("pointermove", move);
1000
+ owner.addEventListener("pointerup", end);
1001
+ owner.addEventListener("pointercancel", end);
1002
+ mark(bar, "data-dragging", true);
1003
+ dragging = stop;
1004
+ show();
1005
+ if (!onThumb) follow(event);
1006
+ };
1007
+ const sizes = new ResizeObserver(soon);
1008
+ const changes = new MutationObserver((records) => {
1009
+ for (const record of records) {
1010
+ for (const node of Array.from(record.removedNodes)) if (node instanceof Element) sizes.unobserve(node);
1011
+ for (const node of Array.from(record.addedNodes)) if (node instanceof Element && (record.target === scroller || isBar(node))) sizes.observe(node);
1012
+ }
1013
+ soon();
1014
+ });
1015
+ sizes.observe(scroller);
1016
+ for (const child of Array.from(scroller.children)) sizes.observe(child);
1017
+ for (const bar of bars()) sizes.observe(bar);
1018
+ changes.observe(scroller, { childList: true });
1019
+ changes.observe(area, { childList: true });
1020
+ scroller.addEventListener("scroll", scrolled, { passive: true });
1021
+ area.addEventListener("pointerenter", entered);
1022
+ area.addEventListener("pointerleave", left);
1023
+ area.addEventListener("pointerover", over);
1024
+ area.addEventListener("pointerdown", pressed);
1025
+ scroller.setAttribute(SCROLL_OVERLAID_ATTRIBUTE, "");
1026
+ update();
1027
+ return () => {
1028
+ dragging?.();
1029
+ if (frame !== void 0) cancelAnimationFrame(frame);
1030
+ if (hiding !== void 0) clearTimeout(hiding);
1031
+ sizes.disconnect();
1032
+ changes.disconnect();
1033
+ scroller.removeEventListener("scroll", scrolled);
1034
+ area.removeEventListener("pointerenter", entered);
1035
+ area.removeEventListener("pointerleave", left);
1036
+ area.removeEventListener("pointerover", over);
1037
+ area.removeEventListener("pointerdown", pressed);
1038
+ area.removeEventListener("pointermove", moved);
1039
+ scroller.removeAttribute(SCROLL_OVERLAID_ATTRIBUTE);
1040
+ for (const bar of bars()) {
1041
+ for (const name of [
1042
+ "data-scrollable",
1043
+ "data-shown",
1044
+ "data-dragging"
1045
+ ]) bar.removeAttribute(name);
1046
+ bar.style.removeProperty(scrollThumb.offset);
1047
+ bar.style.removeProperty(scrollThumb.size);
1048
+ }
1049
+ };
1050
+ }), (stop) => Effect.sync(stop)))
1051
+ });
1052
+ //#endregion
1053
+ //#region src/dom/swipe.ts
1054
+ const SwipeStarted = Schema.TaggedStruct("SwipeStarted", {
1055
+ pointerId: Schema.Number,
1056
+ pointerType: Schema.String,
1057
+ /** The border box of what `measure` names (the element itself by default), as the swipe starts. */
1058
+ width: Schema.Number,
1059
+ height: Schema.Number
1060
+ });
1061
+ const Swiped = Schema.TaggedStruct("Swiped", {
1062
+ pointerId: Schema.Number,
1063
+ /** From where the pointer went down, in CSS pixels. */
1064
+ deltaX: Schema.Number,
1065
+ deltaY: Schema.Number
1066
+ });
1067
+ const SwipeEnded = Schema.TaggedStruct("SwipeEnded", {
1068
+ pointerId: Schema.Number,
1069
+ /** From where the pointer went down to where it came up, in CSS pixels. */
1070
+ deltaX: Schema.Number,
1071
+ deltaY: Schema.Number,
1072
+ /**
1073
+ * How fast the pointer was moving as it came up, in pixels a millisecond,
1074
+ * over the last `SWIPE_WINDOW` milliseconds: 0 when it rested there first.
1075
+ */
1076
+ velocityX: Schema.Number,
1077
+ velocityY: Schema.Number,
1078
+ /** `false` when the pointer was cancelled or capture was lost. */
1079
+ completed: Schema.Boolean
1080
+ });
1081
+ /** How far back, in milliseconds, the release velocity looks. */
1082
+ const SWIPE_WINDOW = 100;
1083
+ /**
1084
+ * The velocity of the samples within `SWIPE_WINDOW` of the last: the way
1085
+ * from the oldest of them to the last over the time between. A pointer that
1086
+ * came to rest before the last sample has no recent way to measure, so 0.
1087
+ */
1088
+ const velocityOf = (samples) => {
1089
+ const last = samples.at(-1);
1090
+ if (last === void 0) return {
1091
+ x: 0,
1092
+ y: 0
1093
+ };
1094
+ const first = samples.find((sample) => last.at - sample.at <= 100) ?? last;
1095
+ const elapsed = last.at - first.at;
1096
+ return elapsed <= 0 ? {
1097
+ x: 0,
1098
+ y: 0
1099
+ } : {
1100
+ x: (last.x - first.x) / elapsed,
1101
+ y: (last.y - first.y) / elapsed
1102
+ };
1103
+ };
1104
+ /**
1105
+ * `Move` with the speed of its release, for a gesture that a flick ends
1106
+ * (a sheet dragged to dismiss, a card swiped away): a primary-button pointer
1107
+ * down captures the pointer, each move reports the delta from where it
1108
+ * started, and up reports the last delta with the velocity over the last
1109
+ * `SWIPE_WINDOW` milliseconds, by the events' own time stamps. Cancel or
1110
+ * lost capture ends it incomplete. The start reports the size of what is
1111
+ * moved, so a distance can be read as a share of it. What a delta or a speed
1112
+ * means is the parent's `update`. Capture is released with the Mount.
1113
+ */
1114
+ const Swipe = Mount.defineStream("Swipe", {
1115
+ messages: [
1116
+ SwipeStarted,
1117
+ Swiped,
1118
+ SwipeEnded
1119
+ ],
1120
+ args: {
1121
+ /**
1122
+ * The nearest ancestor matching this selector is measured as the swipe
1123
+ * starts, for what the swipe moves (a sheet's panel, dragged by its
1124
+ * handle). Default: the element itself.
1125
+ */
1126
+ measure: Schema.optional(Schema.String) },
1127
+ execute: ({ element, measure }) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
1128
+ const target = element;
1129
+ let active = null;
1130
+ const offer = (fact) => Queue.offerUnsafe(queue, fact);
1131
+ const sampleOf = (event) => ({
1132
+ at: event.timeStamp,
1133
+ x: event.clientX ?? 0,
1134
+ y: event.clientY ?? 0
1135
+ });
1136
+ const end = (event, completed) => {
1137
+ const pointerId = event.pointerId ?? 0;
1138
+ if (active === null || active.pointerId !== pointerId) return;
1139
+ const { samples } = active;
1140
+ active = null;
1141
+ try {
1142
+ target.releasePointerCapture?.(pointerId);
1143
+ } catch {}
1144
+ const last = completed ? sampleOf(event) : samples.at(-1) ?? sampleOf(event);
1145
+ const all = completed ? [...samples, last] : samples;
1146
+ const start = all[0] ?? last;
1147
+ const velocity = completed ? velocityOf(all) : {
1148
+ x: 0,
1149
+ y: 0
1150
+ };
1151
+ offer(SwipeEnded.make({
1152
+ pointerId,
1153
+ deltaX: last.x - start.x,
1154
+ deltaY: last.y - start.y,
1155
+ velocityX: velocity.x,
1156
+ velocityY: velocity.y,
1157
+ completed
1158
+ }));
1159
+ };
1160
+ const listeners = [
1161
+ ["pointerdown", (event) => {
1162
+ const pointer = event;
1163
+ if ((pointer.button ?? 0) !== 0 || active !== null) return;
1164
+ const pointerId = pointer.pointerId ?? 0;
1165
+ active = {
1166
+ pointerId,
1167
+ samples: [sampleOf(pointer)]
1168
+ };
1169
+ try {
1170
+ target.setPointerCapture?.(pointerId);
1171
+ } catch {}
1172
+ const box = (measure === void 0 ? element : element.closest(measure) ?? element).getBoundingClientRect();
1173
+ offer(SwipeStarted.make({
1174
+ pointerId,
1175
+ pointerType: pointer.pointerType ?? "mouse",
1176
+ width: box.width,
1177
+ height: box.height
1178
+ }));
1179
+ }],
1180
+ ["pointermove", (event) => {
1181
+ const pointer = event;
1182
+ const pointerId = pointer.pointerId ?? 0;
1183
+ if (active === null || active.pointerId !== pointerId) return;
1184
+ const sample = sampleOf(pointer);
1185
+ const start = active.samples[0] ?? sample;
1186
+ active.samples = [
1187
+ start,
1188
+ ...active.samples.slice(1).filter((each) => sample.at - each.at <= 100),
1189
+ sample
1190
+ ];
1191
+ offer(Swiped.make({
1192
+ pointerId,
1193
+ deltaX: sample.x - start.x,
1194
+ deltaY: sample.y - start.y
1195
+ }));
1196
+ }],
1197
+ ["pointerup", (event) => end(event, true)],
1198
+ ["pointercancel", (event) => end(event, false)],
1199
+ ["lostpointercapture", (event) => end(event, false)]
1200
+ ];
1201
+ for (const [type, listener] of listeners) element.addEventListener(type, listener);
1202
+ return {
1203
+ listeners,
1204
+ current: () => active
1205
+ };
1206
+ }), ({ listeners, current }) => Effect.sync(() => {
1207
+ for (const [type, listener] of listeners) element.removeEventListener(type, listener);
1208
+ const pointer = current();
1209
+ if (pointer !== null) try {
1210
+ element.releasePointerCapture?.(pointer.pointerId);
1211
+ } catch {}
1212
+ })))
1213
+ });
1214
+ //#endregion
1215
+ //#region src/dom/scroll-settle.ts
1216
+ const ScrollSettled = Schema.TaggedStruct("ScrollSettled", {
1217
+ /** The item whose start lies nearest the scroller's start, from 0. */
1218
+ index: Schema.Number });
1219
+ /** How long a scroll must be still to count as settled, where `scrollend` is missing. */
1220
+ const SETTLE_QUIET = 120;
1221
+ /**
1222
+ * Hears a scroller come to rest, as a Mount: after a swipe, a fling, a
1223
+ * trackpad or a scroll a Command made, it reports which of its items starts
1224
+ * nearest its own start (inside its `scroll-padding`), the start being the
1225
+ * right edge right to left. A snapping carousel reads it as the slide shown.
1226
+ *
1227
+ * At insert it first scrolls, at once, to item `start` (the Mount's args are
1228
+ * read only then, so this is where a redrawn or restored scroller begins),
1229
+ * and reports where it lies, since a scroller drawn already at rest sends no
1230
+ * `scrollend`. It listens for `scrollend` where the browser has it, and
1231
+ * otherwise for a scroll that is quiet for `SETTLE_QUIET` milliseconds.
1232
+ */
1233
+ const ScrollSettle = Mount.defineStream("ScrollSettle", {
1234
+ messages: [ScrollSettled],
1235
+ args: {
1236
+ /** Which children are the items; default every child. */
1237
+ item: Schema.optional(Schema.String),
1238
+ /** Default `inline`, the row a carousel scrolls along. */
1239
+ axis: Schema.optional(Schema.Literals(["inline", "block"])),
1240
+ /** The item to scroll to at insert; default the first, which needs no scroll. */
1241
+ start: Schema.optional(Schema.Number)
1242
+ },
1243
+ execute: ({ element, item, axis = "inline", start = 0 }) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
1244
+ const items = () => Array.from(item === void 0 ? element.children : element.querySelectorAll(`:scope > ${item}`));
1245
+ /** How far `child`'s start lies past the scroller's start, along the axis. */
1246
+ const offsetOf = (child) => {
1247
+ const style = getComputedStyle(element);
1248
+ const box = element.getBoundingClientRect();
1249
+ const own = child.getBoundingClientRect();
1250
+ if (axis === "block") return own.top - box.top - (parseFloat(style.scrollPaddingBlockStart) || 0);
1251
+ const padding = parseFloat(style.scrollPaddingInlineStart) || 0;
1252
+ return style.direction === "rtl" ? box.right - padding - own.right : own.left - box.left - padding;
1253
+ };
1254
+ const report = () => {
1255
+ let nearest = -1;
1256
+ let distance = Infinity;
1257
+ items().forEach((child, index) => {
1258
+ const away = Math.abs(offsetOf(child));
1259
+ if (away < distance) {
1260
+ nearest = index;
1261
+ distance = away;
1262
+ }
1263
+ });
1264
+ if (nearest >= 0) Queue.offerUnsafe(queue, ScrollSettled.make({ index: nearest }));
1265
+ };
1266
+ const first = items()[start];
1267
+ const before = element.scrollLeft + element.scrollTop;
1268
+ if (start > 0 && first !== void 0) {
1269
+ const by = offsetOf(first);
1270
+ const rtl = axis === "inline" && getComputedStyle(element).direction === "rtl";
1271
+ element.scrollTo(axis === "block" ? {
1272
+ top: element.scrollTop + by,
1273
+ behavior: "instant"
1274
+ } : {
1275
+ left: element.scrollLeft + (rtl ? -by : by),
1276
+ behavior: "instant"
1277
+ });
1278
+ }
1279
+ if (element.scrollLeft + element.scrollTop === before) report();
1280
+ let quiet;
1281
+ const hasEnd = "onscrollend" in element;
1282
+ const listener = hasEnd ? report : () => {
1283
+ clearTimeout(quiet);
1284
+ quiet = setTimeout(report, 120);
1285
+ };
1286
+ const type = hasEnd ? "scrollend" : "scroll";
1287
+ element.addEventListener(type, listener);
1288
+ return () => {
1289
+ clearTimeout(quiet);
1290
+ element.removeEventListener(type, listener);
1291
+ };
1292
+ }), (detach) => Effect.sync(detach)))
1293
+ });
1294
+ //#endregion
1295
+ //#region src/dom/pointer-position.ts
1296
+ /**
1297
+ * A place in an element's padding box, in CSS pixels from its top left
1298
+ * corner, and the pointer's pressure there (0 to 1; a mouse reads 0.5 while
1299
+ * a button is down). A captured pointer can be outside the box, so `x` and
1300
+ * `y` can be negative or past its size.
1301
+ */
1302
+ const PointerPoint = Schema.Struct({
1303
+ x: Schema.Number,
1304
+ y: Schema.Number,
1305
+ pressure: Schema.Number
1306
+ });
1307
+ /** The padding box's size at the event, in CSS pixels. */
1308
+ const Size = {
1309
+ width: Schema.Number,
1310
+ height: Schema.Number
1311
+ };
1312
+ const PointerPressed = Schema.TaggedStruct("PointerPressed", {
1313
+ ...Size,
1314
+ point: PointerPoint,
1315
+ /** `mouse`, `pen` or `touch`, as the browser names it. */
1316
+ pointerType: Schema.String
1317
+ });
1318
+ const PointerMoved = Schema.TaggedStruct("PointerMoved", {
1319
+ ...Size,
1320
+ /** Where the pointer is now. */
1321
+ point: PointerPoint,
1322
+ /**
1323
+ * Every place it passed since the last move, ending with `point`: the
1324
+ * browser's coalesced events, so a fast stroke keeps its curve. Just
1325
+ * `point` where the browser coalesces none.
1326
+ */
1327
+ points: Schema.Array(PointerPoint)
1328
+ });
1329
+ const PointerReleased = Schema.TaggedStruct("PointerReleased", {
1330
+ /** `false` when the pointer was cancelled, lost its capture, or pressed again unreleased. */
1331
+ completed: Schema.Boolean });
1332
+ /**
1333
+ * The padding box of an element whose border box is `rect`: inside its
1334
+ * border, padding included. An absolutely positioned child (a thumb, a
1335
+ * stroke) is laid out against it, so a place read here is where that child
1336
+ * sits, whatever padding a stylist adds.
1337
+ */
1338
+ const paddingBoxOf = (layout) => {
1339
+ const { rect, border } = layout;
1340
+ return {
1341
+ left: rect.left + border.left,
1342
+ top: rect.top + border.top,
1343
+ width: Math.max(0, rect.width - border.left - border.right),
1344
+ height: Math.max(0, rect.height - border.top - border.bottom)
1345
+ };
1346
+ };
1347
+ const borderOf = (style) => ({
1348
+ top: parseFloat(style.borderTopWidth),
1349
+ right: parseFloat(style.borderRightWidth),
1350
+ bottom: parseFloat(style.borderBottomWidth),
1351
+ left: parseFloat(style.borderLeftWidth)
1352
+ });
1353
+ /**
1354
+ * An element's padding box now, in the viewport, as `PointerPosition`
1355
+ * measures it: for a size kept beside its places, which a `ResizeObserver`'s
1356
+ * content box does not match once the element has padding. Read from the
1357
+ * bounding rectangle and the computed border, since `clientWidth` and
1358
+ * `clientLeft` round to whole pixels.
1359
+ */
1360
+ const measurePaddingBox = (element) => paddingBoxOf({
1361
+ rect: element.getBoundingClientRect(),
1362
+ border: borderOf(getComputedStyle(element))
1363
+ });
1364
+ const pointIn = (box, event) => ({
1365
+ x: event.clientX - box.left,
1366
+ y: event.clientY - box.top,
1367
+ pressure: event.pressure
1368
+ });
1369
+ /**
1370
+ * Where one pointer is inside an element, as a Mount, for a surface the
1371
+ * pointer draws on or points into (a color area, a dial, a signature pad): a
1372
+ * primary press, each move until the release, and the release, each with
1373
+ * the place in the element's padding box and the box's size. The box is
1374
+ * measured at each event, so an element that moved or resized is followed.
1375
+ * Coordinates are raw, left to right and top down whatever the direction:
1376
+ * what a place means (a share, an angle, a reading direction) is the parent's.
1377
+ *
1378
+ * The pointer is captured from press to release, so a drag that leaves the
1379
+ * element is still heard, and the moves and the release are listened to only
1380
+ * meanwhile. A press keeps the browser's own focus and text selection away,
1381
+ * and a second pointer while one is down is ignored.
1382
+ */
1383
+ const PointerPosition = Mount.defineStream("PointerPosition", {
1384
+ messages: [
1385
+ PointerPressed,
1386
+ PointerMoved,
1387
+ PointerReleased
1388
+ ],
1389
+ args: {
1390
+ /**
1391
+ * A descendant to focus on a press, found by this selector (a thumb), so
1392
+ * the keys go on from where the pointer left it.
1393
+ */
1394
+ focus: Schema.optionalKey(Schema.String) },
1395
+ execute: ({ element, focus }) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
1396
+ const offer = (fact) => Queue.offerUnsafe(queue, fact);
1397
+ let held = Option.none();
1398
+ const ours = (event) => event instanceof PointerEvent && Option.contains(held, event.pointerId);
1399
+ const release = () => {
1400
+ if (Option.isNone(held)) return;
1401
+ const pointerId = held.value;
1402
+ held = Option.none();
1403
+ for (const [type, listener] of during) element.removeEventListener(type, listener);
1404
+ try {
1405
+ element.releasePointerCapture(pointerId);
1406
+ } catch {}
1407
+ };
1408
+ const end = (completed) => (event) => {
1409
+ if (!ours(event)) return;
1410
+ release();
1411
+ offer(PointerReleased.make({ completed }));
1412
+ };
1413
+ const move = (event) => {
1414
+ if (!ours(event)) return;
1415
+ const box = measurePaddingBox(element);
1416
+ const point = pointIn(box, event);
1417
+ const coalesced = event.getCoalescedEvents?.() ?? [];
1418
+ offer(PointerMoved.make({
1419
+ width: box.width,
1420
+ height: box.height,
1421
+ point,
1422
+ points: coalesced.length > 0 ? coalesced.map((one) => pointIn(box, one)) : [point]
1423
+ }));
1424
+ };
1425
+ const during = [
1426
+ ["pointermove", move],
1427
+ ["pointerup", end(true)],
1428
+ ["pointercancel", end(false)],
1429
+ ["lostpointercapture", end(false)]
1430
+ ];
1431
+ const down = (event) => {
1432
+ if (!(event instanceof PointerEvent) || event.button !== 0) return;
1433
+ if (Option.isSome(held)) {
1434
+ if (!ours(event)) return;
1435
+ release();
1436
+ offer(PointerReleased.make({ completed: false }));
1437
+ }
1438
+ held = Option.some(event.pointerId);
1439
+ for (const [type, listener] of during) element.addEventListener(type, listener);
1440
+ try {
1441
+ element.setPointerCapture(event.pointerId);
1442
+ } catch {}
1443
+ event.preventDefault();
1444
+ if (focus !== void 0) {
1445
+ const into = element.querySelector(focus);
1446
+ if (into instanceof HTMLElement) into.focus();
1447
+ }
1448
+ const box = measurePaddingBox(element);
1449
+ offer(PointerPressed.make({
1450
+ width: box.width,
1451
+ height: box.height,
1452
+ point: pointIn(box, event),
1453
+ pointerType: event.pointerType
1454
+ }));
1455
+ };
1456
+ element.addEventListener("pointerdown", down);
1457
+ return () => {
1458
+ element.removeEventListener("pointerdown", down);
1459
+ release();
1460
+ };
1461
+ }), (stop) => Effect.sync(stop)))
1462
+ });
1463
+ //#endregion
1464
+ //#region src/dom/track-box.ts
1465
+ /** The attribute on the mounted element naming the element `TrackBox` follows, by id. */
1466
+ const TRACK_TARGET_ATTRIBUTE = "data-target";
1467
+ /** The custom properties `TrackBox` writes for `name`, as `measured` names `Measure`'s. */
1468
+ const trackedBox = (name) => ({
1469
+ x: `--fk-${name}-x`,
1470
+ y: `--fk-${name}-y`,
1471
+ w: `--fk-${name}-w`,
1472
+ h: `--fk-${name}-h`,
1473
+ radius: `--fk-${name}-radius`
1474
+ });
1475
+ /** Sent as the followed element changes: its id, and whether it is on the page. */
1476
+ const TrackedTarget = Schema.TaggedStruct("TrackedTarget", {
1477
+ target: Schema.String,
1478
+ found: Schema.Boolean
1479
+ });
1480
+ /**
1481
+ * The radius a box of `width` by `height` draws for a computed
1482
+ * `border-top-left-radius` of `value` (`8px`, `50%`, `8px 4px`): its
1483
+ * horizontal length in pixels, a share of the width for a percentage, and
1484
+ * never more than half the shorter side, where the browser stops it.
1485
+ */
1486
+ const cornerRadius = (value, width, height) => {
1487
+ const first = value.trim().split(/\s+/)[0] ?? "";
1488
+ const length = parseFloat(first);
1489
+ if (!Number.isFinite(length)) return 0;
1490
+ const pixels = first.endsWith("%") ? length * width / 100 : length;
1491
+ return Math.max(0, Math.min(pixels, width / 2, height / 2));
1492
+ };
1493
+ /**
1494
+ * Follows another element's box, as a Mount: the element whose id is the
1495
+ * mounted element's `data-target` (`TRACK_TARGET_ATTRIBUTE`). Its box in
1496
+ * viewport pixels is written on the mounted element as `--fk-<name>-x`,
1497
+ * `-y`, `-w` and `-h`, and with `radius`, its top left corner's radius as
1498
+ * `--fk-<name>-radius` (`cornerRadius`), so a fixed overlay drawn from them
1499
+ * (a tour's cutout, a coach mark's ring) sits over it. While nothing is
1500
+ * followed they are removed, so a stylist's fallbacks apply.
1501
+ *
1502
+ * The box is read again on a scroll anywhere (heard on the way down, since
1503
+ * `scroll` does not bubble), a window resize, the followed element's own
1504
+ * resize, and any change to the document's elements, which is also how a
1505
+ * target drawn anew under the same id is found. `data-target` is read again
1506
+ * whenever it changes, so one element can glide from target to target
1507
+ * without being drawn again, which a Mount's args, read once, could not do.
1508
+ * What moves the target by any other way (a stylesheet added) is seen at the
1509
+ * next of these.
1510
+ *
1511
+ * Whether a target is on the page is the parent's to act on (a tour shows
1512
+ * the step in the middle instead), so each change of the target or of
1513
+ * whether it is found sends `TrackedTarget`, starting with the first one.
1514
+ * Since it sends a Message, a component may draw it by default: a Scene test
1515
+ * resolves it with what the parent maps it to. An empty `data-target` follows
1516
+ * nothing and sends nothing.
1517
+ */
1518
+ const TrackBox = Mount.defineStream("TrackBox", {
1519
+ messages: [TrackedTarget],
1520
+ args: {
1521
+ /** The custom properties' prefix: `tour` writes `--fk-tour-x`. */
1522
+ name: Schema.String,
1523
+ /** Whether to write the target's corner radius too; it reads the target's computed style. */
1524
+ radius: Schema.optional(Schema.Boolean)
1525
+ },
1526
+ execute: ({ element, name, radius = false }) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
1527
+ const document = element.ownerDocument;
1528
+ const view = document.defaultView;
1529
+ const properties = trackedBox(name);
1530
+ const style = element instanceof HTMLElement ? element.style : void 0;
1531
+ let target = null;
1532
+ let told;
1533
+ const place = () => {
1534
+ if (style === void 0) return;
1535
+ if (target === null) {
1536
+ for (const property of Object.values(properties)) style.removeProperty(property);
1537
+ return;
1538
+ }
1539
+ const box = target.getBoundingClientRect();
1540
+ style.setProperty(properties.x, `${box.left}px`);
1541
+ style.setProperty(properties.y, `${box.top}px`);
1542
+ style.setProperty(properties.w, `${box.width}px`);
1543
+ style.setProperty(properties.h, `${box.height}px`);
1544
+ if (radius) {
1545
+ const corner = getComputedStyle(target).borderTopLeftRadius;
1546
+ style.setProperty(properties.radius, `${cornerRadius(corner, box.width, box.height)}px`);
1547
+ }
1548
+ };
1549
+ const follow = () => {
1550
+ const id = element.getAttribute("data-target") ?? "";
1551
+ const found = id === "" ? null : document.getElementById(id);
1552
+ if (found !== target) {
1553
+ if (target !== null) resized.unobserve(target);
1554
+ if (found !== null) resized.observe(found);
1555
+ target = found;
1556
+ }
1557
+ place();
1558
+ if (id !== "" && (told?.target !== id || told.found !== (found !== null))) {
1559
+ told = TrackedTarget.make({
1560
+ target: id,
1561
+ found: found !== null
1562
+ });
1563
+ Queue.offerUnsafe(queue, told);
1564
+ }
1565
+ };
1566
+ const resized = new ResizeObserver(place);
1567
+ const changed = new MutationObserver(follow);
1568
+ changed.observe(element, {
1569
+ attributes: true,
1570
+ attributeFilter: [TRACK_TARGET_ATTRIBUTE]
1571
+ });
1572
+ changed.observe(document, {
1573
+ childList: true,
1574
+ subtree: true
1575
+ });
1576
+ view?.addEventListener("scroll", place, {
1577
+ capture: true,
1578
+ passive: true
1579
+ });
1580
+ view?.addEventListener("resize", place);
1581
+ follow();
1582
+ return () => {
1583
+ changed.disconnect();
1584
+ resized.disconnect();
1585
+ view?.removeEventListener("scroll", place, { capture: true });
1586
+ view?.removeEventListener("resize", place);
1587
+ };
1588
+ }), (stop) => Effect.sync(stop)))
1589
+ });
1590
+ //#endregion
1591
+ export { Autofocus, ClipboardMessage, ClipboardReadMessage, DRAG_THRESHOLD, DragCancelled, DragDropped, DragPlace, DragStarted, DragZone, DraggedOver, EditAsked, EditableText, FLUID_HOVER_ATTRIBUTE, FLUID_LIT_ATTRIBUTE, FLUID_PRESS_ATTRIBUTE, FluidHover, FocusScope, Focused, FollowTabStop, HideOutside, Input, InputMask, KeepInView, Measure, Move, MoveEnded, MoveStarted, Moved, PointerDrag, PointerMoved, PointerPoint, PointerPosition, PointerPressed, PointerReleased, SCROLL_BAR_ATTRIBUTE, SCROLL_OVERLAID_ATTRIBUTE, SETTLE_QUIET, SWIPE_WINDOW, ScriptMessage, ScrollBars, ScrollLock, ScrollSettle, ScrollSettled, ShareData, ShareMessage, Swipe, SwipeEnded, SwipeStarted, Swiped, TRACK_TARGET_ATTRIBUTE, TargetHovered, TargetPressed, Targets, TextCancelled, TextCommitted, TextEdited, TrackBox, TrackedTarget, applyMask, boxOf, copyText, cornerRadius, fluidHovered, keepScroll, loadScript, measurePaddingBox, measured, paddingBoxOf, pointedLinks, readText, scrollThumb, share, tabbableWithin, targetOf, textOf, trackedBox, velocityOf, zoneOf };
549
1592
 
550
1593
  //# sourceMappingURL=index.mjs.map