@floegence/floe-webapp-core 0.39.4 → 0.39.6

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.
@@ -0,0 +1,33 @@
1
+ import { type JSX } from 'solid-js';
2
+ export type BottomBarCompanionPhase = 'collapsed' | 'expanding' | 'expanded' | 'collapsing';
3
+ export type BottomBarCompanionDismissReason = 'outside-pointer' | 'escape';
4
+ export interface BottomBarCompanionProps {
5
+ retained: boolean;
6
+ visible: boolean;
7
+ open: boolean;
8
+ anchor: HTMLElement | null;
9
+ mount: HTMLElement | null;
10
+ id: string;
11
+ label: string;
12
+ children?: JSX.Element;
13
+ maxWidth?: number;
14
+ maxHeight?: number;
15
+ viewportPadding?: number;
16
+ class?: string;
17
+ contentClass?: string;
18
+ surfaceRef?: (element: HTMLElement | null) => void;
19
+ contentHostRef?: (element: HTMLElement | null) => void;
20
+ isOwnedInteraction?: (event: PointerEvent | KeyboardEvent) => boolean;
21
+ onDismiss?: (reason: BottomBarCompanionDismissReason) => void;
22
+ onPhaseChange?: (phase: BottomBarCompanionPhase) => void;
23
+ }
24
+ /**
25
+ * A non-modal surface that grows upward from a Bottom Bar anchor.
26
+ *
27
+ * This is intentionally distinct from windows, popovers, and modal drawers:
28
+ * it retains one fixed shell and one content host across collapsed and expanded
29
+ * presentation. Products that also have a full-page placement can Portal their
30
+ * stable product tree into `contentHostRef` without giving this component
31
+ * ownership of that tree.
32
+ */
33
+ export declare function BottomBarCompanion(props: BottomBarCompanionProps): JSX.Element;
@@ -0,0 +1,233 @@
1
+ import { createComponent as Z, Portal as ce, use as $, insert as le, effect as ue, setAttribute as R, className as p, style as de, memo as fe, template as me } from "solid-js/web";
2
+ import { createSignal as N, createEffect as k, untrack as G, onCleanup as U, Show as he } from "solid-js";
3
+ import { cn as ee } from "../../utils/cn.js";
4
+ var ve = /* @__PURE__ */ me("<section role=region data-floe-bottom-bar-companion=true><div data-floe-bottom-bar-companion-content=true>");
5
+ const we = 544, be = 544, ge = 12, z = {
6
+ top: "--floe-bottom-bar-companion-safe-area-top",
7
+ right: "--floe-bottom-bar-companion-safe-area-right",
8
+ bottom: "--floe-bottom-bar-companion-safe-area-bottom",
9
+ left: "--floe-bottom-bar-companion-safe-area-left"
10
+ };
11
+ function te(e, a) {
12
+ const l = Number(e);
13
+ return Number.isFinite(l) && l > 0 ? l : a;
14
+ }
15
+ function Ce(e, a) {
16
+ const l = Number(e);
17
+ return Number.isFinite(l) && l >= 0 ? l : a;
18
+ }
19
+ function ne(e) {
20
+ if (!e?.isConnected) return null;
21
+ const a = e.getBoundingClientRect();
22
+ return !Number.isFinite(a.left) || !Number.isFinite(a.top) || a.width <= 0 || a.height <= 0 ? null : {
23
+ left: a.left,
24
+ top: a.top,
25
+ width: a.width,
26
+ height: a.height
27
+ };
28
+ }
29
+ function B(e, a) {
30
+ const l = e.ownerDocument.defaultView;
31
+ if (!l) return 0;
32
+ const m = Number.parseFloat(l.getComputedStyle(e).getPropertyValue(a));
33
+ return Number.isFinite(m) && m > 0 ? m : 0;
34
+ }
35
+ function Fe(e) {
36
+ return {
37
+ top: B(e, z.top),
38
+ right: B(e, z.right),
39
+ bottom: B(e, z.bottom),
40
+ left: B(e, z.left)
41
+ };
42
+ }
43
+ function ye(e, a, l, m, C) {
44
+ const F = a.ownerDocument.defaultView, y = F?.visualViewport, v = y?.offsetLeft ?? 0, D = y?.offsetTop ?? 0, q = y?.width ?? F?.innerWidth ?? e.left + e.width, H = y?.height ?? F?.innerHeight ?? e.top + e.height, E = Fe(a), W = v + E.left + C, w = v + q - E.right - C, b = D + E.top + C, h = D + H - E.bottom - C, g = Math.max(0, w - W), d = Math.min(e.width, l, g), P = e.left + (e.width - d) / 2, M = Math.max(W, Math.min(P, w - d)), L = Math.min(e.top + e.height, h), V = Math.max(e.height, L - b), x = Math.min(m, V);
45
+ return {
46
+ left: M,
47
+ top: L - x,
48
+ width: d,
49
+ height: x
50
+ };
51
+ }
52
+ function Ee(e, a) {
53
+ return !!(e && e.left === a.left && e.top === a.top && e.width === a.width && e.height === a.height);
54
+ }
55
+ function Ae(e) {
56
+ return e ? {
57
+ left: `${e.left}px`,
58
+ top: `${e.top}px`,
59
+ width: `${e.width}px`,
60
+ height: `${e.height}px`
61
+ } : {};
62
+ }
63
+ function Me(e) {
64
+ const [a, l] = N(null), [m, C] = N(!1), [F, y] = N(!1), [v, D] = N(null), [q, H] = N(null), [E, W] = N("collapsed");
65
+ let w = null, b = 0, h = 0, g = null, d = null, P = null, M = !1, L = !1;
66
+ const V = (i) => {
67
+ E() !== i && (W(i), e.onPhaseChange?.(i));
68
+ }, x = () => a()?.ownerDocument.defaultView ?? e.anchor?.ownerDocument.defaultView ?? null, oe = () => x()?.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? !1, _ = (i) => {
69
+ const t = e.mount, r = e.anchor, n = !!(t?.isConnected && i && r?.ownerDocument === t.ownerDocument);
70
+ n && l(t);
71
+ const c = a();
72
+ if (c) {
73
+ C(c.isConnected);
74
+ const o = !!(n && c === t);
75
+ return y(o), {
76
+ mount: c,
77
+ ready: o
78
+ };
79
+ }
80
+ return C(!1), y(!1), {
81
+ mount: null,
82
+ ready: !1
83
+ };
84
+ }, ie = (i) => {
85
+ const t = ne(e.anchor);
86
+ t && H(t);
87
+ const {
88
+ mount: r,
89
+ ready: n
90
+ } = _(t), c = t ?? q();
91
+ return !c || !r || !n ? null : i ? ye(c, r, te(e.maxWidth, we), te(e.maxHeight, be), Ce(e.viewportPadding, ge)) : c;
92
+ }, X = () => {
93
+ b && g && g.cancelAnimationFrame(b), h && d && d.cancelAnimationFrame(h), b = 0, h = 0, g = null, d = null, P = null;
94
+ }, K = (i, t) => {
95
+ const r = ie(i);
96
+ if (!r) return;
97
+ const n = v(), c = i ? "expanded" : "collapsed";
98
+ if (!t || oe() || Ee(n, r)) {
99
+ D(r), P = null, V(c);
100
+ return;
101
+ }
102
+ P = ["height", "width", "top", "left"].find((u) => n?.[u] !== r[u]) ?? null, V(i ? "expanding" : "collapsing");
103
+ const o = x();
104
+ if (!o) {
105
+ D(r), V(c);
106
+ return;
107
+ }
108
+ h = o.requestAnimationFrame(() => {
109
+ h = 0, d = null, D(r);
110
+ }), d = o;
111
+ }, j = (i, t) => {
112
+ const r = x();
113
+ if (!r) {
114
+ K(t, i);
115
+ return;
116
+ }
117
+ b && g && g.cancelAnimationFrame(b), h && d && d.cancelAnimationFrame(h), h = 0, d = null, b = r.requestAnimationFrame(() => {
118
+ b = 0, g = null, G(() => K(t, i));
119
+ }), g = r;
120
+ };
121
+ k(() => {
122
+ const i = e.retained, t = e.visible, r = e.open;
123
+ if (e.anchor, e.mount, e.maxWidth, e.maxHeight, e.viewportPadding, !i) {
124
+ X(), l(null), C(!1), y(!1), D(null), H(null), w = null, e.surfaceRef?.(null), e.contentHostRef?.(null), M = !1, L = r;
125
+ return;
126
+ }
127
+ const n = ne(e.anchor);
128
+ if (n && H(n), _(n), !t) {
129
+ M = !1, L = r;
130
+ return;
131
+ }
132
+ const c = !M, o = L !== r;
133
+ M = !0, L = r, j(!c && o, r);
134
+ }), k(() => {
135
+ if (!e.retained) return;
136
+ const i = e.anchor, t = a(), r = e.mount, n = /* @__PURE__ */ new Set(), c = /* @__PURE__ */ new Set();
137
+ for (const s of [i, t, r]) {
138
+ if (!s) continue;
139
+ n.add(s.ownerDocument);
140
+ const f = s.ownerDocument.defaultView;
141
+ f && c.add(f);
142
+ }
143
+ if (c.size === 0 || n.size === 0) return;
144
+ const o = () => j(!1, G(() => e.open)), u = [];
145
+ for (const s of /* @__PURE__ */ new Set([i, t, r])) {
146
+ const f = s?.ownerDocument.defaultView;
147
+ if (!s || !f || typeof f.ResizeObserver != "function") continue;
148
+ const A = new f.ResizeObserver(o);
149
+ A.observe(s), u.push(A);
150
+ }
151
+ let S = i?.isConnected ?? !1, T = t?.isConnected ?? !1, I = r?.isConnected ?? !1;
152
+ const O = [];
153
+ for (const s of n) {
154
+ const f = s.defaultView;
155
+ if (!f || typeof f.MutationObserver != "function" || !s.documentElement) continue;
156
+ const A = new f.MutationObserver(() => {
157
+ const J = i?.isConnected ?? !1, Q = t?.isConnected ?? !1, Y = r?.isConnected ?? !1;
158
+ S === J && T === Q && I === Y || (S = J, T = Q, I = Y, G(o));
159
+ });
160
+ A.observe(s.documentElement, {
161
+ childList: !0,
162
+ subtree: !0
163
+ }), O.push(A);
164
+ }
165
+ for (const s of c)
166
+ s.addEventListener("resize", o), s.visualViewport?.addEventListener("resize", o), s.visualViewport?.addEventListener("scroll", o);
167
+ U(() => {
168
+ for (const s of u) s.disconnect();
169
+ for (const s of O) s.disconnect();
170
+ for (const s of c)
171
+ s.removeEventListener("resize", o), s.visualViewport?.removeEventListener("resize", o), s.visualViewport?.removeEventListener("scroll", o);
172
+ });
173
+ }), k(() => {
174
+ if (!e.retained || !e.visible || !e.open || !m() || !F() || !v()) return;
175
+ const t = a()?.ownerDocument;
176
+ if (!t) return;
177
+ const r = (o) => {
178
+ const u = typeof o.composedPath == "function" ? o.composedPath() : [];
179
+ return w && u.includes(w) || e.anchor && u.includes(e.anchor) ? !0 : e.isOwnedInteraction?.(o) ?? !1;
180
+ }, n = (o) => {
181
+ r(o) || e.onDismiss?.("outside-pointer");
182
+ }, c = (o) => {
183
+ if (o.key !== "Escape" || o.defaultPrevented || o.isComposing || o.keyCode === 229) return;
184
+ const u = t.activeElement;
185
+ !w?.contains(u) && !e.isOwnedInteraction?.(o) || (o.preventDefault(), e.onDismiss?.("escape"));
186
+ };
187
+ t.addEventListener("pointerdown", n, !0), t.addEventListener("keydown", c), U(() => {
188
+ t.removeEventListener("pointerdown", n, !0), t.removeEventListener("keydown", c);
189
+ });
190
+ }), U(() => {
191
+ X(), e.surfaceRef?.(null), e.contentHostRef?.(null);
192
+ });
193
+ const re = (i) => {
194
+ w = i, e.surfaceRef?.(i);
195
+ }, ae = (i) => {
196
+ e.contentHostRef?.(i);
197
+ }, se = (i) => {
198
+ if (i.target !== w || i.propertyName !== P) return;
199
+ const t = e.open ? "expanding" : "collapsing";
200
+ E() === t && (P = null, V(e.open ? "expanded" : "collapsed"));
201
+ };
202
+ return Z(he, {
203
+ get when() {
204
+ return fe(() => !!e.retained)() && a();
205
+ },
206
+ children: (i) => Z(ce, {
207
+ get mount() {
208
+ return i();
209
+ },
210
+ get children() {
211
+ var t = ve(), r = t.firstChild;
212
+ return t.addEventListener("transitionend", se), $((n) => re(n), t), $((n) => ae(n), r), le(r, () => e.children), ue((n) => {
213
+ var c = e.id, o = e.label, u = !e.visible || !m() || !F() || !v() ? "true" : void 0, S = !e.visible || !m() || !F() || !v(), T = ee("floe-bottom-bar-companion", e.class), I = E(), O = e.visible && m() && F() ? "visible" : "hidden", s = v() ? "true" : "false", f = Ae(v()), A = ee("floe-bottom-bar-companion__content", e.contentClass);
214
+ return c !== n.e && R(t, "id", n.e = c), o !== n.t && R(t, "aria-label", n.t = o), u !== n.a && R(t, "aria-hidden", n.a = u), S !== n.o && (t.inert = n.o = S), T !== n.i && p(t, n.i = T), I !== n.n && R(t, "data-companion-phase", n.n = I), O !== n.s && R(t, "data-companion-visibility", n.s = O), s !== n.h && R(t, "data-companion-frame-ready", n.h = s), n.r = de(t, f, n.r), A !== n.d && p(r, n.d = A), n;
215
+ }, {
216
+ e: void 0,
217
+ t: void 0,
218
+ a: void 0,
219
+ o: void 0,
220
+ i: void 0,
221
+ n: void 0,
222
+ s: void 0,
223
+ h: void 0,
224
+ r: void 0,
225
+ d: void 0
226
+ }), t;
227
+ }
228
+ })
229
+ });
230
+ }
231
+ export {
232
+ Me as BottomBarCompanion
233
+ };
@@ -4,6 +4,7 @@ export { Sidebar, SidebarContent, SidebarItemList, SidebarSection, SidebarItem,
4
4
  export { TopBar, type TopBarProps } from './TopBar';
5
5
  export { TopBarIconButton, type TopBarIconButtonProps } from './TopBarIconButton';
6
6
  export { BottomBar, BottomBarItem, StatusIndicator, type BottomBarProps, type BottomBarItemProps, type StatusIndicatorProps } from './BottomBar';
7
+ export { BottomBarCompanion, type BottomBarCompanionDismissReason, type BottomBarCompanionPhase, type BottomBarCompanionProps, } from './BottomBarCompanion';
7
8
  export { MobileTabBar, type MobileTabBarItem, type MobileTabBarProps } from './MobileTabBar';
8
9
  export { ResizeHandle, type ResizeHandleProps } from './ResizeHandle';
9
10
  export { SidebarPane, type SidebarPaneProps } from './SidebarPane';
@@ -4,7 +4,7 @@ export { Input, Textarea, NumberInput, AffixInput, type InputProps, type InputSi
4
4
  export { Dialog, ConfirmDialog, type DialogProps, type ConfirmDialogProps } from './Dialog';
5
5
  export { FloatingWindow, type FloatingWindowProps } from './FloatingWindow';
6
6
  export { createFloatingPresence, type FloatingPresence, type FloatingPresenceOptions, type FloatingPresenceState, } from './floatingPresence';
7
- export { MENU_ITEM_SELECTOR, focusMenuItem, getMenuItems, handleMenuKeyboardNavigation, moveMenuFocus, type MenuDismissReason, type MenuFocusMode, type MenuKeyboardNavigationOptions, } from './menuUtils';
7
+ export { MENU_ITEM_SELECTOR, focusMenuItem, getMenuItems, handleMenuKeyboardNavigation, moveMenuFocus, type MenuDismissReason, type MenuFocusMode, type MenuItemNavigationOptions, type MenuKeyboardNavigationOptions, } from './menuUtils';
8
8
  export { resolveFloatingWindowViewport, type FloatingWindowRect, type FloatingWindowViewport, type FloatingWindowViewportInsets, } from './floatingWindowGeometry';
9
9
  export { Dropdown, Select, type DropdownProps, type DropdownItem, type SelectProps, } from './Dropdown';
10
10
  export { SurfaceFloatingLayer, type SurfaceFloatingLayerPosition, type SurfaceFloatingLayerProps, type SurfaceFloatingLayerSize, } from './SurfaceFloatingLayer';
@@ -10,8 +10,13 @@ export type MenuBoundaryRect = Readonly<{
10
10
  }>;
11
11
  export type MenuFocusMode = 'first' | 'last' | 'selected';
12
12
  export type MenuDismissReason = 'escape' | 'tab' | 'shift-tab';
13
- export type MenuKeyboardNavigationOptions = Readonly<{
13
+ export type MenuItemNavigationOptions = Readonly<{
14
+ includeAriaDisabledItems?: boolean;
15
+ }>;
16
+ export type MenuKeyboardNavigationOptions = MenuItemNavigationOptions & Readonly<{
14
17
  onDismiss: (reason: MenuDismissReason) => void;
18
+ onActivate?: (item: HTMLElement) => void;
19
+ preventDefaultOnTab?: boolean;
15
20
  }>;
16
21
  export declare function resolveViewportMenuBoundaryRect(): MenuBoundaryRect;
17
22
  export declare function clampMenuPosition(anchor: Readonly<{
@@ -33,7 +38,7 @@ export declare function calculateSubmenuPosition(parentRect: DOMRect, submenuRec
33
38
  y: number;
34
39
  };
35
40
  export declare function getWrappedMenuItemIndex(length: number, currentIndex: number, delta: 1 | -1): number | null;
36
- export declare function getMenuItems(root: ParentNode | null | undefined): HTMLElement[];
37
- export declare function focusMenuItem(root: ParentNode | null | undefined, mode?: MenuFocusMode): boolean;
38
- export declare function moveMenuFocus(root: ParentNode | null | undefined, current: HTMLElement | null, delta: 1 | -1): boolean;
41
+ export declare function getMenuItems(root: ParentNode | null | undefined, options?: MenuItemNavigationOptions): HTMLElement[];
42
+ export declare function focusMenuItem(root: ParentNode | null | undefined, mode?: MenuFocusMode, options?: MenuItemNavigationOptions): boolean;
43
+ export declare function moveMenuFocus(root: ParentNode | null | undefined, current: HTMLElement | null, delta: 1 | -1, options?: MenuItemNavigationOptions): boolean;
39
44
  export declare function handleMenuKeyboardNavigation(event: KeyboardEvent, options: MenuKeyboardNavigationOptions): boolean;
@@ -1,5 +1,5 @@
1
- const E = 8, h = '[role="menuitem"]:not([disabled]):not([aria-disabled="true"])';
2
- function M() {
1
+ const E = 8, I = '[role="menuitem"]:not([disabled]):not([aria-disabled="true"])';
2
+ function c() {
3
3
  return typeof window > "u" ? {
4
4
  left: 0,
5
5
  top: 0,
@@ -16,97 +16,102 @@ function M() {
16
16
  height: window.innerHeight
17
17
  };
18
18
  }
19
- function f(t) {
20
- return t ?? M();
19
+ function l(t) {
20
+ return t ?? c();
21
21
  }
22
- function w(t, e, r) {
23
- const n = f(r);
24
- let o = t.x, i = t.y;
25
- return o + e.width > n.right - 8 && (o = n.right - e.width - 8), i + e.height > n.bottom - 8 && (i = n.bottom - e.height - 8), {
26
- x: Math.max(n.left + 8, o),
27
- y: Math.max(n.top + 8, i)
22
+ function A(t, e, a) {
23
+ const n = l(a);
24
+ let i = t.x, r = t.y;
25
+ return i + e.width > n.right - 8 && (i = n.right - e.width - 8), r + e.height > n.bottom - 8 && (r = n.bottom - e.height - 8), {
26
+ x: Math.max(n.left + 8, i),
27
+ y: Math.max(n.top + 8, r)
28
28
  };
29
29
  }
30
- function T(t, e, r, n) {
31
- const o = f(n);
32
- let i;
33
- switch (r) {
30
+ function T(t, e, a, n) {
31
+ const i = l(n);
32
+ let r;
33
+ switch (a) {
34
34
  case "center":
35
- i = t.left + t.width / 2 - e.width / 2;
35
+ r = t.left + t.width / 2 - e.width / 2;
36
36
  break;
37
37
  case "end":
38
- i = t.right - e.width;
38
+ r = t.right - e.width;
39
39
  break;
40
40
  default:
41
- i = t.left;
41
+ r = t.left;
42
42
  }
43
- let a = t.bottom + 4;
44
- if (i + e.width > o.right - 8 && (i = o.right - e.width - 8), i = Math.max(o.left + 8, i), a + e.height > o.bottom - 8) {
45
- const s = t.top - o.top - 8, d = o.bottom - t.bottom - 8;
46
- s > d && s >= e.height ? a = t.top - e.height - 4 : a = o.bottom - e.height - 8;
43
+ let o = t.bottom + 4;
44
+ if (r + e.width > i.right - 8 && (r = i.right - e.width - 8), r = Math.max(i.left + 8, r), o + e.height > i.bottom - 8) {
45
+ const f = t.top - i.top - 8, d = i.bottom - t.bottom - 8;
46
+ f > d && f >= e.height ? o = t.top - e.height - 4 : o = i.bottom - e.height - 8;
47
47
  }
48
- return a = Math.max(o.top + 8, a), { x: i, y: a };
48
+ return o = Math.max(i.top + 8, o), { x: r, y: o };
49
49
  }
50
- function A(t, e, r) {
51
- const n = f(r);
52
- let o = t.right, i = t.top;
53
- if (o + e.width > n.right - 8) {
54
- const a = t.left - e.width;
55
- a >= n.left + 8 ? o = a : o = n.right - e.width - 8;
50
+ function m(t, e, a) {
51
+ const n = l(a);
52
+ let i = t.right, r = t.top;
53
+ if (i + e.width > n.right - 8) {
54
+ const o = t.left - e.width;
55
+ o >= n.left + 8 ? i = o : i = n.right - e.width - 8;
56
56
  }
57
- return i + e.height > n.bottom - 8 && (i = n.bottom - e.height - 8), o = Math.max(n.left + 8, o), i = Math.max(n.top + 8, i), { x: o, y: i };
57
+ return r + e.height > n.bottom - 8 && (r = n.bottom - e.height - 8), i = Math.max(n.left + 8, i), r = Math.max(n.top + 8, r), { x: i, y: r };
58
58
  }
59
- function c(t, e, r) {
60
- return t <= 0 ? null : e < 0 ? r > 0 ? 0 : t - 1 : (e + r + t) % t;
59
+ function M(t, e, a) {
60
+ return t <= 0 ? null : e < 0 ? a > 0 ? 0 : t - 1 : (e + a + t) % t;
61
61
  }
62
- function I(t) {
63
- return !t || typeof HTMLElement > "u" ? [] : Array.from(t.querySelectorAll(h)).filter(
64
- (e) => e instanceof HTMLElement
62
+ function u(t, e = {}) {
63
+ if (!t || typeof HTMLElement > "u") return [];
64
+ const a = e.includeAriaDisabledItems ? '[role="menuitem"]:not([disabled])' : I;
65
+ return Array.from(t.querySelectorAll(a)).filter(
66
+ (n) => n instanceof HTMLElement
65
67
  );
66
68
  }
67
- function l(t, e = "first") {
68
- const r = I(t);
69
- if (!r.length) return !1;
70
- let n = r[0];
71
- return e === "last" ? n = r[r.length - 1] : e === "selected" && (n = r.find((o) => o.getAttribute("data-floe-selected") === "true") ?? r[0]), n.focus(), !0;
72
- }
73
- function u(t, e, r) {
74
- const n = I(t);
69
+ function s(t, e = "first", a = {}) {
70
+ const n = u(t, a);
75
71
  if (!n.length) return !1;
76
- const o = e ? n.indexOf(e) : -1, i = c(n.length, o, r);
77
- return i === null ? !1 : (n[i]?.focus(), !0);
72
+ let i = n[0];
73
+ return e === "last" ? i = n[n.length - 1] : e === "selected" && (i = n.find((r) => r.getAttribute("data-floe-selected") === "true") ?? n[0]), i.focus(), !0;
74
+ }
75
+ function h(t, e, a, n = {}) {
76
+ const i = u(t, n);
77
+ if (!i.length) return !1;
78
+ const r = e ? i.indexOf(e) : -1, o = M(i.length, r, a);
79
+ return o === null ? !1 : (i[o]?.focus(), !0);
78
80
  }
79
- function P(t, e) {
80
- const r = typeof HTMLElement < "u" && t.target instanceof HTMLElement ? t.target : null, n = r?.closest('[role="menu"]');
81
+ function w(t, e) {
82
+ const a = typeof HTMLElement < "u" && t.target instanceof HTMLElement ? t.target : null, n = a?.closest('[role="menu"]');
81
83
  if (!n) return !1;
82
- const o = r?.closest(h);
84
+ const i = u(n, e), r = a?.closest('[role="menuitem"]'), o = r && i.includes(r) ? r : null;
83
85
  switch (t.key) {
84
86
  case "ArrowDown":
85
- return t.preventDefault(), u(n, o, 1), !0;
87
+ return t.preventDefault(), h(n, o, 1, e), !0;
86
88
  case "ArrowUp":
87
- return t.preventDefault(), u(n, o, -1), !0;
89
+ return t.preventDefault(), h(n, o, -1, e), !0;
88
90
  case "Home":
89
- return t.preventDefault(), l(n, "first"), !0;
91
+ return t.preventDefault(), s(n, "first", e), !0;
90
92
  case "End":
91
- return t.preventDefault(), l(n, "last"), !0;
93
+ return t.preventDefault(), s(n, "last", e), !0;
94
+ case "Enter":
95
+ case " ":
96
+ return o ? o.getAttribute("aria-disabled") === "true" ? (t.preventDefault(), !0) : e.onActivate ? (t.preventDefault(), e.onActivate(o), !0) : !1 : !1;
92
97
  case "Escape":
93
98
  return t.preventDefault(), t.stopPropagation(), e.onDismiss("escape"), !0;
94
99
  case "Tab":
95
- return e.onDismiss(t.shiftKey ? "shift-tab" : "tab"), !0;
100
+ return e.preventDefaultOnTab && t.preventDefault(), e.onDismiss(t.shiftKey ? "shift-tab" : "tab"), !0;
96
101
  default:
97
102
  return !1;
98
103
  }
99
104
  }
100
105
  export {
101
- h as MENU_ITEM_SELECTOR,
106
+ I as MENU_ITEM_SELECTOR,
102
107
  E as VIEWPORT_MARGIN,
103
108
  T as calculateMenuPosition,
104
- A as calculateSubmenuPosition,
105
- w as clampMenuPosition,
106
- l as focusMenuItem,
107
- I as getMenuItems,
108
- c as getWrappedMenuItemIndex,
109
- P as handleMenuKeyboardNavigation,
110
- u as moveMenuFocus,
111
- M as resolveViewportMenuBoundaryRect
109
+ m as calculateSubmenuPosition,
110
+ A as clampMenuPosition,
111
+ s as focusMenuItem,
112
+ u as getMenuItems,
113
+ M as getWrappedMenuItemIndex,
114
+ w as handleMenuKeyboardNavigation,
115
+ h as moveMenuFocus,
116
+ c as resolveViewportMenuBoundaryRect
112
117
  };
package/dist/floe.css CHANGED
@@ -687,6 +687,66 @@
687
687
  }
688
688
  }
689
689
 
690
+ .floe-bottom-bar-companion {
691
+ position: fixed;
692
+ z-index: var(--floe-bottom-bar-companion-z-index, 72);
693
+ min-width: 0;
694
+ min-height: 0;
695
+ overflow: hidden;
696
+ border: 1px solid color-mix(in srgb, var(--border) 92%, transparent);
697
+ border-radius: var(--floe-bottom-bar-companion-collapsed-radius, 0.3125rem);
698
+ background: var(--background);
699
+ color: var(--foreground);
700
+ opacity: 1;
701
+ pointer-events: auto;
702
+ transform-origin: bottom center;
703
+ transition:
704
+ left 240ms cubic-bezier(0.22, 1, 0.36, 1),
705
+ top 240ms cubic-bezier(0.22, 1, 0.36, 1),
706
+ width 240ms cubic-bezier(0.22, 1, 0.36, 1),
707
+ height 240ms cubic-bezier(0.22, 1, 0.36, 1),
708
+ border-radius 200ms ease,
709
+ border-color 180ms ease,
710
+ box-shadow 200ms ease,
711
+ opacity 120ms ease;
712
+ will-change: left, top, width, height;
713
+ }
714
+
715
+ .floe-bottom-bar-companion[data-companion-phase='expanding'],
716
+ .floe-bottom-bar-companion[data-companion-phase='expanded'] {
717
+ border-radius: var(--floe-bottom-bar-companion-expanded-radius, 0.5rem);
718
+ border-color: color-mix(in srgb, var(--border) 96%, transparent);
719
+ box-shadow:
720
+ 0 22px 54px -24px color-mix(in srgb, var(--foreground) 34%, transparent),
721
+ 0 6px 18px -12px color-mix(in srgb, var(--foreground) 24%, transparent);
722
+ }
723
+
724
+ .floe-bottom-bar-companion[data-companion-phase='collapsing'] {
725
+ transition-duration: 180ms, 180ms, 180ms, 180ms, 160ms, 140ms, 160ms, 100ms;
726
+ }
727
+
728
+ .floe-bottom-bar-companion[data-companion-visibility='hidden'],
729
+ .floe-bottom-bar-companion[data-companion-frame-ready='false'] {
730
+ visibility: hidden;
731
+ opacity: 0;
732
+ pointer-events: none;
733
+ }
734
+
735
+ .floe-bottom-bar-companion__content {
736
+ width: 100%;
737
+ height: 100%;
738
+ min-width: 0;
739
+ min-height: 0;
740
+ overflow: hidden;
741
+ }
742
+
743
+ @media (prefers-reduced-motion: reduce) {
744
+ .floe-bottom-bar-companion {
745
+ transition: none;
746
+ will-change: auto;
747
+ }
748
+ }
749
+
690
750
  @keyframes glow-pulse {
691
751
  0%, 100% {
692
752
  box-shadow: 0 0 5px var(--primary), 0 0 20px var(--primary);