@flanksource/clicky-ui 0.2.21 → 0.3.1

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.
Files changed (227) hide show
  1. package/dist/components/Combobox.cjs +5 -2
  2. package/dist/components/Combobox.cjs.map +1 -1
  3. package/dist/components/Combobox.d.ts.map +1 -1
  4. package/dist/components/Combobox.js +5 -2
  5. package/dist/components/Combobox.js.map +1 -1
  6. package/dist/components/FilterBar.cjs +280 -253
  7. package/dist/components/FilterBar.cjs.map +1 -1
  8. package/dist/components/FilterBar.d.ts.map +1 -1
  9. package/dist/components/FilterBar.js +280 -253
  10. package/dist/components/FilterBar.js.map +1 -1
  11. package/dist/components/JsonSchemaForm.cjs +3 -1
  12. package/dist/components/JsonSchemaForm.cjs.map +1 -1
  13. package/dist/components/JsonSchemaForm.d.ts.map +1 -1
  14. package/dist/components/JsonSchemaForm.js +4 -2
  15. package/dist/components/JsonSchemaForm.js.map +1 -1
  16. package/dist/components/MultiSelect.cjs +29 -32
  17. package/dist/components/MultiSelect.cjs.map +1 -1
  18. package/dist/components/MultiSelect.d.ts.map +1 -1
  19. package/dist/components/MultiSelect.js +30 -33
  20. package/dist/components/MultiSelect.js.map +1 -1
  21. package/dist/components/TimeRange.cjs +29 -37
  22. package/dist/components/TimeRange.cjs.map +1 -1
  23. package/dist/components/TimeRange.d.ts.map +1 -1
  24. package/dist/components/TimeRange.js +30 -38
  25. package/dist/components/TimeRange.js.map +1 -1
  26. package/dist/components/TreePickerField.cjs +6 -3
  27. package/dist/components/TreePickerField.cjs.map +1 -1
  28. package/dist/components/TreePickerField.d.ts.map +1 -1
  29. package/dist/components/TreePickerField.js +6 -3
  30. package/dist/components/TreePickerField.js.map +1 -1
  31. package/dist/components/icon-menu-picker.cjs +9 -3
  32. package/dist/components/icon-menu-picker.cjs.map +1 -1
  33. package/dist/components/icon-menu-picker.d.ts.map +1 -1
  34. package/dist/components/icon-menu-picker.js +9 -3
  35. package/dist/components/icon-menu-picker.js.map +1 -1
  36. package/dist/components/json-schema-form-fields.cjs +80 -2
  37. package/dist/components/json-schema-form-fields.cjs.map +1 -1
  38. package/dist/components/json-schema-form-fields.d.ts +15 -0
  39. package/dist/components/json-schema-form-fields.d.ts.map +1 -1
  40. package/dist/components/json-schema-form-fields.js +81 -3
  41. package/dist/components/json-schema-form-fields.js.map +1 -1
  42. package/dist/components/json-schema-form-refs.cjs +60 -0
  43. package/dist/components/json-schema-form-refs.cjs.map +1 -0
  44. package/dist/components/json-schema-form-refs.d.ts +3 -0
  45. package/dist/components/json-schema-form-refs.d.ts.map +1 -0
  46. package/dist/components/json-schema-form-refs.js +60 -0
  47. package/dist/components/json-schema-form-refs.js.map +1 -0
  48. package/dist/components/json-schema-form-render.cjs +14 -2
  49. package/dist/components/json-schema-form-render.cjs.map +1 -1
  50. package/dist/components/json-schema-form-render.d.ts.map +1 -1
  51. package/dist/components/json-schema-form-render.js +15 -3
  52. package/dist/components/json-schema-form-render.js.map +1 -1
  53. package/dist/components/json-schema-form-resolve.cjs +5 -0
  54. package/dist/components/json-schema-form-resolve.cjs.map +1 -1
  55. package/dist/components/json-schema-form-resolve.d.ts.map +1 -1
  56. package/dist/components/json-schema-form-resolve.js +5 -0
  57. package/dist/components/json-schema-form-resolve.js.map +1 -1
  58. package/dist/components/json-schema-form-template-menu.cjs +83 -0
  59. package/dist/components/json-schema-form-template-menu.cjs.map +1 -0
  60. package/dist/components/json-schema-form-template-menu.d.ts +15 -0
  61. package/dist/components/json-schema-form-template-menu.d.ts.map +1 -0
  62. package/dist/components/json-schema-form-template-menu.js +83 -0
  63. package/dist/components/json-schema-form-template-menu.js.map +1 -0
  64. package/dist/components/json-schema-form-template.cjs +18 -0
  65. package/dist/components/json-schema-form-template.cjs.map +1 -0
  66. package/dist/components/json-schema-form-template.d.ts +13 -0
  67. package/dist/components/json-schema-form-template.d.ts.map +1 -0
  68. package/dist/components/json-schema-form-template.js +18 -0
  69. package/dist/components/json-schema-form-template.js.map +1 -0
  70. package/dist/components/json-schema-form-types.d.ts +5 -1
  71. package/dist/components/json-schema-form-types.d.ts.map +1 -1
  72. package/dist/components.cjs +4 -0
  73. package/dist/components.cjs.map +1 -1
  74. package/dist/components.d.ts +3 -0
  75. package/dist/components.d.ts.map +1 -1
  76. package/dist/components.js +4 -0
  77. package/dist/components.js.map +1 -1
  78. package/dist/data/Clicky.cjs +6 -9
  79. package/dist/data/Clicky.cjs.map +1 -1
  80. package/dist/data/Clicky.d.ts.map +1 -1
  81. package/dist/data/Clicky.js +6 -9
  82. package/dist/data/Clicky.js.map +1 -1
  83. package/dist/data/DataTable.cjs +23 -17
  84. package/dist/data/DataTable.cjs.map +1 -1
  85. package/dist/data/DataTable.d.ts +7 -1
  86. package/dist/data/DataTable.d.ts.map +1 -1
  87. package/dist/data/DataTable.js +24 -18
  88. package/dist/data/DataTable.js.map +1 -1
  89. package/dist/data/Gauge.cjs +37 -4
  90. package/dist/data/Gauge.cjs.map +1 -1
  91. package/dist/data/Gauge.d.ts +6 -1
  92. package/dist/data/Gauge.d.ts.map +1 -1
  93. package/dist/data/Gauge.js +37 -4
  94. package/dist/data/Gauge.js.map +1 -1
  95. package/dist/data/TimeseriesCoreBars.cjs +127 -29
  96. package/dist/data/TimeseriesCoreBars.cjs.map +1 -1
  97. package/dist/data/TimeseriesCoreBars.d.ts +11 -1
  98. package/dist/data/TimeseriesCoreBars.d.ts.map +1 -1
  99. package/dist/data/TimeseriesCoreBars.js +128 -30
  100. package/dist/data/TimeseriesCoreBars.js.map +1 -1
  101. package/dist/data/TimeseriesGauge.cjs +105 -47
  102. package/dist/data/TimeseriesGauge.cjs.map +1 -1
  103. package/dist/data/TimeseriesGauge.d.ts +6 -1
  104. package/dist/data/TimeseriesGauge.d.ts.map +1 -1
  105. package/dist/data/TimeseriesGauge.js +105 -47
  106. package/dist/data/TimeseriesGauge.js.map +1 -1
  107. package/dist/data/WorkloadCard.cjs +371 -0
  108. package/dist/data/WorkloadCard.cjs.map +1 -0
  109. package/dist/data/WorkloadCard.d.ts +24 -0
  110. package/dist/data/WorkloadCard.d.ts.map +1 -0
  111. package/dist/data/WorkloadCard.js +371 -0
  112. package/dist/data/WorkloadCard.js.map +1 -0
  113. package/dist/data/WorkloadCard.model.cjs +51 -0
  114. package/dist/data/WorkloadCard.model.cjs.map +1 -0
  115. package/dist/data/WorkloadCard.model.d.ts +53 -0
  116. package/dist/data/WorkloadCard.model.d.ts.map +1 -0
  117. package/dist/data/WorkloadCard.model.js +51 -0
  118. package/dist/data/WorkloadCard.model.js.map +1 -0
  119. package/dist/data/ai/ChatFab.cjs +3 -1
  120. package/dist/data/ai/ChatFab.cjs.map +1 -1
  121. package/dist/data/ai/ChatFab.d.ts.map +1 -1
  122. package/dist/data/ai/ChatFab.js +3 -1
  123. package/dist/data/ai/ChatFab.js.map +1 -1
  124. package/dist/data/ai/ChatWindow.cjs +25 -7
  125. package/dist/data/ai/ChatWindow.cjs.map +1 -1
  126. package/dist/data/ai/ChatWindow.d.ts +11 -3
  127. package/dist/data/ai/ChatWindow.d.ts.map +1 -1
  128. package/dist/data/ai/ChatWindow.js +26 -8
  129. package/dist/data/ai/ChatWindow.js.map +1 -1
  130. package/dist/data/ai/chat-window-context.cjs +2 -1
  131. package/dist/data/ai/chat-window-context.cjs.map +1 -1
  132. package/dist/data/ai/chat-window-context.d.ts +1 -1
  133. package/dist/data/ai/chat-window-context.d.ts.map +1 -1
  134. package/dist/data/ai/chat-window-context.js +2 -1
  135. package/dist/data/ai/chat-window-context.js.map +1 -1
  136. package/dist/data/ai/context.cjs.map +1 -1
  137. package/dist/data/ai/context.d.ts +3 -0
  138. package/dist/data/ai/context.d.ts.map +1 -1
  139. package/dist/data/ai/context.js.map +1 -1
  140. package/dist/data/chat/Chat.cjs +9 -1
  141. package/dist/data/chat/Chat.cjs.map +1 -1
  142. package/dist/data/chat/Chat.d.ts +4 -2
  143. package/dist/data/chat/Chat.d.ts.map +1 -1
  144. package/dist/data/chat/Chat.js +10 -2
  145. package/dist/data/chat/Chat.js.map +1 -1
  146. package/dist/data/chat/Message.cjs +13 -4
  147. package/dist/data/chat/Message.cjs.map +1 -1
  148. package/dist/data/chat/Message.d.ts +4 -2
  149. package/dist/data/chat/Message.d.ts.map +1 -1
  150. package/dist/data/chat/Message.js +13 -4
  151. package/dist/data/chat/Message.js.map +1 -1
  152. package/dist/data/chat/ToolCall.cjs +9 -4
  153. package/dist/data/chat/ToolCall.cjs.map +1 -1
  154. package/dist/data/chat/ToolCall.d.ts +3 -2
  155. package/dist/data/chat/ToolCall.d.ts.map +1 -1
  156. package/dist/data/chat/ToolCall.js +9 -4
  157. package/dist/data/chat/ToolCall.js.map +1 -1
  158. package/dist/data/chat/index.d.ts +1 -1
  159. package/dist/data/chat/index.d.ts.map +1 -1
  160. package/dist/data/chat/types.cjs.map +1 -1
  161. package/dist/data/chat/types.d.ts +7 -0
  162. package/dist/data/chat/types.d.ts.map +1 -1
  163. package/dist/data/chat/types.js.map +1 -1
  164. package/dist/data/version-info.cjs +2 -2
  165. package/dist/data/version-info.js +2 -2
  166. package/dist/data.cjs +7 -0
  167. package/dist/data.cjs.map +1 -1
  168. package/dist/data.d.ts +5 -3
  169. package/dist/data.d.ts.map +1 -1
  170. package/dist/data.js +8 -1
  171. package/dist/data.js.map +1 -1
  172. package/dist/hooks/use-sort.cjs +19 -10
  173. package/dist/hooks/use-sort.cjs.map +1 -1
  174. package/dist/hooks/use-sort.d.ts +3 -0
  175. package/dist/hooks/use-sort.d.ts.map +1 -1
  176. package/dist/hooks/use-sort.js +19 -10
  177. package/dist/hooks/use-sort.js.map +1 -1
  178. package/dist/index.cjs +11 -0
  179. package/dist/index.cjs.map +1 -1
  180. package/dist/index.js +11 -0
  181. package/dist/index.js.map +1 -1
  182. package/dist/layout/AppShell.cjs +6 -10
  183. package/dist/layout/AppShell.cjs.map +1 -1
  184. package/dist/layout/AppShell.d.ts +2 -0
  185. package/dist/layout/AppShell.d.ts.map +1 -1
  186. package/dist/layout/AppShell.js +6 -10
  187. package/dist/layout/AppShell.js.map +1 -1
  188. package/dist/overlay/DropdownMenu.cjs +50 -38
  189. package/dist/overlay/DropdownMenu.cjs.map +1 -1
  190. package/dist/overlay/DropdownMenu.d.ts +5 -1
  191. package/dist/overlay/DropdownMenu.d.ts.map +1 -1
  192. package/dist/overlay/DropdownMenu.js +50 -38
  193. package/dist/overlay/DropdownMenu.js.map +1 -1
  194. package/dist/overlay/HoverCard.cjs +7 -2
  195. package/dist/overlay/HoverCard.cjs.map +1 -1
  196. package/dist/overlay/HoverCard.d.ts.map +1 -1
  197. package/dist/overlay/HoverCard.js +7 -2
  198. package/dist/overlay/HoverCard.js.map +1 -1
  199. package/dist/overlay/Modal.cjs +20 -17
  200. package/dist/overlay/Modal.cjs.map +1 -1
  201. package/dist/overlay/Modal.d.ts.map +1 -1
  202. package/dist/overlay/Modal.js +21 -18
  203. package/dist/overlay/Modal.js.map +1 -1
  204. package/dist/overlay/Toast.cjs +9 -1
  205. package/dist/overlay/Toast.cjs.map +1 -1
  206. package/dist/overlay/Toast.d.ts.map +1 -1
  207. package/dist/overlay/Toast.js +9 -1
  208. package/dist/overlay/Toast.js.map +1 -1
  209. package/dist/overlay/modalStack.cjs +53 -1
  210. package/dist/overlay/modalStack.cjs.map +1 -1
  211. package/dist/overlay/modalStack.d.ts +12 -0
  212. package/dist/overlay/modalStack.d.ts.map +1 -1
  213. package/dist/overlay/modalStack.js +54 -2
  214. package/dist/overlay/modalStack.js.map +1 -1
  215. package/dist/overlay/zIndex.cjs +20 -0
  216. package/dist/overlay/zIndex.cjs.map +1 -0
  217. package/dist/overlay/zIndex.d.ts +39 -0
  218. package/dist/overlay/zIndex.d.ts.map +1 -0
  219. package/dist/overlay/zIndex.js +20 -0
  220. package/dist/overlay/zIndex.js.map +1 -0
  221. package/dist/rpc/AcceptPicker.cjs +28 -26
  222. package/dist/rpc/AcceptPicker.cjs.map +1 -1
  223. package/dist/rpc/AcceptPicker.d.ts.map +1 -1
  224. package/dist/rpc/AcceptPicker.js +29 -27
  225. package/dist/rpc/AcceptPicker.js.map +1 -1
  226. package/dist/styles.css +1 -1
  227. package/package.json +1 -1
@@ -1,10 +1,12 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useMemo, useRef, useState, useLayoutEffect, useCallback, useEffect, createContext, useContext, isValidElement, cloneElement } from "react";
3
+ import { useMergeRefs, FloatingPortal, FloatingFocusManager, useFloating, autoUpdate, offset, flip, shift, useInteractions, useClick, useDismiss, useRole } from "@floating-ui/react";
3
4
  import { FilterPill } from "../data/FilterPill.js";
4
5
  import { isFilterBarFilterActive, clearFilterBarFilter } from "./filter-bar-utils.js";
5
6
  import { Icon, LabelIcon } from "../data/Icon.js";
6
7
  import { formatDateTimeRelative } from "../data/cells/timestamp-format.js";
7
8
  import { cn } from "../lib/utils.js";
9
+ import { useFloatingZIndex, useEscapeLayer } from "../overlay/modalStack.js";
8
10
  import { Button } from "./button.js";
9
11
  import { Combobox } from "./Combobox.js";
10
12
  import { DateTimePicker } from "./DateTimePicker.js";
@@ -260,7 +262,6 @@ function OverflowFiltersMenu({
260
262
  activeHidden,
261
263
  onApply
262
264
  }) {
263
- const rootRef = useRef(null);
264
265
  const [open, setOpen] = useState(false);
265
266
  const [stagedValues, setStagedValues] = useState({});
266
267
  const hasHidden = filters.length > 0;
@@ -268,15 +269,19 @@ function OverflowFiltersMenu({
268
269
  () => filters.map((filter) => filter.key).join("\0"),
269
270
  [filters]
270
271
  );
271
- const openOverflowMenu = useCallback(() => {
272
- setStagedValues(createFilterValueMap(filters));
273
- setOpen(true);
274
- }, [filters]);
272
+ const { refs, floatingStyles, context, floatingZ, getReferenceProps, getFloatingProps } = useAnchoredPopup(
273
+ open,
274
+ (next) => {
275
+ setStagedValues(createFilterValueMap(filters));
276
+ setOpen(next);
277
+ },
278
+ "bottom-end"
279
+ );
275
280
  const closeOverflowMenu = useCallback(() => {
276
281
  setStagedValues(createFilterValueMap(filters));
277
282
  setOpen(false);
278
283
  }, [filters]);
279
- useDismissablePopup(open, rootRef, triggerRef, closeOverflowMenu);
284
+ const setTriggerRef = useMergeRefs([refs.setReference, triggerRef]);
280
285
  useEffect(() => {
281
286
  if (!hasHidden) closeOverflowMenu();
282
287
  }, [closeOverflowMenu, hasHidden]);
@@ -297,47 +302,39 @@ function OverflowFiltersMenu({
297
302
  return /* @__PURE__ */ jsxs(
298
303
  "div",
299
304
  {
300
- ref: rootRef,
301
- className: cn("relative shrink-0", !hasHidden && "invisible pointer-events-none"),
305
+ className: cn("inline-flex shrink-0", !hasHidden && "invisible pointer-events-none"),
302
306
  "aria-hidden": !hasHidden || void 0,
303
307
  children: [
304
308
  /* @__PURE__ */ jsxs(
305
309
  Button,
306
310
  {
307
- ref: triggerRef,
311
+ ref: setTriggerRef,
308
312
  type: "button",
309
313
  variant: "outline",
310
314
  size: "sm",
311
315
  "aria-label": "More filters",
312
- "aria-haspopup": "dialog",
313
- "aria-expanded": hasHidden ? open : false,
314
316
  tabIndex: hasHidden ? 0 : -1,
315
317
  title: hasHidden ? "More filters" : void 0,
316
- onClick: () => {
317
- if (!hasHidden) return;
318
- if (open) {
319
- closeOverflowMenu();
320
- } else {
321
- openOverflowMenu();
322
- }
323
- },
324
318
  className: cn(
325
319
  "h-8 min-w-0 gap-1.5 px-2 text-xs font-normal",
326
320
  activeHidden > 0 && "border-primary/40 text-primary"
327
321
  ),
322
+ ...getReferenceProps(),
328
323
  children: [
329
324
  /* @__PURE__ */ jsx(Icon, { icon: UiFilter, className: "text-[14px]" }),
330
325
  activeHidden > 0 && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground", children: activeHidden })
331
326
  ]
332
327
  }
333
328
  ),
334
- hasHidden && open && /* @__PURE__ */ jsxs(
329
+ hasHidden && open && /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(FloatingFocusManager, { context, modal: false, children: /* @__PURE__ */ jsxs(
335
330
  "div",
336
331
  {
332
+ ref: refs.setFloating,
337
333
  role: "dialog",
338
334
  "aria-label": "Overflow filters",
339
- className: "fixed inset-x-2 bottom-4 top-16 z-50 flex flex-col overflow-hidden rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-lg shadow-black/10 md:absolute md:bottom-auto md:left-auto md:right-0 md:top-[calc(100%+0.375rem)] md:block md:w-[min(34rem,calc(100vw-2rem))] md:overflow-visible md:shadow-black/5",
340
- onClick: (event) => event.stopPropagation(),
335
+ style: { ...floatingStyles, zIndex: floatingZ },
336
+ className: "flex max-h-[min(34rem,calc(100vh-2rem))] w-[min(34rem,calc(100vw-2rem))] flex-col overflow-hidden rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-lg shadow-black/10",
337
+ ...getFloatingProps(),
341
338
  children: [
342
339
  /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-2 px-1", children: [
343
340
  /* @__PURE__ */ jsx("div", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: "Filters" }),
@@ -365,7 +362,7 @@ function OverflowFiltersMenu({
365
362
  )
366
363
  ] })
367
364
  ] }),
368
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 divide-y divide-border/70 overflow-y-auto rounded-md border border-border/70 md:overflow-visible", children: stagedFilters.map((filter) => {
365
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 divide-y divide-border/70 overflow-y-auto rounded-md border border-border/70", children: stagedFilters.map((filter) => {
369
366
  const active = isFilterBarFilterActive(filter);
370
367
  return /* @__PURE__ */ jsxs(
371
368
  "div",
@@ -440,7 +437,7 @@ function OverflowFiltersMenu({
440
437
  ] })
441
438
  ]
442
439
  }
443
- )
440
+ ) }) })
444
441
  ]
445
442
  }
446
443
  );
@@ -852,10 +849,8 @@ function LookupMultiFilterField({
852
849
  );
853
850
  }
854
851
  function NumberFilterField({ filter, grow }) {
855
- const rootRef = useRef(null);
856
- const triggerRef = useRef(null);
857
852
  const [open, setOpen] = useState(false);
858
- useDismissablePopup(open, rootRef, triggerRef, () => setOpen(false));
853
+ const popup = useAnchoredPopup(open, setOpen);
859
854
  const bounds = resolveNumberFilterBounds(filter);
860
855
  const [draft, setDraft] = useDebouncedNumberDraft(filter.value, filter.onChange);
861
856
  const sliderMin = clampNumber(parseFilterNumber(draft.min) ?? bounds.min, bounds.min, bounds.max);
@@ -866,10 +861,9 @@ function NumberFilterField({ filter, grow }) {
866
861
  return /* @__PURE__ */ jsxs(
867
862
  "div",
868
863
  {
869
- ref: rootRef,
870
864
  title: filter.description,
871
865
  className: cn(
872
- "relative min-w-0",
866
+ "inline-flex min-w-0",
873
867
  grow ? "min-w-[8rem] max-w-[12rem] flex-1" : "shrink-0",
874
868
  filter.disabled && "opacity-60",
875
869
  filter.className
@@ -878,20 +872,18 @@ function NumberFilterField({ filter, grow }) {
878
872
  /* @__PURE__ */ jsxs(
879
873
  Button,
880
874
  {
881
- ref: triggerRef,
875
+ ref: popup.refs.setReference,
882
876
  type: "button",
883
877
  variant: "outline",
884
878
  size: "sm",
885
879
  "aria-label": `${filter.label} filter`,
886
- "aria-haspopup": "dialog",
887
- "aria-expanded": open,
888
880
  disabled: filter.disabled,
889
- onClick: () => setOpen((current) => !current),
890
881
  className: cn(
891
882
  "min-w-0 gap-2 font-normal",
892
883
  grow ? "w-full max-w-[12rem] justify-between" : "w-auto max-w-[9.5rem] px-2.5",
893
884
  summary === filter.label && "text-muted-foreground"
894
885
  ),
886
+ ...popup.getReferenceProps(),
895
887
  children: [
896
888
  /* @__PURE__ */ jsx(LabelIcon, { icon: filter.icon, className: "text-[14px] text-muted-foreground" }),
897
889
  /* @__PURE__ */ jsx("span", { className: "truncate", children: summary }),
@@ -899,88 +891,99 @@ function NumberFilterField({ filter, grow }) {
899
891
  ]
900
892
  }
901
893
  ),
902
- open && /* @__PURE__ */ jsxs("div", { className: "absolute left-0 top-[calc(100%+0.375rem)] z-50 min-w-[18rem] max-w-[22rem] rounded-md border border-border bg-popover p-3 text-popover-foreground shadow-lg shadow-black/5", children: [
903
- /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [
904
- /* @__PURE__ */ jsx("div", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: filter.label }),
905
- /* @__PURE__ */ jsx(
906
- "button",
907
- {
908
- type: "button",
909
- className: "text-[10px] text-primary disabled:text-muted-foreground",
910
- onClick: () => {
911
- setDraft({});
912
- filter.onChange({});
913
- },
914
- disabled: !String(draft.min ?? "").trim() && !String(draft.max ?? "").trim(),
915
- children: "Clear all"
916
- }
917
- )
918
- ] }),
919
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
920
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
921
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-[11px] text-muted-foreground", children: [
922
- /* @__PURE__ */ jsx("span", { children: formatNumberValue(bounds.min, filter) }),
923
- /* @__PURE__ */ jsx("span", { children: formatNumberValue(bounds.max, filter) })
924
- ] }),
925
- /* @__PURE__ */ jsx("div", { className: "relative h-6", children: /* @__PURE__ */ jsx(
926
- RangeSlider,
927
- {
928
- min: bounds.min,
929
- max: bounds.max,
930
- step: bounds.step,
931
- value: [activeMin, activeMax],
932
- ariaLabelMin: `${filter.label} minimum slider`,
933
- ariaLabelMax: `${filter.label} maximum slider`,
934
- onChange: ([nextMin, nextMax]) => setDraft(numberFilterValueFromSlider([nextMin, nextMax], bounds))
935
- }
936
- ) })
937
- ] }),
938
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
939
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
940
- /* @__PURE__ */ jsx("label", { className: "text-[10px] text-muted-foreground", children: "Min" }),
894
+ open && /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(FloatingFocusManager, { context: popup.context, modal: false, children: /* @__PURE__ */ jsxs(
895
+ "div",
896
+ {
897
+ ref: popup.refs.setFloating,
898
+ role: "dialog",
899
+ "aria-label": filter.label,
900
+ style: { ...popup.floatingStyles, zIndex: popup.floatingZ },
901
+ className: "min-w-[18rem] max-w-[22rem] rounded-md border border-border bg-popover p-3 text-popover-foreground shadow-lg shadow-black/5",
902
+ ...popup.getFloatingProps(),
903
+ children: [
904
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [
905
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: filter.label }),
941
906
  /* @__PURE__ */ jsx(
942
- "input",
907
+ "button",
943
908
  {
944
- type: "number",
945
- inputMode: "decimal",
946
- step: bounds.step,
947
- "aria-label": `${filter.label} minimum`,
948
- className: "h-8 w-full rounded-md border border-input bg-background px-2 text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring",
949
- ...filter.minPlaceholder !== void 0 ? { placeholder: filter.minPlaceholder } : {},
950
- value: draft.min ?? "",
951
- onChange: (event) => setDraft(
952
- normalizeNumberFilterValue(
953
- { min: event.target.value, max: draft.max ?? "" },
954
- "min-input"
955
- )
956
- )
909
+ type: "button",
910
+ className: "text-[10px] text-primary disabled:text-muted-foreground",
911
+ onClick: () => {
912
+ setDraft({});
913
+ filter.onChange({});
914
+ },
915
+ disabled: !String(draft.min ?? "").trim() && !String(draft.max ?? "").trim(),
916
+ children: "Clear all"
957
917
  }
958
918
  )
959
919
  ] }),
960
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
961
- /* @__PURE__ */ jsx("label", { className: "text-[10px] text-muted-foreground", children: "Max" }),
962
- /* @__PURE__ */ jsx(
963
- "input",
964
- {
965
- type: "number",
966
- inputMode: "decimal",
967
- step: bounds.step,
968
- "aria-label": `${filter.label} maximum`,
969
- className: "h-8 w-full rounded-md border border-input bg-background px-2 text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring",
970
- ...filter.maxPlaceholder !== void 0 ? { placeholder: filter.maxPlaceholder } : {},
971
- value: draft.max ?? "",
972
- onChange: (event) => setDraft(
973
- normalizeNumberFilterValue(
974
- { min: draft.min ?? "", max: event.target.value },
975
- "max-input"
976
- )
920
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
921
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
922
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-[11px] text-muted-foreground", children: [
923
+ /* @__PURE__ */ jsx("span", { children: formatNumberValue(bounds.min, filter) }),
924
+ /* @__PURE__ */ jsx("span", { children: formatNumberValue(bounds.max, filter) })
925
+ ] }),
926
+ /* @__PURE__ */ jsx("div", { className: "relative h-6", children: /* @__PURE__ */ jsx(
927
+ RangeSlider,
928
+ {
929
+ min: bounds.min,
930
+ max: bounds.max,
931
+ step: bounds.step,
932
+ value: [activeMin, activeMax],
933
+ ariaLabelMin: `${filter.label} minimum slider`,
934
+ ariaLabelMax: `${filter.label} maximum slider`,
935
+ onChange: ([nextMin, nextMax]) => setDraft(numberFilterValueFromSlider([nextMin, nextMax], bounds))
936
+ }
937
+ ) })
938
+ ] }),
939
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
940
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
941
+ /* @__PURE__ */ jsx("label", { className: "text-[10px] text-muted-foreground", children: "Min" }),
942
+ /* @__PURE__ */ jsx(
943
+ "input",
944
+ {
945
+ type: "number",
946
+ inputMode: "decimal",
947
+ step: bounds.step,
948
+ "aria-label": `${filter.label} minimum`,
949
+ className: "h-8 w-full rounded-md border border-input bg-background px-2 text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring",
950
+ ...filter.minPlaceholder !== void 0 ? { placeholder: filter.minPlaceholder } : {},
951
+ value: draft.min ?? "",
952
+ onChange: (event) => setDraft(
953
+ normalizeNumberFilterValue(
954
+ { min: event.target.value, max: draft.max ?? "" },
955
+ "min-input"
956
+ )
957
+ )
958
+ }
977
959
  )
978
- }
979
- )
960
+ ] }),
961
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
962
+ /* @__PURE__ */ jsx("label", { className: "text-[10px] text-muted-foreground", children: "Max" }),
963
+ /* @__PURE__ */ jsx(
964
+ "input",
965
+ {
966
+ type: "number",
967
+ inputMode: "decimal",
968
+ step: bounds.step,
969
+ "aria-label": `${filter.label} maximum`,
970
+ className: "h-8 w-full rounded-md border border-input bg-background px-2 text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring",
971
+ ...filter.maxPlaceholder !== void 0 ? { placeholder: filter.maxPlaceholder } : {},
972
+ value: draft.max ?? "",
973
+ onChange: (event) => setDraft(
974
+ normalizeNumberFilterValue(
975
+ { min: draft.min ?? "", max: event.target.value },
976
+ "max-input"
977
+ )
978
+ )
979
+ }
980
+ )
981
+ ] })
982
+ ] })
980
983
  ] })
981
- ] })
982
- ] })
983
- ] })
984
+ ]
985
+ }
986
+ ) }) })
984
987
  ]
985
988
  }
986
989
  );
@@ -1089,14 +1092,16 @@ function NumberFilterPanel({
1089
1092
  }
1090
1093
  );
1091
1094
  }
1095
+ const MULTI_FILTER_OPTION_ROW_HEIGHT = 30;
1096
+ const MULTI_FILTER_OPTION_LIST_HEIGHT = 288;
1097
+ const MULTI_FILTER_OPTION_OVERSCAN = 6;
1092
1098
  function MultiFilterField({ filter, grow }) {
1093
- const rootRef = useRef(null);
1094
- const triggerRef = useRef(null);
1095
1099
  const [open, setOpen] = useState(false);
1096
1100
  const [optionQuery, setOptionQuery] = useState("");
1101
+ const [optionScrollTop, setOptionScrollTop] = useState(0);
1097
1102
  const [searchOptions, setSearchOptions] = useState([]);
1098
1103
  const [draft, setDraft] = useDebouncedMultiDraft(filter.value, filter.onChange);
1099
- useDismissablePopup(open, rootRef, triggerRef, () => setOpen(false));
1104
+ const popup = useAnchoredPopup(open, setOpen);
1100
1105
  const onSearch = filter.onSearch;
1101
1106
  useEffect(() => {
1102
1107
  if (!onSearch) return;
@@ -1139,6 +1144,25 @@ function MultiFilterField({ filter, grow }) {
1139
1144
  (option) => multiSelectOptionText(option).toLowerCase().includes(lowered)
1140
1145
  );
1141
1146
  }, [baseOptions, searchOptions, optionQuery, onSearch, draft]);
1147
+ useEffect(() => {
1148
+ setOptionScrollTop(0);
1149
+ }, [optionQuery]);
1150
+ const optionWindow = useMemo(() => {
1151
+ const visibleRows = Math.ceil(MULTI_FILTER_OPTION_LIST_HEIGHT / MULTI_FILTER_OPTION_ROW_HEIGHT);
1152
+ const start = Math.max(
1153
+ 0,
1154
+ Math.floor(optionScrollTop / MULTI_FILTER_OPTION_ROW_HEIGHT) - MULTI_FILTER_OPTION_OVERSCAN
1155
+ );
1156
+ const end = Math.min(
1157
+ visibleOptions.length,
1158
+ start + visibleRows + MULTI_FILTER_OPTION_OVERSCAN * 2
1159
+ );
1160
+ return {
1161
+ options: visibleOptions.slice(start, end),
1162
+ totalHeight: visibleOptions.length * MULTI_FILTER_OPTION_ROW_HEIGHT,
1163
+ top: start * MULTI_FILTER_OPTION_ROW_HEIGHT
1164
+ };
1165
+ }, [optionScrollTop, visibleOptions]);
1142
1166
  const trimmedQuery = optionQuery.trim();
1143
1167
  const canAddCustom = allowCustomValue && trimmedQuery !== "" && !visibleOptions.some((o) => o.value === trimmedQuery);
1144
1168
  const addCustom = () => {
@@ -1149,10 +1173,9 @@ function MultiFilterField({ filter, grow }) {
1149
1173
  return /* @__PURE__ */ jsxs(
1150
1174
  "div",
1151
1175
  {
1152
- ref: rootRef,
1153
1176
  title: filter.description,
1154
1177
  className: cn(
1155
- "relative min-w-0",
1178
+ "inline-flex min-w-0",
1156
1179
  grow ? "min-w-[8rem] max-w-[12rem] flex-1" : "shrink-0",
1157
1180
  filter.disabled && "opacity-60",
1158
1181
  filter.className
@@ -1161,20 +1184,18 @@ function MultiFilterField({ filter, grow }) {
1161
1184
  /* @__PURE__ */ jsxs(
1162
1185
  Button,
1163
1186
  {
1164
- ref: triggerRef,
1187
+ ref: popup.refs.setReference,
1165
1188
  type: "button",
1166
1189
  variant: "outline",
1167
1190
  size: "sm",
1168
1191
  "aria-label": `${filter.label} filter`,
1169
- "aria-haspopup": "dialog",
1170
- "aria-expanded": open,
1171
1192
  disabled: filter.disabled,
1172
- onClick: () => setOpen((current) => !current),
1173
1193
  className: cn(
1174
1194
  "min-w-0 gap-2 font-normal",
1175
1195
  grow ? "w-full max-w-[12rem] justify-between" : "w-auto max-w-[8.5rem] px-2.5",
1176
1196
  summary === filter.label && "text-muted-foreground"
1177
1197
  ),
1198
+ ...popup.getReferenceProps(),
1178
1199
  children: [
1179
1200
  /* @__PURE__ */ jsx(LabelIcon, { icon: filter.icon, className: "text-[14px] text-muted-foreground" }),
1180
1201
  /* @__PURE__ */ jsx("span", { className: "truncate", children: summary }),
@@ -1182,97 +1203,118 @@ function MultiFilterField({ filter, grow }) {
1182
1203
  ]
1183
1204
  }
1184
1205
  ),
1185
- open && /* @__PURE__ */ jsxs("div", { className: "absolute left-0 top-[calc(100%+0.375rem)] z-50 min-w-[18rem] max-w-[22rem] rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-lg shadow-black/5", children: [
1186
- /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
1187
- /* @__PURE__ */ jsx("div", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: filter.label }),
1188
- /* @__PURE__ */ jsx(
1189
- "button",
1190
- {
1191
- type: "button",
1192
- className: "text-[10px] text-primary disabled:text-muted-foreground",
1193
- onClick: () => setDraft({}),
1194
- disabled: Object.keys(draft).length === 0,
1195
- children: "Clear all"
1196
- }
1197
- )
1198
- ] }),
1199
- showOptionFilter && /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2 rounded-md border border-input bg-background px-2", children: [
1200
- /* @__PURE__ */ jsx(Icon, { icon: UiSearch, className: "shrink-0 text-muted-foreground" }),
1201
- /* @__PURE__ */ jsx(
1202
- "input",
1203
- {
1204
- type: "search",
1205
- "aria-label": `Filter ${filter.label} options`,
1206
- className: "h-8 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground",
1207
- placeholder: `Filter ${filter.label.toLowerCase()}`,
1208
- value: optionQuery,
1209
- onChange: (event) => setOptionQuery(event.target.value)
1210
- }
1211
- )
1212
- ] }),
1213
- /* @__PURE__ */ jsxs("div", { className: "max-h-72 space-y-0.5 overflow-auto", children: [
1214
- visibleOptions.map((option) => {
1215
- const mode = draft[option.value] ?? "neutral";
1216
- const title = option.title ?? multiSelectOptionText(option);
1217
- return /* @__PURE__ */ jsx(
1206
+ open && /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(FloatingFocusManager, { context: popup.context, modal: false, children: /* @__PURE__ */ jsxs(
1207
+ "div",
1208
+ {
1209
+ ref: popup.refs.setFloating,
1210
+ role: "dialog",
1211
+ "aria-label": filter.label,
1212
+ style: { ...popup.floatingStyles, zIndex: popup.floatingZ },
1213
+ className: "min-w-[18rem] max-w-[22rem] rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-lg shadow-black/5",
1214
+ ...popup.getFloatingProps(),
1215
+ children: [
1216
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
1217
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: filter.label }),
1218
+ /* @__PURE__ */ jsx(
1219
+ "button",
1220
+ {
1221
+ type: "button",
1222
+ className: "text-[10px] text-primary disabled:text-muted-foreground",
1223
+ onClick: () => setDraft({}),
1224
+ disabled: Object.keys(draft).length === 0,
1225
+ children: "Clear all"
1226
+ }
1227
+ )
1228
+ ] }),
1229
+ showOptionFilter && /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2 rounded-md border border-input bg-background px-2", children: [
1230
+ /* @__PURE__ */ jsx(Icon, { icon: UiSearch, className: "shrink-0 text-muted-foreground" }),
1231
+ /* @__PURE__ */ jsx(
1232
+ "input",
1233
+ {
1234
+ type: "search",
1235
+ "aria-label": `Filter ${filter.label} options`,
1236
+ className: "h-8 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground",
1237
+ placeholder: `Filter ${filter.label.toLowerCase()}`,
1238
+ value: optionQuery,
1239
+ onChange: (event) => setOptionQuery(event.target.value)
1240
+ }
1241
+ )
1242
+ ] }),
1243
+ /* @__PURE__ */ jsxs(
1218
1244
  "div",
1219
1245
  {
1220
- role: "button",
1221
- tabIndex: 0,
1222
- "data-filter-option": option.value,
1223
- className: "rounded-md px-1.5 py-0.5 hover:bg-accent/50 focus-visible:bg-accent/50 focus-visible:outline-none",
1224
- onClick: () => setDraft(updateMultiFilterValue(draft, option.value, nextFilterMode(mode))),
1225
- onKeyDown: (event) => {
1226
- if (event.key === "Enter" || event.key === " ") {
1227
- event.preventDefault();
1228
- setDraft(updateMultiFilterValue(draft, option.value, nextFilterMode(mode)));
1229
- }
1230
- },
1231
- children: /* @__PURE__ */ jsx(
1232
- FilterPill,
1233
- {
1234
- className: "w-full justify-between",
1235
- label: option.label,
1236
- mode,
1237
- title,
1238
- togglePosition: "right",
1239
- onModeChange: (next) => setDraft(updateMultiFilterValue(draft, option.value, next))
1240
- }
1241
- )
1242
- },
1243
- option.value
1244
- );
1245
- }),
1246
- canAddCustom && /* @__PURE__ */ jsxs(
1247
- "div",
1248
- {
1249
- role: "button",
1250
- tabIndex: 0,
1251
- "data-filter-add-custom": trimmedQuery,
1252
- className: "rounded-md px-2 py-1 text-sm text-primary hover:bg-accent/50 focus-visible:bg-accent/50 focus-visible:outline-none",
1253
- onClick: addCustom,
1254
- onKeyDown: (event) => {
1255
- if (event.key === "Enter" || event.key === " ") {
1256
- event.preventDefault();
1257
- addCustom();
1258
- }
1259
- },
1260
- children: [
1261
- "Add ",
1262
- trimmedQuery,
1263
- ""
1264
- ]
1265
- }
1266
- ),
1267
- visibleOptions.length === 0 && !canAddCustom && /* @__PURE__ */ jsx("div", { className: "px-2 py-3 text-sm text-muted-foreground", children: "No options found" })
1268
- ] }),
1269
- moreCount > 0 && !optionQuery.trim() && /* @__PURE__ */ jsxs("div", { className: "mt-2 px-1.5 text-[11px] text-muted-foreground", children: [
1270
- "… and ",
1271
- moreCount.toLocaleString(),
1272
- " more",
1273
- onSearch ? " — type to search all" : ""
1274
- ] })
1275
- ] })
1246
+ className: "max-h-72 overflow-auto",
1247
+ onScroll: (event) => setOptionScrollTop(event.currentTarget.scrollTop),
1248
+ children: [
1249
+ /* @__PURE__ */ jsx("div", { className: "relative", style: { height: optionWindow.totalHeight }, children: /* @__PURE__ */ jsx("div", { style: { transform: `translateY(${optionWindow.top}px)` }, children: optionWindow.options.map((option) => {
1250
+ const mode = draft[option.value] ?? "neutral";
1251
+ const title = option.title ?? multiSelectOptionText(option);
1252
+ return /* @__PURE__ */ jsx(
1253
+ "div",
1254
+ {
1255
+ role: "button",
1256
+ tabIndex: 0,
1257
+ "data-filter-option": option.value,
1258
+ className: "box-border flex items-center rounded-md px-1.5 py-0.5 hover:bg-accent/50 focus-visible:bg-accent/50 focus-visible:outline-none",
1259
+ style: { height: MULTI_FILTER_OPTION_ROW_HEIGHT },
1260
+ onClick: () => setDraft(updateMultiFilterValue(draft, option.value, nextFilterMode(mode))),
1261
+ onKeyDown: (event) => {
1262
+ if (event.key === "Enter" || event.key === " ") {
1263
+ event.preventDefault();
1264
+ setDraft(
1265
+ updateMultiFilterValue(draft, option.value, nextFilterMode(mode))
1266
+ );
1267
+ }
1268
+ },
1269
+ children: /* @__PURE__ */ jsx(
1270
+ FilterPill,
1271
+ {
1272
+ className: "w-full justify-between",
1273
+ label: option.label,
1274
+ mode,
1275
+ title,
1276
+ togglePosition: "right",
1277
+ onModeChange: (next) => setDraft(updateMultiFilterValue(draft, option.value, next))
1278
+ }
1279
+ )
1280
+ },
1281
+ option.value
1282
+ );
1283
+ }) }) }),
1284
+ canAddCustom && /* @__PURE__ */ jsxs(
1285
+ "div",
1286
+ {
1287
+ role: "button",
1288
+ tabIndex: 0,
1289
+ "data-filter-add-custom": trimmedQuery,
1290
+ className: "rounded-md px-2 py-1 text-sm text-primary hover:bg-accent/50 focus-visible:bg-accent/50 focus-visible:outline-none",
1291
+ onClick: addCustom,
1292
+ onKeyDown: (event) => {
1293
+ if (event.key === "Enter" || event.key === " ") {
1294
+ event.preventDefault();
1295
+ addCustom();
1296
+ }
1297
+ },
1298
+ children: [
1299
+ "Add ",
1300
+ trimmedQuery,
1301
+ "”"
1302
+ ]
1303
+ }
1304
+ ),
1305
+ visibleOptions.length === 0 && !canAddCustom && /* @__PURE__ */ jsx("div", { className: "px-2 py-3 text-sm text-muted-foreground", children: "No options found" })
1306
+ ]
1307
+ }
1308
+ ),
1309
+ moreCount > 0 && !optionQuery.trim() && /* @__PURE__ */ jsxs("div", { className: "mt-2 px-1.5 text-[11px] text-muted-foreground", children: [
1310
+ "… and ",
1311
+ moreCount.toLocaleString(),
1312
+ " more",
1313
+ onSearch ? " — type to search all" : ""
1314
+ ] })
1315
+ ]
1316
+ }
1317
+ ) }) })
1276
1318
  ]
1277
1319
  }
1278
1320
  );
@@ -1559,14 +1601,12 @@ function NestedMultiFilterField({
1559
1601
  filter,
1560
1602
  grow
1561
1603
  }) {
1562
- const rootRef = useRef(null);
1563
- const triggerRef = useRef(null);
1564
1604
  const [open, setOpen] = useState(false);
1565
1605
  const [activeGroup, setActiveGroup] = useState(null);
1566
1606
  const [draft, setDraft] = useDebouncedMultiDraft(filter.value, filter.onChange);
1567
- useDismissablePopup(open, rootRef, triggerRef, () => {
1568
- setOpen(false);
1569
- setActiveGroup(null);
1607
+ const popup = useAnchoredPopup(open, (next) => {
1608
+ setOpen(next);
1609
+ if (!next) setActiveGroup(null);
1570
1610
  });
1571
1611
  const summary = summarizeMultiFilter(filter.label, draft);
1572
1612
  const groups = filter.groups;
@@ -1606,10 +1646,9 @@ function NestedMultiFilterField({
1606
1646
  return /* @__PURE__ */ jsxs(
1607
1647
  "div",
1608
1648
  {
1609
- ref: rootRef,
1610
1649
  title: filter.description,
1611
1650
  className: cn(
1612
- "relative min-w-0",
1651
+ "inline-flex min-w-0",
1613
1652
  grow ? "min-w-[8rem] max-w-[12rem] flex-1" : "shrink-0",
1614
1653
  filter.disabled && "opacity-60",
1615
1654
  filter.className
@@ -1618,41 +1657,33 @@ function NestedMultiFilterField({
1618
1657
  /* @__PURE__ */ jsxs(
1619
1658
  Button,
1620
1659
  {
1621
- ref: triggerRef,
1660
+ ref: popup.refs.setReference,
1622
1661
  type: "button",
1623
1662
  variant: "outline",
1624
1663
  size: "sm",
1625
1664
  "aria-label": `${filter.label} filter`,
1626
- "aria-haspopup": "dialog",
1627
- "aria-expanded": open,
1628
1665
  disabled: filter.disabled,
1629
- onClick: () => {
1630
- setOpen((current) => !current);
1631
- if (open) setActiveGroup(null);
1632
- },
1633
1666
  className: cn(
1634
1667
  "min-w-0 gap-2 font-normal",
1635
1668
  grow ? "w-full max-w-[12rem] justify-between" : "w-auto max-w-[8.5rem] px-2.5",
1636
1669
  summary === filter.label && "text-muted-foreground"
1637
1670
  ),
1671
+ ...popup.getReferenceProps(),
1638
1672
  children: [
1639
1673
  /* @__PURE__ */ jsx("span", { className: "truncate", children: summary }),
1640
1674
  /* @__PURE__ */ jsx(Icon, { icon: open ? UiChevronUp : UiChevronDown, className: "text-muted-foreground" })
1641
1675
  ]
1642
1676
  }
1643
1677
  ),
1644
- open && /* @__PURE__ */ jsxs(
1678
+ open && /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(FloatingFocusManager, { context: popup.context, modal: false, children: /* @__PURE__ */ jsxs(
1645
1679
  "div",
1646
1680
  {
1681
+ ref: popup.refs.setFloating,
1647
1682
  role: "dialog",
1648
- onKeyDown: (event) => {
1649
- if (event.key === "Escape") {
1650
- event.preventDefault();
1651
- setOpen(false);
1652
- setActiveGroup(null);
1653
- }
1654
- },
1655
- className: "absolute left-0 top-[calc(100%+0.375rem)] z-50 flex",
1683
+ "aria-label": filter.label,
1684
+ style: { ...popup.floatingStyles, zIndex: popup.floatingZ },
1685
+ className: "flex",
1686
+ ...popup.getFloatingProps(),
1656
1687
  children: [
1657
1688
  /* @__PURE__ */ jsxs("div", { className: "min-w-[14rem] max-w-[16rem] rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-lg shadow-black/5", children: [
1658
1689
  /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
@@ -1771,7 +1802,7 @@ function NestedMultiFilterField({
1771
1802
  )
1772
1803
  ]
1773
1804
  }
1774
- )
1805
+ ) }) })
1775
1806
  ]
1776
1807
  }
1777
1808
  );
@@ -1851,29 +1882,25 @@ function RangeControlButton({
1851
1882
  }
1852
1883
  );
1853
1884
  }
1854
- function useDismissablePopup(open, rootRef, triggerRef, onClose) {
1855
- useEffect(() => {
1856
- if (!open) return;
1857
- const onPointerDown = (event) => {
1858
- var _a;
1859
- if (!((_a = rootRef.current) == null ? void 0 : _a.contains(event.target))) {
1860
- onClose();
1861
- }
1862
- };
1863
- const onKeyDown = (event) => {
1864
- var _a;
1865
- if (event.key === "Escape") {
1866
- onClose();
1867
- (_a = triggerRef.current) == null ? void 0 : _a.focus();
1868
- }
1869
- };
1870
- document.addEventListener("mousedown", onPointerDown);
1871
- document.addEventListener("keydown", onKeyDown);
1872
- return () => {
1873
- document.removeEventListener("mousedown", onPointerDown);
1874
- document.removeEventListener("keydown", onKeyDown);
1875
- };
1876
- }, [onClose, open, rootRef, triggerRef]);
1885
+ function useAnchoredPopup(open, onOpenChange, placement = "bottom-start") {
1886
+ const floatingZ = useFloatingZIndex();
1887
+ const { refs, floatingStyles, context } = useFloating({
1888
+ open,
1889
+ onOpenChange,
1890
+ placement,
1891
+ whileElementsMounted: autoUpdate,
1892
+ middleware: [offset(6), flip({ padding: 8 }), shift({ padding: 8 })]
1893
+ });
1894
+ const { getReferenceProps, getFloatingProps } = useInteractions([
1895
+ useClick(context),
1896
+ useDismiss(context, { escapeKey: false }),
1897
+ useRole(context, { role: "dialog" })
1898
+ ]);
1899
+ useEscapeLayer(open, () => {
1900
+ onOpenChange(false);
1901
+ if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();
1902
+ });
1903
+ return { refs, floatingStyles, context, floatingZ, getReferenceProps, getFloatingProps };
1877
1904
  }
1878
1905
  function useMediaQuery(query) {
1879
1906
  const [matches, setMatches] = useState(() => {