@rio-m3e/core 0.2.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ const buttonGroupSkeleton = {
32
32
  tag: "div",
33
33
  content: true
34
34
  };
35
- const valuesOf = (selected) => selected === void 0 ? [] : Array.isArray(selected) ? selected : [selected];
35
+ const valuesOf$1 = (selected) => selected === void 0 ? [] : Array.isArray(selected) ? selected : [selected];
36
36
  function createButtonGroup(options) {
37
37
  const props = () => ({
38
38
  ...buttonGroupDefaults,
@@ -40,7 +40,7 @@ function createButtonGroup(options) {
40
40
  });
41
41
  const isSelected = (value) => {
42
42
  const p = props();
43
- return p.selection !== "none" && valuesOf(p.selected).includes(value);
43
+ return p.selection !== "none" && valuesOf$1(p.selected).includes(value);
44
44
  };
45
45
  return {
46
46
  parts: { root() {
@@ -60,7 +60,7 @@ function createButtonGroup(options) {
60
60
  select(value, selected) {
61
61
  const p = props();
62
62
  if (p.selection === "none" || isSelected(value) === selected) return;
63
- const current = valuesOf(p.selected);
63
+ const current = valuesOf$1(p.selected);
64
64
  if (!selected && p.required && current.length === 1) return;
65
65
  if (p.selection === "single") options.onChange(selected ? value : void 0);
66
66
  else options.onChange(selected ? [...current, value] : current.filter((other) => other !== value));
@@ -199,7 +199,7 @@ function readSpringParams(element, token) {
199
199
  const PRESSED_WIDTH_RATIO = .15;
200
200
  function attachButtonGroup(root, options = {}) {
201
201
  const ratio = options.pressedWidthRatio ?? PRESSED_WIDTH_RATIO;
202
- const items = () => [...root.children].filter((el) => el instanceof HTMLElement);
202
+ const items = () => [...root.children].map((el) => el.classList.contains("rio-tooltip-trigger") ? el.firstElementChild : el).filter((el) => el instanceof HTMLElement);
203
203
  const onKeydown = (event) => {
204
204
  if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
205
205
  if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
@@ -269,7 +269,8 @@ function attachButtonGroup(root, options = {}) {
269
269
  }
270
270
  };
271
271
  const observer = new MutationObserver((mutations) => {
272
- const own = mutations.filter((m) => m.target.parentElement === root);
272
+ const current = items();
273
+ const own = mutations.filter((m) => current.includes(m.target));
273
274
  if (own.length > 0) layout(own.some((m) => m.attributeName !== "data-pressed"));
274
275
  });
275
276
  observer.observe(root, {
@@ -384,6 +385,448 @@ function createButton(options) {
384
385
  }
385
386
  };
386
387
  }
388
+ // src/dialog/dialog.ts
389
+ /**
390
+ * The dialog's ARIA role: `alertdialog` for a prompt that asks for a response, such as a
391
+ * confirmation, and `dialog` for one that holds a list or a form.
392
+ */
393
+ const DIALOG_ROLES = ["alertdialog", "dialog"];
394
+ const dialogDefaults = {
395
+ open: false,
396
+ role: "alertdialog"
397
+ };
398
+ /**
399
+ * The markup of an open dialog. The parts inside render from opening until `attachDialog`'s
400
+ * `onClosed`; a closed dialog is the empty `<dialog>`.
401
+ */
402
+ const dialogSkeleton = {
403
+ part: "root",
404
+ tag: "dialog",
405
+ children: [
406
+ {
407
+ part: "icon",
408
+ tag: "div",
409
+ when: "showsIcon",
410
+ content: true
411
+ },
412
+ {
413
+ part: "headline",
414
+ tag: "h2",
415
+ when: "showsHeadline",
416
+ content: true
417
+ },
418
+ {
419
+ part: "content",
420
+ tag: "div",
421
+ content: true
422
+ },
423
+ {
424
+ part: "actions",
425
+ tag: "div",
426
+ when: "showsActions",
427
+ content: true
428
+ }
429
+ ]
430
+ };
431
+ const NO_CONTENT$2 = {
432
+ icon: false,
433
+ headline: false,
434
+ actions: false
435
+ };
436
+ function createDialog(options) {
437
+ const props = () => ({
438
+ ...dialogDefaults,
439
+ ...omitUndefined(options.props())
440
+ });
441
+ const content = () => options.content?.() ?? NO_CONTENT$2;
442
+ const showsIcon = () => content().icon;
443
+ const showsHeadline = () => content().headline;
444
+ const showsActions = () => content().actions;
445
+ const { ids } = options;
446
+ return {
447
+ parts: {
448
+ root() {
449
+ const alert = props().role === "alertdialog";
450
+ return {
451
+ class: "rio-dialog",
452
+ role: alert ? "alertdialog" : void 0,
453
+ "aria-labelledby": showsHeadline() ? ids.headline : void 0,
454
+ "aria-describedby": alert ? ids.content : void 0,
455
+ "data-icon": showsIcon() ? "" : void 0
456
+ };
457
+ },
458
+ icon: () => ({
459
+ class: "rio-dialog__icon",
460
+ "aria-hidden": "true"
461
+ }),
462
+ headline: () => ({
463
+ class: "rio-dialog__headline",
464
+ id: ids.headline
465
+ }),
466
+ content: () => ({
467
+ class: "rio-dialog__content rio-focus-ring",
468
+ id: ids.content,
469
+ "data-focus-ring": "inner"
470
+ }),
471
+ actions: () => ({ class: "rio-dialog__actions" })
472
+ },
473
+ showsIcon,
474
+ showsHeadline,
475
+ showsActions
476
+ };
477
+ }
478
+ // src/primitives/overlay/overlay.ts
479
+ const registries = /* @__PURE__ */ new WeakMap();
480
+ /** What a `contain`ed overlay keeps in */
481
+ const PRESSES = [
482
+ "click",
483
+ "auxclick",
484
+ "dblclick",
485
+ "pointerdown",
486
+ "pointerup",
487
+ "mousedown",
488
+ "mouseup",
489
+ "contextmenu"
490
+ ];
491
+ const stopPropagation = (event) => event.stopPropagation();
492
+ const isOpen = ({ element, options }) => options.mode === "modal" ? element.open : element.matches(":popover-open");
493
+ /** Open on the record, but no longer in the top layer, after leaving the document */
494
+ const left = (entry) => entry.options.mode === "modal" ? !entry.element.matches(":modal") : !isOpen(entry);
495
+ const close = (entry) => {
496
+ if (!isOpen(entry)) return;
497
+ if (entry.options.mode === "modal") entry.element.close();
498
+ else entry.element.hidePopover();
499
+ };
500
+ const track = (registry) => {
501
+ if (registry.stack.length) registry.moves.observe(registry.document, {
502
+ childList: true,
503
+ subtree: true
504
+ });
505
+ else registry.moves.disconnect();
506
+ };
507
+ const opened = (entry) => {
508
+ const { stack } = entry.registry;
509
+ if (stack.includes(entry)) return;
510
+ stack.push(entry);
511
+ track(entry.registry);
512
+ for (const other of stack.filter((open) => open !== entry && open.options.transient)) {
513
+ other.reason = "replaced";
514
+ close(other);
515
+ }
516
+ };
517
+ const closed = (entry, asked) => {
518
+ const { stack } = entry.registry;
519
+ const index = stack.indexOf(entry);
520
+ if (index < 0) return;
521
+ stack.splice(index, 1);
522
+ track(entry.registry);
523
+ const reason = entry.reason ?? "dismiss";
524
+ entry.reason = void 0;
525
+ if (!asked) entry.options.onClose(reason);
526
+ const inside = (node) => !!node && entry.element.contains(node);
527
+ for (const other of stack.filter((open) => inside(open.element) || inside(open.source))) {
528
+ other.reason = "parent";
529
+ close(other);
530
+ }
531
+ };
532
+ const registryOf = (document) => {
533
+ let registry = registries.get(document);
534
+ if (!registry) {
535
+ const stack = [];
536
+ const onKeydown = (event) => {
537
+ if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
538
+ const top = stack.at(-1);
539
+ if (!top) return;
540
+ const target = event.target;
541
+ const host = typeof target?.closest === "function" ? target.closest("dialog:modal, :popover-open:not([popover=\"manual\"])") : null;
542
+ if (host && !stack.some((open) => open.element === host)) return;
543
+ event.preventDefault();
544
+ top.options.onClose("escape");
545
+ };
546
+ registry = {
547
+ stack,
548
+ attached: 0,
549
+ onKeydown,
550
+ moves: new MutationObserver(() => {
551
+ for (const entry of stack.filter(left)) {
552
+ if (entry.options.mode === "modal") entry.element.close();
553
+ closed(entry, false);
554
+ }
555
+ }),
556
+ document
557
+ };
558
+ registries.set(document, registry);
559
+ }
560
+ return registry;
561
+ };
562
+ function attachOverlay(element, options) {
563
+ const modal = options.mode === "modal";
564
+ if (modal && element.localName !== "dialog") throw new TypeError("A modal overlay must be a <dialog> element.");
565
+ const document = element.ownerDocument;
566
+ const registry = registryOf(document);
567
+ const entry = {
568
+ element,
569
+ options,
570
+ registry
571
+ };
572
+ const onToggle = () => isOpen(entry) ? opened(entry) : closed(entry, false);
573
+ const onDialogClose = () => {
574
+ if (!isOpen(entry)) closed(entry, false);
575
+ };
576
+ const onCancel = (event) => {
577
+ event.preventDefault();
578
+ options.onClose("escape");
579
+ };
580
+ if (modal) {
581
+ element.addEventListener("close", onDialogClose);
582
+ element.addEventListener("cancel", onCancel);
583
+ } else {
584
+ element.popover = options.mode;
585
+ element.addEventListener("toggle", onToggle);
586
+ }
587
+ if (options.contain) for (const type of PRESSES) element.addEventListener(type, stopPropagation);
588
+ if (registry.attached++ === 0) document.addEventListener("keydown", registry.onKeydown);
589
+ let detached = false;
590
+ return {
591
+ show(source) {
592
+ if (detached || isOpen(entry)) return;
593
+ const index = registry.stack.indexOf(entry);
594
+ if (index >= 0) registry.stack.splice(index, 1);
595
+ track(registry);
596
+ entry.reason = void 0;
597
+ entry.source = source;
598
+ if (modal) element.showModal();
599
+ else element.showPopover({ source });
600
+ opened(entry);
601
+ },
602
+ hide() {
603
+ if (!isOpen(entry)) return;
604
+ close(entry);
605
+ closed(entry, true);
606
+ },
607
+ covered() {
608
+ const index = registry.stack.indexOf(entry);
609
+ return index >= 0 && registry.stack.slice(index + 1).some((open) => !open.options.transient);
610
+ },
611
+ detach() {
612
+ if (detached) return;
613
+ detached = true;
614
+ element.removeEventListener("close", onDialogClose);
615
+ element.removeEventListener("cancel", onCancel);
616
+ element.removeEventListener("toggle", onToggle);
617
+ for (const type of PRESSES) element.removeEventListener(type, stopPropagation);
618
+ const index = registry.stack.indexOf(entry);
619
+ if (index >= 0) registry.stack.splice(index, 1);
620
+ track(registry);
621
+ close(entry);
622
+ if (!modal) element.removeAttribute("popover");
623
+ if (--registry.attached === 0) document.removeEventListener("keydown", registry.onKeydown);
624
+ }
625
+ };
626
+ }
627
+ // src/dialog/interaction.ts
628
+ const TABBABLE = [
629
+ "a[href]",
630
+ "area[href]",
631
+ "button",
632
+ "input:not([type=\"hidden\"])",
633
+ "select",
634
+ "textarea",
635
+ "iframe",
636
+ "audio[controls]",
637
+ "video[controls]",
638
+ "summary",
639
+ "[contenteditable]:not([contenteditable=\"false\"])",
640
+ "[tabindex]"
641
+ ].join(",");
642
+ const isRadio = (element) => element.localName === "input" && element.type === "radio";
643
+ const visible = (element) => typeof element.checkVisibility === "function" ? element.checkVisibility({ visibilityProperty: true }) : element.getClientRects().length > 0;
644
+ /** The elements Tab stops at in `root`, in order. Elements inside shadow roots aren't found. */
645
+ const tabbablesIn = (root) => {
646
+ const all = [...root.querySelectorAll(TABBABLE)].filter((element) => element.tabIndex >= 0 && !element.matches(":disabled") && !element.closest("[inert]") && visible(element));
647
+ return all.filter((element) => {
648
+ if (!isRadio(element) || !element.name) return true;
649
+ const group = all.filter((other) => isRadio(other) && other.name === element.name && other.form === element.form);
650
+ return element === (group.find((radio) => radio.checked) ?? group[0]);
651
+ });
652
+ };
653
+ /** How far, in px, the content may scroll and still count as at its end */
654
+ const SCROLL_SLOP = 1;
655
+ const scrollLocks = /* @__PURE__ */ new WeakMap();
656
+ const lockScroll = (document) => {
657
+ const lock = scrollLocks.get(document);
658
+ if (lock) {
659
+ lock.count++;
660
+ return;
661
+ }
662
+ const html = document.documentElement;
663
+ const view = document.defaultView;
664
+ const { overflow, scrollbarGutter } = html.style;
665
+ const scrollbar = view.innerWidth - html.clientWidth;
666
+ html.style.overflow = "hidden";
667
+ if (scrollbar > 0 && view.getComputedStyle(html).scrollbarGutter === "auto") html.style.scrollbarGutter = "stable";
668
+ scrollLocks.set(document, {
669
+ count: 1,
670
+ restore() {
671
+ html.style.overflow = overflow;
672
+ html.style.scrollbarGutter = scrollbarGutter;
673
+ }
674
+ });
675
+ };
676
+ const unlockScroll = (document) => {
677
+ const lock = scrollLocks.get(document);
678
+ if (!lock || --lock.count > 0) return;
679
+ lock.restore();
680
+ scrollLocks.delete(document);
681
+ };
682
+ function attachDialog(root, options) {
683
+ const document = root.ownerDocument;
684
+ const cancel = (reason) => {
685
+ let prevented = false;
686
+ options.onCancel?.({
687
+ reason,
688
+ get defaultPrevented() {
689
+ return prevented;
690
+ },
691
+ preventDefault() {
692
+ prevented = true;
693
+ }
694
+ });
695
+ if (!prevented) options.onOpenChange(false);
696
+ };
697
+ const overlay = attachOverlay(root, {
698
+ mode: "modal",
699
+ contain: true,
700
+ onClose: (reason) => reason === "escape" ? cancel("escape") : options.onOpenChange(false)
701
+ });
702
+ const onKeydown = (event) => {
703
+ if (event.key !== "Tab" || event.defaultPrevented) return;
704
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
705
+ const tabbables = tabbablesIn(root);
706
+ const first = tabbables[0];
707
+ const last = tabbables.at(-1);
708
+ if (!first || !last) {
709
+ event.preventDefault();
710
+ return;
711
+ }
712
+ const focused = document.activeElement;
713
+ if (!(event.shiftKey ? focused === first || focused === root : focused === last)) return;
714
+ event.preventDefault();
715
+ (event.shiftKey ? last : first).focus();
716
+ };
717
+ const onScrim = (event) => {
718
+ if (event.target !== root) return false;
719
+ const box = root.getBoundingClientRect();
720
+ return event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom;
721
+ };
722
+ let pressedScrim = false;
723
+ const onPointerDown = (event) => {
724
+ pressedScrim = onScrim(event) && !overlay.covered();
725
+ };
726
+ const onClick = (event) => {
727
+ const pressed = pressedScrim;
728
+ pressedScrim = false;
729
+ if (pressed && onScrim(event)) cancel("scrim");
730
+ };
731
+ const content = () => root.querySelector(":scope > .rio-dialog__content");
732
+ const updateDividers = () => {
733
+ const element = content();
734
+ if (!element) return;
735
+ const hidden = element.scrollHeight - element.clientHeight;
736
+ element.toggleAttribute("data-overflow-top", element.scrollTop >= SCROLL_SLOP);
737
+ element.toggleAttribute("data-overflow-bottom", hidden - element.scrollTop >= SCROLL_SLOP);
738
+ };
739
+ /** The content, while it takes the focus so the keyboard can scroll it */
740
+ let reachable = null;
741
+ const setReachable = (element) => {
742
+ if (element === reachable) return;
743
+ reachable?.removeAttribute("tabindex");
744
+ reachable = element;
745
+ reachable?.setAttribute("tabindex", "0");
746
+ };
747
+ const updateOverflow = () => {
748
+ updateDividers();
749
+ const element = content();
750
+ const scrolls = !!element && element.scrollHeight - element.clientHeight >= SCROLL_SLOP;
751
+ setReachable(scrolls && tabbablesIn(element).length === 0 ? element : null);
752
+ };
753
+ const resizes = new ResizeObserver(updateOverflow);
754
+ let watched = null;
755
+ const watch = () => {
756
+ resizes.disconnect();
757
+ watched?.removeEventListener("scroll", updateDividers);
758
+ watched = content();
759
+ if (!watched) return;
760
+ watched.addEventListener("scroll", updateDividers, { passive: true });
761
+ resizes.observe(watched);
762
+ for (const child of watched.children) resizes.observe(child);
763
+ };
764
+ const unwatch = () => {
765
+ resizes.disconnect();
766
+ watched?.removeEventListener("scroll", updateDividers);
767
+ watched = null;
768
+ setReachable(null);
769
+ };
770
+ const focusFirst = () => {
771
+ if (root.querySelector("[autofocus]")) return;
772
+ const tabbables = tabbablesIn(root);
773
+ (tabbables.find((element) => element !== reachable) ?? tabbables[0])?.focus();
774
+ };
775
+ /** Whether this dialog holds a lock on the page's scroll: from opening until it closes */
776
+ let locked = false;
777
+ /** Counts closings, so only the last one reports its end, and only if the dialog stayed closed */
778
+ let closings = 0;
779
+ const closed = () => {
780
+ if (!locked) return;
781
+ locked = false;
782
+ unlockScroll(document);
783
+ const closing = ++closings;
784
+ const motion = root.getAnimations({ subtree: true }).map((animation) => animation.finished.catch(() => void 0));
785
+ Promise.all(motion).then(() => {
786
+ if (closing === closings && !root.open) options.onClosed?.();
787
+ });
788
+ };
789
+ root.addEventListener("keydown", onKeydown);
790
+ root.addEventListener("pointerdown", onPointerDown);
791
+ root.addEventListener("click", onClick);
792
+ return {
793
+ sync(open) {
794
+ if (open && !root.open) {
795
+ overlay.show();
796
+ if (!locked) {
797
+ locked = true;
798
+ lockScroll(document);
799
+ }
800
+ const element = content();
801
+ if (element) element.scrollTop = 0;
802
+ watch();
803
+ updateOverflow();
804
+ focusFirst();
805
+ } else if (!open) {
806
+ if (root.open) overlay.hide();
807
+ unwatch();
808
+ closed();
809
+ }
810
+ },
811
+ update() {
812
+ if (!root.open) return;
813
+ watch();
814
+ updateOverflow();
815
+ },
816
+ detach() {
817
+ root.removeEventListener("keydown", onKeydown);
818
+ root.removeEventListener("pointerdown", onPointerDown);
819
+ root.removeEventListener("click", onClick);
820
+ unwatch();
821
+ overlay.detach();
822
+ closings++;
823
+ if (locked) {
824
+ locked = false;
825
+ unlockScroll(document);
826
+ }
827
+ }
828
+ };
829
+ }
387
830
  // src/icon-button/icon-button.ts
388
831
  const ICON_BUTTON_VARIANTS = [
389
832
  "filled",
@@ -456,6 +899,943 @@ function createIconButton(options) {
456
899
  handleClick: button.handleClick
457
900
  };
458
901
  }
902
+ // src/primitives/anchored-position/anchored-position.ts
903
+ const OPPOSITE = {
904
+ top: "bottom",
905
+ bottom: "top",
906
+ start: "end",
907
+ end: "start"
908
+ };
909
+ const VARS = [
910
+ "--rio-anchored-available-width",
911
+ "--rio-anchored-available-height",
912
+ "--rio-anchored-anchor-width"
913
+ ];
914
+ const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
915
+ /** An anchor point, rather than an element. Checked by shape, so elements from iframes count. */
916
+ const isPoint = (target) => typeof target.getBoundingClientRect !== "function";
917
+ function attachAnchoredPosition(floating, options) {
918
+ const document = floating.ownerDocument;
919
+ const view = document.defaultView;
920
+ floating.dataset.anchored = "";
921
+ const offset = options.offset ?? 0;
922
+ const alignOffset = options.alignOffset ?? 0;
923
+ const padding = options.padding ?? 8;
924
+ const preferred = options.side ?? "bottom";
925
+ const align = options.align ?? "start";
926
+ const candidates = [.../* @__PURE__ */ new Set([preferred, ...options.fallbacks ?? [OPPOSITE[preferred]]])];
927
+ /** Rounds to the device's pixel grid, so text in the element stays sharp. */
928
+ const snap = (value) => Math.round(value * view.devicePixelRatio) / view.devicePixelRatio;
929
+ let frame;
930
+ const schedule = () => {
931
+ frame ??= view.requestAnimationFrame(() => {
932
+ frame = void 0;
933
+ place();
934
+ });
935
+ };
936
+ const onScroll = (event) => {
937
+ if (event.target !== document && floating.contains(event.target)) return;
938
+ schedule();
939
+ };
940
+ const resize = new view.ResizeObserver(schedule);
941
+ resize.observe(floating);
942
+ let observed;
943
+ const observeAnchor = (element) => {
944
+ if (element === observed) return;
945
+ if (observed) resize.unobserve(observed);
946
+ observed = element;
947
+ if (element) resize.observe(element);
948
+ };
949
+ const place = () => {
950
+ const target = options.anchor();
951
+ if (!target || floating.getClientRects().length === 0) return;
952
+ if (!isPoint(target) && !target.isConnected) return;
953
+ observeAnchor(isPoint(target) ? void 0 : target);
954
+ const anchor = isPoint(target) ? {
955
+ left: target.x,
956
+ right: target.x,
957
+ top: target.y,
958
+ bottom: target.y,
959
+ width: 0
960
+ } : target.getBoundingClientRect();
961
+ const rtl = view.getComputedStyle(isPoint(target) ? floating : target).direction === "rtl";
962
+ const viewport = {
963
+ width: document.documentElement.clientWidth,
964
+ height: document.documentElement.clientHeight
965
+ };
966
+ const physical = (side) => side === "start" ? rtl ? "right" : "left" : side === "end" ? rtl ? "left" : "right" : side;
967
+ const room = {
968
+ top: anchor.top - offset - padding,
969
+ bottom: viewport.height - anchor.bottom - offset - padding,
970
+ left: anchor.left - offset - padding,
971
+ right: viewport.width - anchor.right - offset - padding
972
+ };
973
+ floating.style.left = "0px";
974
+ floating.style.top = "0px";
975
+ const borders = {
976
+ width: floating.offsetWidth - floating.clientWidth,
977
+ height: floating.offsetHeight - floating.clientHeight
978
+ };
979
+ const natural = {
980
+ width: floating.scrollWidth + borders.width,
981
+ height: floating.scrollHeight + borders.height
982
+ };
983
+ const fits = (candidate) => {
984
+ const at = physical(candidate);
985
+ return at === "top" || at === "bottom" ? room[at] >= natural.height && natural.width <= viewport.width - 2 * padding : room[at] >= natural.width && natural.height <= viewport.height - 2 * padding;
986
+ };
987
+ const roomier = (a, b) => room[physical(b)] > room[physical(a)] ? b : a;
988
+ const side = candidates.find(fits) ?? roomier(preferred, OPPOSITE[preferred]);
989
+ const at = physical(side);
990
+ const vertical = at === "top" || at === "bottom";
991
+ const available = {
992
+ width: vertical ? viewport.width - 2 * padding : room[at],
993
+ height: vertical ? room[at] : viewport.height - 2 * padding
994
+ };
995
+ floating.style.setProperty(VARS[0], `${Math.max(0, available.width)}px`);
996
+ floating.style.setProperty(VARS[1], `${Math.max(0, available.height)}px`);
997
+ floating.style.setProperty(VARS[2], `${anchor.width}px`);
998
+ const size = {
999
+ width: floating.offsetWidth,
1000
+ height: floating.offsetHeight
1001
+ };
1002
+ let x;
1003
+ let y;
1004
+ if (vertical) {
1005
+ y = at === "bottom" ? anchor.bottom + offset : anchor.top - offset - size.height;
1006
+ const startEdge = rtl ? anchor.right - size.width : anchor.left;
1007
+ const endEdge = rtl ? anchor.left : anchor.right - size.width;
1008
+ x = (align === "center" ? anchor.left + (anchor.width - size.width) / 2 : align === "start" ? startEdge : endEdge) + (rtl ? -alignOffset : alignOffset);
1009
+ } else {
1010
+ x = at === "right" ? anchor.right + offset : anchor.left - offset - size.width;
1011
+ const height = anchor.bottom - anchor.top;
1012
+ y = (align === "center" ? anchor.top + (height - size.height) / 2 : align === "start" ? anchor.top : anchor.bottom - size.height) + alignOffset;
1013
+ }
1014
+ const fit = (value, length, extent, startAtEnd) => length > extent - 2 * padding ? startAtEnd ? extent - padding - length : padding : clamp(value, padding, extent - padding - length);
1015
+ x = fit(x, size.width, viewport.width, rtl);
1016
+ y = fit(y, size.height, viewport.height, false);
1017
+ floating.style.left = `${snap(x)}px`;
1018
+ floating.style.top = `${snap(y)}px`;
1019
+ floating.dataset.side = side;
1020
+ floating.dataset.align = align;
1021
+ if (anchor.bottom < 0 || anchor.top > viewport.height || anchor.right < 0 || anchor.left > viewport.width) floating.dataset.anchorHidden = "";
1022
+ else delete floating.dataset.anchorHidden;
1023
+ };
1024
+ document.addEventListener("scroll", onScroll, {
1025
+ capture: true,
1026
+ passive: true
1027
+ });
1028
+ view.addEventListener("resize", schedule, { passive: true });
1029
+ return {
1030
+ update: place,
1031
+ detach() {
1032
+ document.removeEventListener("scroll", onScroll, { capture: true });
1033
+ view.removeEventListener("resize", schedule);
1034
+ resize.disconnect();
1035
+ if (frame !== void 0) view.cancelAnimationFrame(frame);
1036
+ for (const name of [
1037
+ "left",
1038
+ "top",
1039
+ ...VARS
1040
+ ]) floating.style.removeProperty(name);
1041
+ for (const name of [
1042
+ "side",
1043
+ "align",
1044
+ "anchorHidden",
1045
+ "anchored"
1046
+ ]) delete floating.dataset[name];
1047
+ }
1048
+ };
1049
+ }
1050
+ // src/menu/menu.ts
1051
+ /** M3E vertical menu color styles: surface-based, or tertiary-based for more emphasis. */
1052
+ const MENU_VARIANTS = ["standard", "vibrant"];
1053
+ /** Whether the menu's items can be selected: none, one at a time, or any number. */
1054
+ const MENU_SELECTIONS = [
1055
+ "none",
1056
+ "single",
1057
+ "multiple"
1058
+ ];
1059
+ const menuDefaults = {
1060
+ open: false,
1061
+ variant: "standard",
1062
+ selection: "none",
1063
+ side: "bottom",
1064
+ align: "start",
1065
+ offset: 0,
1066
+ instant: false
1067
+ };
1068
+ const menuSkeleton = {
1069
+ part: "root",
1070
+ tag: "div",
1071
+ content: true
1072
+ };
1073
+ const valuesOf = (selected) => selected === void 0 ? [] : Array.isArray(selected) ? selected : [selected];
1074
+ function createMenu(options) {
1075
+ const props = () => ({
1076
+ ...menuDefaults,
1077
+ ...omitUndefined(options.props())
1078
+ });
1079
+ const isSelected = (value) => {
1080
+ const p = props();
1081
+ return p.selection !== "none" && valuesOf(p.selected).includes(value);
1082
+ };
1083
+ return {
1084
+ parts: { root() {
1085
+ const p = props();
1086
+ return {
1087
+ class: "rio-menu",
1088
+ role: "menu",
1089
+ "aria-label": p.label,
1090
+ "data-variant": p.variant,
1091
+ "data-instant": p.instant ? "" : void 0
1092
+ };
1093
+ } },
1094
+ selection: () => props().selection,
1095
+ isSelected,
1096
+ choose(value) {
1097
+ const p = props();
1098
+ if (p.selection !== "none" && value !== void 0) {
1099
+ if (p.selection === "single") {
1100
+ if (!isSelected(value)) options.onChange(value);
1101
+ } else {
1102
+ const current = valuesOf(p.selected);
1103
+ options.onChange(isSelected(value) ? current.filter((other) => other !== value) : [...current, value]);
1104
+ return;
1105
+ }
1106
+ }
1107
+ options.onOpenChange(false);
1108
+ },
1109
+ close: () => options.onOpenChange(false),
1110
+ variant: () => props().variant,
1111
+ instant: () => props().instant
1112
+ };
1113
+ }
1114
+ const menuItemDefaults = { disabled: false };
1115
+ const menuItemSkeleton = {
1116
+ part: "root",
1117
+ tag: "button",
1118
+ children: [
1119
+ {
1120
+ part: "touch",
1121
+ tag: "span"
1122
+ },
1123
+ {
1124
+ part: "icon",
1125
+ tag: "span",
1126
+ when: "showsIcon",
1127
+ content: true
1128
+ },
1129
+ {
1130
+ part: "text",
1131
+ tag: "span",
1132
+ children: [{
1133
+ part: "label",
1134
+ tag: "span",
1135
+ content: true
1136
+ }, {
1137
+ part: "supportingText",
1138
+ tag: "span",
1139
+ when: "showsSupportingText",
1140
+ content: true
1141
+ }]
1142
+ },
1143
+ {
1144
+ part: "trailingText",
1145
+ tag: "span",
1146
+ when: "showsTrailingText",
1147
+ content: true
1148
+ },
1149
+ {
1150
+ part: "trailingIcon",
1151
+ tag: "span",
1152
+ when: "showsTrailingIcon",
1153
+ content: true
1154
+ }
1155
+ ]
1156
+ };
1157
+ const NO_CONTENT$1 = {
1158
+ icon: false,
1159
+ supportingText: false,
1160
+ trailingText: false,
1161
+ trailingIcon: false
1162
+ };
1163
+ function createMenuItem(options) {
1164
+ const props = () => ({
1165
+ ...menuItemDefaults,
1166
+ ...omitUndefined(options.props())
1167
+ });
1168
+ const content = () => options.content?.() ?? NO_CONTENT$1;
1169
+ const kind = () => {
1170
+ if (options.submenu) return void 0;
1171
+ const selection = options.menu?.()?.selection() ?? "none";
1172
+ if (selection === "none" || props().value === void 0) return void 0;
1173
+ return selection === "single" ? "radio" : "checkbox";
1174
+ };
1175
+ const selected = () => {
1176
+ const value = props().value;
1177
+ return kind() !== void 0 && !!options.menu?.()?.isSelected(value);
1178
+ };
1179
+ return {
1180
+ parts: {
1181
+ root() {
1182
+ const p = props();
1183
+ const k = kind();
1184
+ return {
1185
+ class: "rio-menu-item rio-state-layer rio-focus-ring",
1186
+ type: "button",
1187
+ role: k ? `menuitem${k}` : "menuitem",
1188
+ tabindex: "-1",
1189
+ "aria-checked": k ? String(selected()) : void 0,
1190
+ "aria-haspopup": options.submenu ? "menu" : void 0,
1191
+ "aria-expanded": options.submenu ? String(options.submenu.open()) : void 0,
1192
+ "aria-disabled": p.disabled ? "true" : void 0,
1193
+ "data-selected": k ? String(selected()) : void 0,
1194
+ "data-focus-ring": "inner"
1195
+ };
1196
+ },
1197
+ touch: () => ({
1198
+ class: "rio-menu-item__touch",
1199
+ "aria-hidden": "true"
1200
+ }),
1201
+ icon: () => ({
1202
+ class: "rio-menu-item__icon",
1203
+ "aria-hidden": "true"
1204
+ }),
1205
+ text: () => ({ class: "rio-menu-item__text" }),
1206
+ label: () => ({ class: "rio-menu-item__label" }),
1207
+ supportingText: () => ({ class: "rio-menu-item__supporting-text" }),
1208
+ trailingText: () => ({ class: "rio-menu-item__trailing-text" }),
1209
+ trailingIcon: () => ({
1210
+ class: "rio-menu-item__trailing-icon",
1211
+ "aria-hidden": "true"
1212
+ })
1213
+ },
1214
+ showsIcon: () => content().icon,
1215
+ showsSupportingText: () => content().supportingText,
1216
+ showsTrailingText: () => content().trailingText,
1217
+ showsTrailingIcon: () => content().trailingIcon,
1218
+ selected,
1219
+ handleClick(event) {
1220
+ if (props().disabled) {
1221
+ event.preventDefault();
1222
+ return;
1223
+ }
1224
+ options.onClick(event);
1225
+ if (options.submenu) options.submenu.onOpen();
1226
+ else options.menu?.()?.choose(props().value);
1227
+ }
1228
+ };
1229
+ }
1230
+ /**
1231
+ * A submenu and the item that opens it. The two render side by side in the menu, the item where it
1232
+ * sits and the submenu after it; the submenu shows in the top layer next to the item.
1233
+ */
1234
+ function createSubMenu(options) {
1235
+ const menu = createMenu({
1236
+ props: () => {
1237
+ const p = options.props();
1238
+ const parent = options.menu?.();
1239
+ return {
1240
+ open: options.open(),
1241
+ variant: parent?.variant(),
1242
+ instant: parent?.instant(),
1243
+ selection: p.selection,
1244
+ selected: p.selected,
1245
+ label: p.label
1246
+ };
1247
+ },
1248
+ onChange: options.onChange,
1249
+ onOpenChange: (open) => {
1250
+ const parent = options.menu?.();
1251
+ if (open) options.onOpenChange(true);
1252
+ else if (parent) parent.close();
1253
+ else options.onOpenChange(false);
1254
+ }
1255
+ });
1256
+ return {
1257
+ item: createMenuItem({
1258
+ props: () => ({ disabled: options.props().disabled }),
1259
+ content: options.content,
1260
+ onClick: () => {},
1261
+ submenu: {
1262
+ open: options.open,
1263
+ onOpen: () => options.onOpenChange(true)
1264
+ }
1265
+ }),
1266
+ menu
1267
+ };
1268
+ }
1269
+ const menuGroupSkeleton = {
1270
+ part: "root",
1271
+ tag: "div",
1272
+ children: [{
1273
+ part: "label",
1274
+ tag: "div",
1275
+ when: "showsLabel",
1276
+ content: true
1277
+ }, {
1278
+ part: "items",
1279
+ tag: "div",
1280
+ content: true
1281
+ }]
1282
+ };
1283
+ function createMenuGroup(options) {
1284
+ return {
1285
+ parts: {
1286
+ root: () => ({
1287
+ class: "rio-menu-group",
1288
+ role: "group",
1289
+ "aria-label": options.props().label
1290
+ }),
1291
+ label: () => ({
1292
+ class: "rio-menu-group__label",
1293
+ "aria-hidden": "true"
1294
+ }),
1295
+ items: () => ({ class: "rio-menu-group__items" })
1296
+ },
1297
+ showsLabel: () => !!options.props().label
1298
+ };
1299
+ }
1300
+ const menuDividerSkeleton = {
1301
+ part: "root",
1302
+ tag: "div"
1303
+ };
1304
+ function createMenuDivider() {
1305
+ return { parts: { root: () => ({
1306
+ class: "rio-menu-divider",
1307
+ role: "separator"
1308
+ }) } };
1309
+ }
1310
+ // src/menu/interaction.ts
1311
+ /** Where to try next when the preferred side has no room: across, then to the sides. */
1312
+ const FALLBACKS = {
1313
+ bottom: [
1314
+ "top",
1315
+ "end",
1316
+ "start"
1317
+ ],
1318
+ top: [
1319
+ "bottom",
1320
+ "end",
1321
+ "start"
1322
+ ],
1323
+ end: [
1324
+ "start",
1325
+ "bottom",
1326
+ "top"
1327
+ ],
1328
+ start: [
1329
+ "end",
1330
+ "bottom",
1331
+ "top"
1332
+ ]
1333
+ };
1334
+ /** How long typed letters add up to one search. */
1335
+ const TYPEAHEAD_MS = 500;
1336
+ /** How long the pointer rests on an item before its submenu opens */
1337
+ const HOVER_OPEN_MS = 100;
1338
+ /** How long the pointer may take to reach an open submenu across other items */
1339
+ const GRACE_MS = 300;
1340
+ /**
1341
+ * A submenu goes beside its item, 4px off the menu around it (the item is inset 4px), with its
1342
+ * first item lined up with the item (its own are inset 4px from its top).
1343
+ */
1344
+ const SUBMENU_PLACEMENT = {
1345
+ side: "end",
1346
+ align: "start",
1347
+ offset: 8,
1348
+ alignOffset: -4
1349
+ };
1350
+ const isElement = (anchor) => !!anchor && typeof anchor.focus === "function";
1351
+ /** Points are compared by value: an inline `{ x, y }` is a new object on every render. */
1352
+ const samePoint = (a, b) => a.x === b.x && a.y === b.y && a.element === b.element;
1353
+ /** Whether a point is inside a polygon, by casting a ray to the right and counting crossings */
1354
+ const isInside = ({ x, y }, polygon) => {
1355
+ let inside = false;
1356
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
1357
+ const a = polygon[i];
1358
+ const b = polygon[j];
1359
+ if (a.y > y !== b.y > y && x < (b.x - a.x) * (y - a.y) / (b.y - a.y) + a.x) inside = !inside;
1360
+ }
1361
+ return inside;
1362
+ };
1363
+ const menuStates = /* @__PURE__ */ new WeakMap();
1364
+ const stateOf = (menu) => {
1365
+ let state = menuStates.get(menu);
1366
+ if (!state) {
1367
+ state = {
1368
+ openers: /* @__PURE__ */ new Map(),
1369
+ typing: () => false
1370
+ };
1371
+ menuStates.set(menu, state);
1372
+ }
1373
+ return state;
1374
+ };
1375
+ const endGrace = (state) => {
1376
+ if (state.grace) clearTimeout(state.grace.timer);
1377
+ state.grace = void 0;
1378
+ };
1379
+ const menuAround = (element) => element.parentElement?.closest(".rio-menu");
1380
+ const topOf = (menu) => {
1381
+ let top = menu;
1382
+ for (let up = menuAround(menu); up; up = menuAround(up)) top = up;
1383
+ return top;
1384
+ };
1385
+ const activeMenus = /* @__PURE__ */ new WeakMap();
1386
+ const shapeTree = (menu, active) => {
1387
+ const top = topOf(menu);
1388
+ if (active) activeMenus.set(top, active);
1389
+ let current = activeMenus.get(top) ?? top;
1390
+ while (current && !current.matches(":popover-open")) current = menuAround(current);
1391
+ const open = [top, ...top.querySelectorAll(".rio-menu")].filter((each) => each.matches(":popover-open"));
1392
+ for (const each of open) each.toggleAttribute("data-inactive", open.length > 1 && each !== current);
1393
+ };
1394
+ function attachMenu(root, options) {
1395
+ return setup(root, options);
1396
+ }
1397
+ function setup(root, options, nested) {
1398
+ const document = root.ownerDocument;
1399
+ const view = document.defaultView;
1400
+ let reanchoring = false;
1401
+ const overlay = attachOverlay(root, {
1402
+ mode: "auto",
1403
+ onClose: (reason) => {
1404
+ if (!(reanchoring && reason === "dismiss")) options.onClose();
1405
+ }
1406
+ });
1407
+ const items = () => [...root.querySelectorAll("[role^=\"menuitem\"]")].filter((item) => item.closest("[role=\"menu\"]") === root);
1408
+ const focused = () => items().indexOf(document.activeElement);
1409
+ const focusAt = (index) => {
1410
+ const all = items();
1411
+ if (all.length) all[(index + all.length) % all.length].focus();
1412
+ };
1413
+ let anchored;
1414
+ let placement = "";
1415
+ /** What had the focus when the menu opened */
1416
+ let opener = null;
1417
+ let pinned;
1418
+ const pin = (fresh) => {
1419
+ const anchor = options.anchor();
1420
+ if (!anchor || isElement(anchor) || !anchor.element) {
1421
+ pinned = void 0;
1422
+ return;
1423
+ }
1424
+ if (!fresh && pinned && samePoint(pinned.point, anchor)) return;
1425
+ const rect = anchor.element.getBoundingClientRect();
1426
+ pinned = {
1427
+ point: { ...anchor },
1428
+ x: anchor.x - rect.left,
1429
+ y: anchor.y - rect.top
1430
+ };
1431
+ };
1432
+ /** The anchor for placing: for a point on an element, where that spot is now */
1433
+ const anchorNow = () => {
1434
+ const anchor = options.anchor();
1435
+ if (!anchor || isElement(anchor) || !anchor.element) return anchor;
1436
+ if (!anchor.element.isConnected) return void 0;
1437
+ if (!pinned || !samePoint(pinned.point, anchor)) pin(false);
1438
+ const rect = anchor.element.getBoundingClientRect();
1439
+ return {
1440
+ x: rect.left + pinned.x,
1441
+ y: rect.top + pinned.y
1442
+ };
1443
+ };
1444
+ const place = () => {
1445
+ const p = {
1446
+ ...menuDefaults,
1447
+ alignOffset: 0,
1448
+ ...omitUndefined(options.props())
1449
+ };
1450
+ const key = `${p.side} ${p.align} ${p.offset} ${p.alignOffset}`;
1451
+ if (anchored && key === placement) return anchored;
1452
+ anchored?.detach();
1453
+ placement = key;
1454
+ anchored = attachAnchoredPosition(root, {
1455
+ anchor: anchorNow,
1456
+ side: p.side,
1457
+ align: p.align,
1458
+ offset: p.offset,
1459
+ alignOffset: p.alignOffset,
1460
+ fallbacks: FALLBACKS[p.side]
1461
+ });
1462
+ return anchored;
1463
+ };
1464
+ const returnTarget = () => {
1465
+ const anchor = options.anchor();
1466
+ if (isElement(anchor) && anchor.isConnected) return anchor;
1467
+ return opener?.isConnected && !root.contains(opener) ? opener : null;
1468
+ };
1469
+ let typed = "";
1470
+ let typedAt = 0;
1471
+ const typeahead = (key) => {
1472
+ const now = performance.now();
1473
+ typed = now - typedAt > TYPEAHEAD_MS ? key : typed + key;
1474
+ typedAt = now;
1475
+ const all = items();
1476
+ const labelOf = (item) => (item.querySelector(".rio-menu-item__label")?.textContent ?? item.textContent ?? "").trim().toLowerCase();
1477
+ const search = [...typed].every((letter) => letter === typed[0]) ? typed[0] : typed;
1478
+ const start = focused() + (search.length === 1 ? 1 : 0);
1479
+ for (let step = 0; step < all.length; step++) {
1480
+ const item = all[(Math.max(start, 0) + step) % all.length];
1481
+ if (labelOf(item).startsWith(search)) {
1482
+ item.focus();
1483
+ return;
1484
+ }
1485
+ }
1486
+ };
1487
+ const state = stateOf(root);
1488
+ state.typing = () => typed !== "" && performance.now() - typedAt <= TYPEAHEAD_MS;
1489
+ const onKeydown = (event) => {
1490
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;
1491
+ if (event.key === "Tab") {
1492
+ if (nested) return;
1493
+ options.onClose();
1494
+ returnTarget()?.focus();
1495
+ return;
1496
+ }
1497
+ if (event.target.closest("[role=\"menu\"]") !== root) return;
1498
+ if (activeMenus.get(topOf(root)) !== root) shapeTree(root, root);
1499
+ const handled = () => event.preventDefault();
1500
+ if (nested && event.key === (isRtl() ? "ArrowRight" : "ArrowLeft")) {
1501
+ handled();
1502
+ options.onClose();
1503
+ return;
1504
+ }
1505
+ switch (event.key) {
1506
+ case "ArrowDown":
1507
+ handled();
1508
+ focusAt(focused() + 1);
1509
+ return;
1510
+ case "ArrowUp":
1511
+ handled();
1512
+ focusAt(focused() < 0 ? -1 : focused() - 1);
1513
+ return;
1514
+ case "Home":
1515
+ handled();
1516
+ focusAt(0);
1517
+ return;
1518
+ case "End":
1519
+ handled();
1520
+ focusAt(-1);
1521
+ return;
1522
+ }
1523
+ if (event.key.length === 1 && !(event.key === " " && !typed)) {
1524
+ handled();
1525
+ typeahead(event.key.toLowerCase());
1526
+ }
1527
+ };
1528
+ root.addEventListener("keydown", onKeydown);
1529
+ const isRtl = () => view.getComputedStyle(root).direction === "rtl";
1530
+ let last;
1531
+ /** Which way the pointer last moved, across and down */
1532
+ const heading = {};
1533
+ const onPointerMove = (event) => {
1534
+ if (event.pointerType !== "mouse") return;
1535
+ const target = event.target;
1536
+ if (target.closest("[role=\"menu\"]") !== root) return;
1537
+ const point = {
1538
+ x: event.clientX,
1539
+ y: event.clientY
1540
+ };
1541
+ if (last && point.x !== last.x) heading.x = point.x > last.x ? "right" : "left";
1542
+ if (last && point.y !== last.y) heading.y = point.y > last.y ? "down" : "up";
1543
+ last = point;
1544
+ if (activeMenus.get(topOf(root)) !== root) shapeTree(root, root);
1545
+ const { grace } = state;
1546
+ if (grace) {
1547
+ if ((grace.toward === "left" || grace.toward === "right" ? heading.x : heading.y) === grace.toward && isInside(point, grace.area)) return;
1548
+ }
1549
+ endGrace(state);
1550
+ const item = target.closest("[role^=\"menuitem\"]");
1551
+ if (!item) return;
1552
+ const focusedMenu = document.activeElement?.closest("[role=\"menu\"]");
1553
+ if (focusedMenu && focusedMenu !== root && topOf(focusedMenu) === topOf(root)) item.focus({ preventScroll: true });
1554
+ for (const [opener, control] of state.openers) if (opener !== item) control.cancel();
1555
+ state.openers.get(item)?.hover();
1556
+ };
1557
+ root.addEventListener("pointermove", onPointerMove);
1558
+ const onFocusIn = (event) => {
1559
+ if (event.target.closest("[role=\"menu\"]") === root) shapeTree(root, root);
1560
+ };
1561
+ root.addEventListener("focusin", onFocusIn);
1562
+ const focusFirst = () => {
1563
+ const all = items();
1564
+ (all.find((item) => item.getAttribute("aria-checked") === "true") ?? all[0])?.focus();
1565
+ };
1566
+ /** What the menu last opened at: its anchor element or point, or null for none */
1567
+ let shownAt = null;
1568
+ const isNewAnchor = () => {
1569
+ const anchor = options.anchor();
1570
+ if (!anchor || !shownAt) return false;
1571
+ if (isElement(anchor) || isElement(shownAt)) return anchor !== shownAt;
1572
+ return !samePoint(anchor, shownAt);
1573
+ };
1574
+ const remember = () => {
1575
+ const anchor = options.anchor();
1576
+ shownAt = !anchor ? null : isElement(anchor) ? anchor : { ...anchor };
1577
+ };
1578
+ /**
1579
+ * Opens the menu. `afresh` opens it as if it had been closed: a menu still open, or still fading
1580
+ * out after a click outside, would only turn its closing back where it is, so that is cut short
1581
+ * and the opening motion plays from the start. By default, at a new anchor.
1582
+ */
1583
+ const show = (afresh = isNewAnchor()) => {
1584
+ const active = document.activeElement;
1585
+ if (!root.contains(active)) opener = active instanceof document.defaultView.HTMLElement ? active : null;
1586
+ if (afresh) {
1587
+ root.style.setProperty("transition", "none");
1588
+ if (root.matches(":popover-open")) overlay.hide();
1589
+ root.offsetWidth;
1590
+ root.style.removeProperty("transition");
1591
+ }
1592
+ const anchored = place();
1593
+ const anchor = options.anchor();
1594
+ remember();
1595
+ const on = anchor && !isElement(anchor) ? anchor.element : void 0;
1596
+ overlay.show(isElement(anchor) ? anchor : on instanceof document.defaultView.HTMLElement ? on : void 0);
1597
+ anchored.update();
1598
+ const focusing = !nested || nested.focusOnShow();
1599
+ shapeTree(root, focusing ? root : void 0);
1600
+ if (focusing) focusFirst();
1601
+ };
1602
+ let pressing = false;
1603
+ let afterPress;
1604
+ let pressTimer;
1605
+ const release = () => {
1606
+ if (!pressing) return;
1607
+ pressing = false;
1608
+ clearTimeout(pressTimer);
1609
+ const run = afterPress;
1610
+ afterPress = void 0;
1611
+ if (run) setTimeout(run);
1612
+ };
1613
+ const onContextMenu = (event) => {
1614
+ if (event.buttons === 0) return;
1615
+ pressing = true;
1616
+ clearTimeout(pressTimer);
1617
+ pressTimer = setTimeout(release, 1e3);
1618
+ };
1619
+ const onContextMenuDone = (event) => {
1620
+ setTimeout(() => {
1621
+ if (!event.defaultPrevented) release();
1622
+ });
1623
+ };
1624
+ if (!nested) {
1625
+ document.addEventListener("contextmenu", onContextMenu, { capture: true });
1626
+ view.addEventListener("contextmenu", onContextMenuDone);
1627
+ for (const type of ["pointerup", "pointercancel"]) document.addEventListener(type, release, { capture: true });
1628
+ view.addEventListener("blur", release);
1629
+ }
1630
+ let wanted = false;
1631
+ return {
1632
+ sync(open) {
1633
+ wanted = open;
1634
+ const shown = root.matches(":popover-open");
1635
+ if (open && !shown) {
1636
+ pin(true);
1637
+ if (pressing) afterPress = () => wanted && !root.matches(":popover-open") && show();
1638
+ else show();
1639
+ } else if (!open) {
1640
+ const hadFocus = root.contains(document.activeElement);
1641
+ afterPress = void 0;
1642
+ reanchoring = false;
1643
+ overlay.hide();
1644
+ typed = "";
1645
+ shapeTree(root);
1646
+ if (hadFocus) returnTarget()?.focus();
1647
+ }
1648
+ },
1649
+ update() {
1650
+ pin(false);
1651
+ if (!root.matches(":popover-open")) {
1652
+ if (wanted && !pressing && isNewAnchor()) show(true);
1653
+ return;
1654
+ }
1655
+ if (pressing) {
1656
+ reanchoring = true;
1657
+ afterPress = () => {
1658
+ reanchoring = false;
1659
+ if (wanted) show(true);
1660
+ };
1661
+ return;
1662
+ }
1663
+ if (isNewAnchor()) show(true);
1664
+ else {
1665
+ remember();
1666
+ place().update();
1667
+ }
1668
+ },
1669
+ focusFirst,
1670
+ detach() {
1671
+ root.removeEventListener("keydown", onKeydown);
1672
+ root.removeEventListener("pointermove", onPointerMove);
1673
+ root.removeEventListener("focusin", onFocusIn);
1674
+ endGrace(state);
1675
+ document.removeEventListener("contextmenu", onContextMenu, { capture: true });
1676
+ view.removeEventListener("contextmenu", onContextMenuDone);
1677
+ for (const type of ["pointerup", "pointercancel"]) document.removeEventListener(type, release, { capture: true });
1678
+ view.removeEventListener("blur", release);
1679
+ clearTimeout(pressTimer);
1680
+ pressing = false;
1681
+ afterPress = void 0;
1682
+ overlay.detach();
1683
+ anchored?.detach();
1684
+ }
1685
+ };
1686
+ }
1687
+ let submenuIds = 0;
1688
+ /**
1689
+ * A submenu's behavior in the DOM: a menu (see `attachMenu`) that `item` opens, shown beside it.
1690
+ * `item` sits in a menu that `attachMenu` or this function runs, and `root` follows it.
1691
+ */
1692
+ function attachSubMenu(item, root, options) {
1693
+ const view = item.ownerDocument.defaultView;
1694
+ const parent = item.closest("[role=\"menu\"]");
1695
+ let open = false;
1696
+ /** Whether the opening asked for moves the focus in: from the keyboard, not the pointer */
1697
+ let focusIn = false;
1698
+ const request = (next, focus = false) => {
1699
+ focusIn = focus;
1700
+ options.onOpenChange(next);
1701
+ };
1702
+ const dom = setup(root, {
1703
+ anchor: () => item,
1704
+ props: () => SUBMENU_PLACEMENT,
1705
+ onClose: () => request(false)
1706
+ }, { focusOnShow: () => focusIn });
1707
+ const disabled = () => item.getAttribute("aria-disabled") === "true";
1708
+ root.id ||= `rio-submenu-${++submenuIds}`;
1709
+ item.setAttribute("popovertarget", root.id);
1710
+ item.setAttribute("popovertargetaction", "show");
1711
+ const onClickFirst = (event) => {
1712
+ focusIn = event.detail === 0;
1713
+ };
1714
+ const onClick = (event) => event.preventDefault();
1715
+ item.addEventListener("click", onClickFirst, { capture: true });
1716
+ item.addEventListener("click", onClick);
1717
+ let timer;
1718
+ const stopTimer = () => {
1719
+ clearTimeout(timer);
1720
+ timer = void 0;
1721
+ };
1722
+ const opener = {
1723
+ hover() {
1724
+ if (open || disabled() || timer !== void 0) return;
1725
+ timer = setTimeout(() => {
1726
+ timer = void 0;
1727
+ if (!parent || parent.matches(":popover-open")) request(true);
1728
+ }, HOVER_OPEN_MS);
1729
+ },
1730
+ cancel() {
1731
+ stopTimer();
1732
+ if (open) request(false);
1733
+ }
1734
+ };
1735
+ if (parent) stateOf(parent).openers.set(item, opener);
1736
+ const onPointerLeave = (event) => {
1737
+ if (event.pointerType !== "mouse") return;
1738
+ stopTimer();
1739
+ if (!open || !parent) return;
1740
+ const state = stateOf(parent);
1741
+ const box = root.getBoundingClientRect();
1742
+ const from = item.getBoundingClientRect();
1743
+ const toward = box.top >= from.bottom - 1 ? "down" : box.bottom <= from.top + 1 ? "up" : box.left >= from.left ? "right" : "left";
1744
+ const across = toward === "left" || toward === "right";
1745
+ const bleed = toward === "right" || toward === "down" ? -5 : 5;
1746
+ const exit = across ? {
1747
+ x: event.clientX + bleed,
1748
+ y: event.clientY
1749
+ } : {
1750
+ x: event.clientX,
1751
+ y: event.clientY + bleed
1752
+ };
1753
+ const near = {
1754
+ right: box.left,
1755
+ left: box.right,
1756
+ down: box.top,
1757
+ up: box.bottom
1758
+ }[toward];
1759
+ const far = {
1760
+ right: box.right,
1761
+ left: box.left,
1762
+ down: box.bottom,
1763
+ up: box.top
1764
+ }[toward];
1765
+ endGrace(state);
1766
+ state.grace = {
1767
+ area: across ? [
1768
+ exit,
1769
+ {
1770
+ x: near,
1771
+ y: box.top
1772
+ },
1773
+ {
1774
+ x: far,
1775
+ y: box.top
1776
+ },
1777
+ {
1778
+ x: far,
1779
+ y: box.bottom
1780
+ },
1781
+ {
1782
+ x: near,
1783
+ y: box.bottom
1784
+ }
1785
+ ] : [
1786
+ exit,
1787
+ {
1788
+ x: box.left,
1789
+ y: near
1790
+ },
1791
+ {
1792
+ x: box.left,
1793
+ y: far
1794
+ },
1795
+ {
1796
+ x: box.right,
1797
+ y: far
1798
+ },
1799
+ {
1800
+ x: box.right,
1801
+ y: near
1802
+ }
1803
+ ],
1804
+ toward,
1805
+ timer: setTimeout(() => endGrace(state), GRACE_MS)
1806
+ };
1807
+ };
1808
+ item.addEventListener("pointerleave", onPointerLeave);
1809
+ const onKeydown = (event) => {
1810
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;
1811
+ const forward = view.getComputedStyle(item).direction === "rtl" ? "ArrowLeft" : "ArrowRight";
1812
+ const space = event.key === " " && !(parent && stateOf(parent).typing());
1813
+ if (event.key !== forward && event.key !== "Enter" && !space) return;
1814
+ event.preventDefault();
1815
+ if (disabled()) return;
1816
+ if (open) dom.focusFirst();
1817
+ else request(true, true);
1818
+ };
1819
+ item.addEventListener("keydown", onKeydown);
1820
+ return {
1821
+ sync(next) {
1822
+ open = next;
1823
+ if (!next) stopTimer();
1824
+ dom.sync(next);
1825
+ },
1826
+ detach() {
1827
+ stopTimer();
1828
+ item.removeEventListener("click", onClickFirst, { capture: true });
1829
+ item.removeEventListener("click", onClick);
1830
+ item.removeEventListener("pointerleave", onPointerLeave);
1831
+ item.removeEventListener("keydown", onKeydown);
1832
+ item.removeAttribute("popovertarget");
1833
+ item.removeAttribute("popovertargetaction");
1834
+ if (parent && stateOf(parent).openers.get(item) === opener) stateOf(parent).openers.delete(item);
1835
+ dom.detach();
1836
+ }
1837
+ };
1838
+ }
459
1839
  // src/primitives/drag/drag.ts
460
1840
  /** Velocity is measured over this window, so a pause before release reads as slow. */
461
1841
  const VELOCITY_WINDOW_MS = 100;
@@ -1044,4 +2424,375 @@ function createSwitch(options) {
1044
2424
  }
1045
2425
  };
1046
2426
  }
1047
- export { BUTTON_GROUP_SELECTIONS, BUTTON_GROUP_VARIANTS, BUTTON_SIZES, BUTTON_VARIANTS, ICON_BUTTON_VARIANTS, ICON_BUTTON_WIDTHS, SPLIT_BUTTON_VARIANTS, attachButtonGroup, attachDrag, attachRipple, attachShapeMorph, attachSplitButtonMenuIcon, attachSpringProperty, attachSwitchHandle, buttonDefaults, buttonGroupDefaults, buttonGroupSkeleton, buttonSkeleton, createButton, createButtonGroup, createIconButton, createSplitButton, createSpring, createSwitch, iconButtonDefaults, iconButtonSkeleton, prefersReducedMotion, readSpringParams, solveSpring, splitButtonDefaults, splitButtonSkeleton, switchDefaults, switchSkeleton };
2427
+ // src/tooltip/interaction.ts
2428
+ /** How long the pointer rests on the element before its tooltip shows */
2429
+ const SHOW_DELAY_MS = 500;
2430
+ /** How long a tooltip stays after the pointer and the focus leave */
2431
+ const HIDE_DELAY_MS = 1500;
2432
+ /** How soon after a tooltip closes the next one still shows at once */
2433
+ const WARM_MS = 300;
2434
+ /** How long a touch is held to show the tooltip */
2435
+ const LONG_PRESS_MS = 500;
2436
+ /** How long after a touch that showed the tooltip ends its click is swallowed */
2437
+ const SWALLOW_CLICK_MS = 600;
2438
+ /** How far a held touch may move, in px, and still count as held */
2439
+ const MOVE_SLOP = 10;
2440
+ const shown = /* @__PURE__ */ new Set();
2441
+ let lastClosedAt = -Infinity;
2442
+ const warm = () => shown.size > 0 || performance.now() - lastClosedAt < WARM_MS;
2443
+ const textOf = (element) => (element?.textContent ?? "").replace(/\s+/g, " ").trim();
2444
+ /** The element's accessible name, near enough to tell whether a tooltip repeats it */
2445
+ const nameOf = (element) => {
2446
+ const labelledBy = element.getAttribute("aria-labelledby");
2447
+ if (labelledBy) {
2448
+ const document = element.ownerDocument;
2449
+ return labelledBy.split(/\s+/).map((id) => textOf(document.getElementById(id))).join(" ").trim();
2450
+ }
2451
+ return element.getAttribute("aria-label")?.trim() || textOf(element);
2452
+ };
2453
+ /** Adds or removes one id in a space-separated attribute, keeping the others. */
2454
+ const setToken = (element, name, id, on) => {
2455
+ const ids = (element.getAttribute(name) ?? "").split(/\s+/).filter((each) => each && each !== id);
2456
+ if (on) ids.push(id);
2457
+ if (ids.length) element.setAttribute(name, ids.join(" "));
2458
+ else element.removeAttribute(name);
2459
+ };
2460
+ function attachTooltip(trigger, root, options) {
2461
+ const document = trigger.ownerDocument;
2462
+ const view = document.defaultView;
2463
+ /** The element the tooltip is for: the trigger's first child, ahead of the tooltip */
2464
+ const element = () => {
2465
+ const first = trigger.firstElementChild;
2466
+ return first instanceof view.HTMLElement && first !== root ? first : null;
2467
+ };
2468
+ const isOpen = () => root.matches(":popover-open");
2469
+ const request = (open) => options.onOpenChange(open);
2470
+ let overlay;
2471
+ let overlayPersistent;
2472
+ /** When the pointer last went down on the element, whether the tooltip was open */
2473
+ let openAtPress = false;
2474
+ const overlayNow = () => {
2475
+ const persistent = options.persistent();
2476
+ if (overlay && persistent === overlayPersistent) return overlay;
2477
+ overlay?.detach();
2478
+ overlayPersistent = persistent;
2479
+ const onClose = () => request(false);
2480
+ overlay = attachOverlay(root, persistent ? {
2481
+ mode: "auto",
2482
+ contain: true,
2483
+ onClose
2484
+ } : {
2485
+ mode: "manual",
2486
+ transient: true,
2487
+ contain: true,
2488
+ onClose
2489
+ });
2490
+ return overlay;
2491
+ };
2492
+ let anchored;
2493
+ let placement = "";
2494
+ const place = () => {
2495
+ const p = options.placement();
2496
+ const key = `${p.side} ${p.align} ${p.offset}`;
2497
+ if (anchored && key === placement) return anchored;
2498
+ anchored?.detach();
2499
+ placement = key;
2500
+ anchored = attachAnchoredPosition(root, {
2501
+ anchor: element,
2502
+ side: p.side,
2503
+ align: p.align,
2504
+ offset: p.offset
2505
+ });
2506
+ return anchored;
2507
+ };
2508
+ let described;
2509
+ let expanded;
2510
+ const updateAria = () => {
2511
+ const target = element();
2512
+ const text = options.interactive() ? root.querySelector(".rio-tooltip__supporting-text") : root;
2513
+ const describe = !!target && !!text?.id && nameOf(target) !== textOf(text);
2514
+ if (described && (!describe || described.element !== target || described.id !== text.id)) {
2515
+ setToken(described.element, "aria-describedby", described.id, false);
2516
+ described = void 0;
2517
+ }
2518
+ if (describe && !described) {
2519
+ setToken(target, "aria-describedby", text.id, true);
2520
+ described = {
2521
+ element: target,
2522
+ id: text.id
2523
+ };
2524
+ }
2525
+ const persistent = options.persistent();
2526
+ if (expanded && (!persistent || expanded !== target)) {
2527
+ expanded.removeAttribute("aria-expanded");
2528
+ expanded = void 0;
2529
+ }
2530
+ if (persistent && target) {
2531
+ target.setAttribute("aria-expanded", String(isOpen()));
2532
+ expanded = target;
2533
+ }
2534
+ };
2535
+ let showTimer;
2536
+ let hideTimer;
2537
+ const stopShow = () => {
2538
+ clearTimeout(showTimer);
2539
+ showTimer = void 0;
2540
+ };
2541
+ const stopHide = () => {
2542
+ clearTimeout(hideTimer);
2543
+ hideTimer = void 0;
2544
+ };
2545
+ const hideLater = () => {
2546
+ stopHide();
2547
+ hideTimer = setTimeout(() => {
2548
+ hideTimer = void 0;
2549
+ const focused = document.activeElement;
2550
+ const keyboard = !!focused && trigger.contains(focused) && focused.matches(":focus-visible");
2551
+ if (!trigger.matches(":hover") && !keyboard) request(false);
2552
+ }, HIDE_DELAY_MS);
2553
+ };
2554
+ const onPointerEnter = (event) => {
2555
+ if (event.pointerType !== "mouse" || options.persistent()) return;
2556
+ stopHide();
2557
+ if (isOpen() || showTimer !== void 0) return;
2558
+ if (warm()) {
2559
+ request(true);
2560
+ return;
2561
+ }
2562
+ showTimer = setTimeout(() => {
2563
+ showTimer = void 0;
2564
+ request(true);
2565
+ }, SHOW_DELAY_MS);
2566
+ };
2567
+ const onPointerLeave = (event) => {
2568
+ if (event.pointerType !== "mouse" || options.persistent()) return;
2569
+ stopShow();
2570
+ if (isOpen()) hideLater();
2571
+ };
2572
+ const onFocusIn = (event) => {
2573
+ if (options.persistent()) return;
2574
+ const focused = event.target;
2575
+ if (!root.contains(focused) && !focused.matches(":focus-visible")) return;
2576
+ stopShow();
2577
+ stopHide();
2578
+ if (!isOpen()) request(true);
2579
+ };
2580
+ const onFocusOut = (event) => {
2581
+ const next = event.relatedTarget;
2582
+ if (next && trigger.contains(next)) return;
2583
+ if (options.persistent()) {
2584
+ if (next && isOpen()) request(false);
2585
+ return;
2586
+ }
2587
+ if (isOpen()) hideLater();
2588
+ };
2589
+ let pressTimer;
2590
+ let pressedAt;
2591
+ let swallowUntil = -Infinity;
2592
+ const stopPress = () => {
2593
+ clearTimeout(pressTimer);
2594
+ pressTimer = void 0;
2595
+ pressedAt = void 0;
2596
+ };
2597
+ const onPointerDown = (event) => {
2598
+ if (!element()?.contains(event.target)) return;
2599
+ openAtPress = isOpen();
2600
+ if (options.persistent()) return;
2601
+ stopShow();
2602
+ if (event.pointerType === "touch") {
2603
+ stopPress();
2604
+ pressedAt = {
2605
+ x: event.clientX,
2606
+ y: event.clientY
2607
+ };
2608
+ pressTimer = setTimeout(() => {
2609
+ stopPress();
2610
+ swallowUntil = Infinity;
2611
+ stopHide();
2612
+ request(true);
2613
+ }, LONG_PRESS_MS);
2614
+ return;
2615
+ }
2616
+ stopHide();
2617
+ if (isOpen()) request(false);
2618
+ };
2619
+ const onPointerMove = (event) => {
2620
+ if (!pressedAt) return;
2621
+ if (Math.hypot(event.clientX - pressedAt.x, event.clientY - pressedAt.y) > MOVE_SLOP) stopPress();
2622
+ };
2623
+ const onPointerUp = (event) => {
2624
+ if (event.pointerType !== "touch" || options.persistent()) return;
2625
+ stopPress();
2626
+ if (swallowUntil === Infinity) {
2627
+ swallowUntil = performance.now() + SWALLOW_CLICK_MS;
2628
+ if (isOpen()) hideLater();
2629
+ }
2630
+ };
2631
+ const onPointerCancel = () => {
2632
+ stopPress();
2633
+ swallowUntil = -Infinity;
2634
+ };
2635
+ const onContextMenu = (event) => {
2636
+ if (pressTimer !== void 0 || performance.now() < swallowUntil) event.preventDefault();
2637
+ };
2638
+ const onClickFirst = (event) => {
2639
+ if (performance.now() >= swallowUntil || !element()?.contains(event.target)) return;
2640
+ swallowUntil = -Infinity;
2641
+ event.preventDefault();
2642
+ event.stopImmediatePropagation();
2643
+ };
2644
+ const onClick = (event) => {
2645
+ if (!options.persistent()) return;
2646
+ if (!element()?.contains(event.target)) return;
2647
+ request(!(event.detail === 0 ? isOpen() : openAtPress));
2648
+ };
2649
+ trigger.addEventListener("pointerenter", onPointerEnter);
2650
+ trigger.addEventListener("pointerleave", onPointerLeave);
2651
+ trigger.addEventListener("focusin", onFocusIn);
2652
+ trigger.addEventListener("focusout", onFocusOut);
2653
+ trigger.addEventListener("pointerdown", onPointerDown);
2654
+ trigger.addEventListener("pointermove", onPointerMove);
2655
+ trigger.addEventListener("pointerup", onPointerUp);
2656
+ trigger.addEventListener("pointercancel", onPointerCancel);
2657
+ trigger.addEventListener("contextmenu", onContextMenu);
2658
+ trigger.addEventListener("click", onClickFirst, { capture: true });
2659
+ trigger.addEventListener("click", onClick);
2660
+ overlayNow();
2661
+ updateAria();
2662
+ return {
2663
+ sync(open) {
2664
+ const showing = isOpen();
2665
+ if (open && !showing) {
2666
+ const target = element();
2667
+ if (!target) {
2668
+ request(false);
2669
+ return;
2670
+ }
2671
+ overlayNow().show(target);
2672
+ place().update();
2673
+ shown.add(root);
2674
+ } else if (!open) {
2675
+ stopShow();
2676
+ stopHide();
2677
+ if (showing) overlay?.hide();
2678
+ if (shown.delete(root)) lastClosedAt = performance.now();
2679
+ }
2680
+ updateAria();
2681
+ },
2682
+ update: updateAria,
2683
+ detach() {
2684
+ trigger.removeEventListener("pointerenter", onPointerEnter);
2685
+ trigger.removeEventListener("pointerleave", onPointerLeave);
2686
+ trigger.removeEventListener("focusin", onFocusIn);
2687
+ trigger.removeEventListener("focusout", onFocusOut);
2688
+ trigger.removeEventListener("pointerdown", onPointerDown);
2689
+ trigger.removeEventListener("pointermove", onPointerMove);
2690
+ trigger.removeEventListener("pointerup", onPointerUp);
2691
+ trigger.removeEventListener("pointercancel", onPointerCancel);
2692
+ trigger.removeEventListener("contextmenu", onContextMenu);
2693
+ trigger.removeEventListener("click", onClickFirst, { capture: true });
2694
+ trigger.removeEventListener("click", onClick);
2695
+ stopShow();
2696
+ stopHide();
2697
+ stopPress();
2698
+ if (described) setToken(described.element, "aria-describedby", described.id, false);
2699
+ expanded?.removeAttribute("aria-expanded");
2700
+ if (shown.delete(root)) lastClosedAt = performance.now();
2701
+ overlay?.detach();
2702
+ anchored?.detach();
2703
+ }
2704
+ };
2705
+ }
2706
+ // src/tooltip/tooltip.ts
2707
+ /** M3 tooltip variants: a brief label, or a card with a subhead, text, and actions. */
2708
+ const TOOLTIP_VARIANTS = ["plain", "rich"];
2709
+ const tooltipDefaults = {
2710
+ variant: "plain",
2711
+ persistent: false,
2712
+ open: false,
2713
+ offset: 4
2714
+ };
2715
+ /** The trigger holds the app's element first, then the tooltip (`tooltipSkeleton`). */
2716
+ const tooltipTriggerSkeleton = {
2717
+ part: "trigger",
2718
+ tag: "span",
2719
+ content: true
2720
+ };
2721
+ /** Spans throughout, so a tooltip may sit inside a paragraph. */
2722
+ const tooltipSkeleton = {
2723
+ part: "root",
2724
+ tag: "span",
2725
+ children: [
2726
+ {
2727
+ part: "subhead",
2728
+ tag: "span",
2729
+ when: "showsSubhead",
2730
+ content: true
2731
+ },
2732
+ {
2733
+ part: "supportingText",
2734
+ tag: "span",
2735
+ content: true
2736
+ },
2737
+ {
2738
+ part: "actions",
2739
+ tag: "span",
2740
+ when: "showsActions",
2741
+ content: true
2742
+ }
2743
+ ]
2744
+ };
2745
+ const NO_CONTENT = {
2746
+ subhead: false,
2747
+ actions: false
2748
+ };
2749
+ function createTooltip(options) {
2750
+ const props = () => ({
2751
+ ...tooltipDefaults,
2752
+ ...omitUndefined(options.props())
2753
+ });
2754
+ const rich = () => props().variant === "rich";
2755
+ const content = () => options.content?.() ?? NO_CONTENT;
2756
+ const showsSubhead = () => rich() && content().subhead;
2757
+ const showsActions = () => rich() && content().actions;
2758
+ const interactive = showsActions;
2759
+ const { ids } = options;
2760
+ return {
2761
+ parts: {
2762
+ trigger: () => ({ class: "rio-tooltip-trigger" }),
2763
+ root() {
2764
+ const p = props();
2765
+ const dialog = interactive();
2766
+ return {
2767
+ class: "rio-tooltip",
2768
+ id: ids.root,
2769
+ role: dialog ? "dialog" : "tooltip",
2770
+ "aria-labelledby": dialog ? showsSubhead() ? ids.subhead : ids.supportingText : void 0,
2771
+ "data-variant": p.variant
2772
+ };
2773
+ },
2774
+ subhead: () => ({
2775
+ class: "rio-tooltip__subhead",
2776
+ id: ids.subhead
2777
+ }),
2778
+ supportingText: () => ({
2779
+ class: "rio-tooltip__supporting-text",
2780
+ id: ids.supportingText
2781
+ }),
2782
+ actions: () => ({ class: "rio-tooltip__actions" })
2783
+ },
2784
+ showsSubhead,
2785
+ showsActions,
2786
+ interactive,
2787
+ persistent: () => rich() && props().persistent,
2788
+ placement() {
2789
+ const p = props();
2790
+ return {
2791
+ side: p.side ?? (rich() ? "bottom" : "top"),
2792
+ align: p.align ?? (rich() ? "start" : "center"),
2793
+ offset: p.offset
2794
+ };
2795
+ }
2796
+ };
2797
+ }
2798
+ export { BUTTON_GROUP_SELECTIONS, BUTTON_GROUP_VARIANTS, BUTTON_SIZES, BUTTON_VARIANTS, DIALOG_ROLES, ICON_BUTTON_VARIANTS, ICON_BUTTON_WIDTHS, MENU_SELECTIONS, MENU_VARIANTS, SPLIT_BUTTON_VARIANTS, TOOLTIP_VARIANTS, attachAnchoredPosition, attachButtonGroup, attachDialog, attachDrag, attachMenu, attachOverlay, attachRipple, attachShapeMorph, attachSplitButtonMenuIcon, attachSpringProperty, attachSubMenu, attachSwitchHandle, attachTooltip, buttonDefaults, buttonGroupDefaults, buttonGroupSkeleton, buttonSkeleton, createButton, createButtonGroup, createDialog, createIconButton, createMenu, createMenuDivider, createMenuGroup, createMenuItem, createSplitButton, createSpring, createSubMenu, createSwitch, createTooltip, dialogDefaults, dialogSkeleton, iconButtonDefaults, iconButtonSkeleton, menuDefaults, menuDividerSkeleton, menuGroupSkeleton, menuItemDefaults, menuItemSkeleton, menuSkeleton, prefersReducedMotion, readSpringParams, solveSpring, splitButtonDefaults, splitButtonSkeleton, switchDefaults, switchSkeleton, tooltipDefaults, tooltipSkeleton, tooltipTriggerSkeleton };