@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 +1 @@
1
- {"version":3,"file":"TimeRange.js","sources":["../../src/components/TimeRange.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type RefObject } from \"react\";\nimport { Icon } from \"../data/Icon\";\nimport { UiArrowRight, UiCalendar, UiClose, UiWatch } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"./button\";\nimport { Select } from \"./select\";\n\nexport type TimeRangeKind = \"time\" | \"date\";\n\nexport type TimeRangePresetGroup = \"min\" | \"hr\" | \"day\" | \"wk+\" | \"this\" | \"last\";\n\nexport type TimeRangePreset = {\n /** Visible label for the preset row. */\n label: string;\n /** Start value applied when the preset is selected, e.g. `now-1h`. */\n from: string;\n /** End value applied when the preset is selected, usually `now`. */\n to: string;\n};\n\nexport type TimeRangeChipRow = {\n /** Stable row id used as the React key. */\n id: string;\n /** Group label shown beside the chips. */\n label: string;\n /** Preset chips rendered in this row. */\n chips: {\n /** Compact label shown in the chip. */\n label: string;\n /** Start value applied when clicked, e.g. `now-15m`. */\n token: string;\n /** Optional end value; defaults to `now`. */\n to?: string;\n /** Accessible label for the chip button. */\n ariaLabel?: string;\n /** Optional browser tooltip. */\n title?: string;\n }[];\n};\n\nexport type TimeRangeProps = {\n /** Controls whether the inputs accept date-only or date-time values. */\n kind?: TimeRangeKind;\n /** Label shown in the trigger before the selected range summary. */\n label?: string;\n /** Current start value. Relative values such as `now-1h` are supported. */\n from?: string;\n /** Current end value. Relative values such as `now` are supported. */\n to?: string;\n /** Called when the user applies a typed value or preset. */\n onApply: (from: string, to: string) => void;\n /** Presets or preset groups to show in the popup. */\n presets?: Array<TimeRangePreset | TimeRangePresetGroup>;\n /** Explicit chip rows; overrides the built-in preset group layout. */\n chipRows?: TimeRangeChipRow[];\n /** Enables time fields for date-time ranges. */\n timeEnabled?: boolean;\n /** Selected timezone shown in the popup. */\n timeZone?: string;\n /** Available timezone options. */\n timeZones?: string[];\n /** Placeholder for the start input. */\n fromPlaceholder?: string;\n /** Placeholder for the end input. */\n toPlaceholder?: string;\n /** Trigger text when neither boundary is set. */\n emptyLabel?: string;\n /** Aligns the popup to the trigger's left or right edge. */\n align?: \"left\" | \"right\";\n /** Disables opening and applying changes. */\n disabled?: boolean;\n /** Classes applied to the root wrapper. */\n className?: string;\n /** Classes applied to the trigger button. */\n triggerClassName?: string;\n /** Classes applied to the popup panel. */\n panelClassName?: string;\n};\n\nconst defaultTimeChipRows: TimeRangeChipRow[] = [\n {\n id: \"minutes\",\n label: \"Min\",\n chips: [\n { label: \"5m\", token: \"now-5m\" },\n { label: \"15m\", token: \"now-15m\" },\n { label: \"30m\", token: \"now-30m\" },\n ],\n },\n {\n id: \"hours\",\n label: \"Hr\",\n chips: [\n { label: \"1h\", token: \"now-1h\" },\n { label: \"2h\", token: \"now-2h\" },\n { label: \"4h\", token: \"now-4h\" },\n { label: \"8h\", token: \"now-8h\" },\n { label: \"12h\", token: \"now-12h\" },\n ],\n },\n {\n id: \"days\",\n label: \"Day\",\n chips: [\n { label: \"1d\", token: \"now-1d\" },\n { label: \"2d\", token: \"now-2d\" },\n { label: \"3d\", token: \"now-3d\" },\n { label: \"1w\", token: \"now-1w\" },\n ],\n },\n {\n id: \"weeks\",\n label: \"Wk+\",\n chips: [\n { label: \"2w\", token: \"now-2w\" },\n { label: \"1M\", token: \"now-1M\" },\n { label: \"3M\", token: \"now-3M\" },\n { label: \"6M\", token: \"now-6M\" },\n { label: \"1y\", token: \"now-1y\" },\n ],\n },\n];\n\nconst timeChipRowsByGroup: Record<\n Exclude<TimeRangePresetGroup, \"this\" | \"last\">,\n TimeRangeChipRow\n> = {\n min: defaultTimeChipRows[0]!,\n hr: defaultTimeChipRows[1]!,\n day: defaultTimeChipRows[2]!,\n \"wk+\": defaultTimeChipRows[3]!,\n};\n\nconst defaultDateChipRows: TimeRangeChipRow[] = [\n {\n id: \"days\",\n label: \"Day\",\n chips: [\n { label: \"1d\", token: \"now-1d\" },\n { label: \"2d\", token: \"now-2d\" },\n { label: \"3d\", token: \"now-3d\" },\n { label: \"7d\", token: \"now-7d\" },\n ],\n },\n {\n id: \"weeks\",\n label: \"Wk\",\n chips: [\n { label: \"1w\", token: \"now-1w\" },\n { label: \"2w\", token: \"now-2w\" },\n { label: \"4w\", token: \"now-4w\" },\n ],\n },\n {\n id: \"months\",\n label: \"Mo\",\n chips: [\n { label: \"1M\", token: \"now-1M\" },\n { label: \"3M\", token: \"now-3M\" },\n { label: \"6M\", token: \"now-6M\" },\n { label: \"1y\", token: \"now-1y\" },\n ],\n },\n];\n\nconst thisLastChipRows: Record<Extract<TimeRangePresetGroup, \"this\" | \"last\">, TimeRangeChipRow> = {\n this: {\n id: \"this\",\n label: \"This\",\n chips: [\n { label: \"Week\", token: \"now/w\", to: \"now/w+1w\", ariaLabel: \"this week\" },\n {\n label: \"Month\",\n token: \"now/M\",\n to: \"now/M+1M\",\n ariaLabel: \"this month\",\n },\n {\n label: \"Quarter\",\n token: \"now/q\",\n to: \"now/q+1q\",\n ariaLabel: \"this quarter\",\n },\n { label: \"Year\", token: \"now/y\", to: \"now/y+1y\", ariaLabel: \"this year\" },\n ],\n },\n last: {\n id: \"last\",\n label: \"Last\",\n chips: [\n { label: \"Week\", token: \"now/w-1w\", to: \"now/w\", ariaLabel: \"last week\" },\n {\n label: \"Month\",\n token: \"now/M-1M\",\n to: \"now/M\",\n ariaLabel: \"last month\",\n },\n {\n label: \"Quarter\",\n token: \"now/q-1q\",\n to: \"now/q\",\n ariaLabel: \"last quarter\",\n },\n { label: \"Year\", token: \"now/y-1y\", to: \"now/y\", ariaLabel: \"last year\" },\n ],\n },\n};\n\nexport function TimeRange({\n kind = \"time\",\n label = \"Time range\",\n from = \"\",\n to = \"\",\n onApply,\n presets,\n chipRows,\n timeEnabled = false,\n timeZone,\n timeZones,\n fromPlaceholder,\n toPlaceholder,\n emptyLabel,\n align = \"right\",\n disabled = false,\n className,\n triggerClassName,\n panelClassName,\n}: TimeRangeProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const [open, setOpen] = useState(false);\n const [draftFrom, setDraftFrom] = useState(from);\n const [draftTo, setDraftTo] = useState(to);\n const resolvedDefaultTimeZone = timeZone ?? getDefaultTimeZone();\n const [selectedTimeZone, setSelectedTimeZone] = useState(resolvedDefaultTimeZone);\n\n const activeTimeZones = resolveTimeZones(timeZones, selectedTimeZone);\n const { rows: activeChipRows, presets: activePresets } = resolvePresetConfig(\n kind,\n presets,\n chipRows,\n );\n\n useDismissablePopup(open, rootRef, triggerRef, () => setOpen(false));\n\n useEffect(() => {\n if (open) {\n setDraftFrom(from);\n setDraftTo(to);\n setSelectedTimeZone(resolvedDefaultTimeZone);\n }\n }, [from, open, resolvedDefaultTimeZone, to]);\n\n const buttonLabel =\n formatSelectedPresetLabel(from, to, activeChipRows, activePresets) ??\n formatRangeLabel(kind, from, to, emptyLabel);\n const activeToken = normalizeRangeValue(kind, from, {\n timeEnabled,\n timeZone: selectedTimeZone,\n });\n\n function applyRange(nextFrom: string, nextTo: string) {\n onApply(\n normalizeRangeValue(kind, nextFrom, {\n timeEnabled,\n timeZone: selectedTimeZone,\n }),\n normalizeRangeValue(kind, nextTo, {\n timeEnabled,\n timeZone: selectedTimeZone,\n }),\n );\n setOpen(false);\n }\n\n function clearRange() {\n setDraftFrom(\"\");\n setDraftTo(\"\");\n onApply(\"\", \"\");\n setOpen(false);\n }\n\n return (\n <div ref={rootRef} className={cn(\"relative inline-flex\", className)}>\n <Button\n ref={triggerRef}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={`${label} filter`}\n aria-expanded={open}\n aria-haspopup=\"dialog\"\n disabled={disabled}\n onClick={() => setOpen((current) => !current)}\n className={cn(\n \"h-7 w-fit max-w-[12rem] min-w-0 gap-2 px-2 text-xs font-normal\",\n triggerClassName,\n )}\n >\n <Icon\n icon={kind === \"time\" ? UiWatch : UiCalendar}\n className=\"text-muted-foreground text-[14px]\"\n />\n <span className=\"truncate font-normal tabular-nums\">{buttonLabel}</span>\n </Button>\n\n {open && (\n <div\n role=\"dialog\"\n aria-label={label}\n className={cn(\n \"fixed inset-x-2 bottom-4 top-16 z-50 flex flex-col overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-lg shadow-black/10 outline-none md:absolute md:bottom-auto md:top-[calc(100%+0.375rem)] md:block md:w-[22.5rem] md:shadow-black/5\",\n align === \"left\" ? \"md:left-0\" : \"md:right-0\",\n panelClassName,\n )}\n >\n <div className=\"flex items-center justify-between border-b border-border/60 px-3.5 py-2.5\">\n <span className=\"text-[10px] font-semibold uppercase tracking-wider text-muted-foreground\">\n {label}\n </span>\n <span className=\"font-mono text-[10px] text-muted-foreground\">\n {timeEnabled ? selectedTimeZone : \"Relative\"}\n </span>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto md:overflow-visible\">\n {activeChipRows.length > 0 && (\n <ChipRowList rows={activeChipRows} activeToken={activeToken} onApply={applyRange} />\n )}\n {activePresets.length > 0 && (\n <div className={activeChipRows.length > 0 ? \"px-3.5 pb-3\" : \"px-3.5 pt-3\"}>\n <PresetList presets={activePresets} onApply={applyRange} />\n </div>\n )}\n\n <div className=\"border-t border-border/60 bg-muted/30 px-3.5 py-2.5\">\n <div className=\"mb-1.5 flex flex-wrap items-center justify-between gap-2\">\n <div className=\"text-[10px] font-semibold uppercase tracking-wider text-muted-foreground\">\n Custom\n </div>\n {timeEnabled && (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <span className=\"text-[10px] font-semibold uppercase tracking-wider text-muted-foreground\">\n TZ\n </span>\n <Select\n aria-label={`${label} timezone`}\n value={selectedTimeZone}\n options={activeTimeZones.map((zone) => ({\n value: zone,\n label: zone,\n }))}\n onChange={(event) => setSelectedTimeZone(event.target.value)}\n className=\"h-7 w-40 truncate px-2 pr-7 font-mono text-xs\"\n />\n </div>\n )}\n </div>\n <div className=\"grid gap-1.5 md:grid-cols-[1fr_auto_1fr] md:items-center\">\n <DraftRangeInput\n aria-label={`${label} from`}\n placeholder={\n fromPlaceholder ?? (kind === \"date\" ? \"YYYY-MM-DD or now-1d\" : \"now-24h\")\n }\n value={draftFrom}\n onChange={setDraftFrom}\n onClear={() => setDraftFrom(\"\")}\n pickerKind={kind}\n timeEnabled={timeEnabled}\n />\n <Icon\n icon={UiArrowRight}\n className=\"hidden text-muted-foreground text-[12px] md:block\"\n />\n <DraftRangeInput\n aria-label={`${label} to`}\n placeholder={toPlaceholder ?? (kind === \"date\" ? \"YYYY-MM-DD or now\" : \"now\")}\n value={draftTo}\n onChange={setDraftTo}\n onClear={() => setDraftTo(\"\")}\n pickerKind={kind}\n timeEnabled={timeEnabled}\n />\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between border-t border-border/60 px-3 py-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-7 px-2.5 text-xs\"\n disabled={!from && !to}\n onClick={clearRange}\n >\n Clear\n </Button>\n <div className=\"flex gap-1.5\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-7 px-2.5 text-xs\"\n onClick={() => setOpen(false)}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n className=\"h-7 px-3.5 text-xs\"\n onClick={() => applyRange(draftFrom, draftTo)}\n >\n Apply\n </Button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction DraftRangeInput({\n value,\n onChange,\n onClear,\n placeholder,\n pickerKind,\n timeEnabled,\n ...rest\n}: {\n value: string;\n onChange: (value: string) => void;\n onClear: () => void;\n placeholder: string | undefined;\n pickerKind?: TimeRangeKind;\n timeEnabled: boolean;\n \"aria-label\": string;\n}) {\n const ariaLabel = rest[\"aria-label\"];\n const showCalendar = pickerKind !== undefined;\n const showTimeInput = pickerKind === \"time\" && timeEnabled;\n const { date: datePart, time: timePart } = splitDateTime(value);\n\n function setDatePart(nextDate: string) {\n if (!showTimeInput) {\n onChange(nextDate);\n return;\n }\n if (!nextDate) {\n onChange(timePart ? `T${timePart}` : \"\");\n return;\n }\n onChange(timePart ? `${nextDate}T${timePart}` : nextDate);\n }\n\n function setTimePart(nextTime: string) {\n if (!nextTime) {\n onChange(datePart);\n return;\n }\n onChange(datePart ? `${datePart}T${nextTime}` : `T${nextTime}`);\n }\n\n return (\n <div className=\"space-y-1\">\n <DraftTextInput\n ariaLabel={ariaLabel}\n placeholder={placeholder}\n value={showTimeInput ? datePart : value}\n onChange={showTimeInput ? setDatePart : onChange}\n onClear={onClear}\n showCalendar={showCalendar}\n />\n {showTimeInput && (\n <DraftTimeInput ariaLabel={`${ariaLabel} time`} value={timePart} onChange={setTimePart} />\n )}\n </div>\n );\n}\n\nfunction resolvePresetConfig(\n kind: TimeRangeKind,\n presets: TimeRangeProps[\"presets\"],\n chipRows: TimeRangeChipRow[] | undefined,\n) {\n if (presets === undefined) {\n return {\n rows: chipRows ?? (kind === \"date\" ? defaultDateChipRows : defaultTimeChipRows),\n presets: [] as TimeRangePreset[],\n };\n }\n\n const rows: TimeRangeChipRow[] = chipRows ? [...chipRows] : [];\n const presetList: TimeRangePreset[] = [];\n\n for (const preset of presets) {\n if (typeof preset !== \"string\") {\n presetList.push(preset);\n continue;\n }\n if (preset === \"this\" || preset === \"last\") {\n rows.push(thisLastChipRows[preset]);\n continue;\n }\n rows.push(timeChipRowsByGroup[preset]);\n }\n\n return { rows, presets: presetList };\n}\n\nfunction DraftTextInput({\n ariaLabel,\n value,\n onChange,\n onClear,\n placeholder,\n showCalendar,\n}: {\n ariaLabel: string;\n value: string;\n onChange: (value: string) => void;\n onClear: () => void;\n placeholder: string | undefined;\n showCalendar: boolean;\n}) {\n const pickerRef = useRef<HTMLInputElement | null>(null);\n const hasValue = value.length > 0;\n const paddingRight = showCalendar ? (hasValue ? \"pr-12\" : \"pr-7\") : \"pr-7\";\n\n return (\n <div className=\"relative\">\n <input\n type=\"text\"\n aria-label={ariaLabel}\n placeholder={placeholder}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n className={cn(\n \"h-8 w-full rounded-md border border-input bg-background px-2 font-mono text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n paddingRight,\n )}\n />\n {hasValue && (\n <button\n type=\"button\"\n aria-label={`Clear ${ariaLabel}`}\n onClick={onClear}\n className={cn(\n \"absolute inset-y-0 inline-flex w-5 items-center justify-center text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n showCalendar ? \"right-6\" : \"right-1\",\n )}\n >\n <Icon icon={UiClose} className=\"text-[12px]\" />\n </button>\n )}\n {showCalendar && (\n <>\n <input\n ref={pickerRef}\n type=\"date\"\n tabIndex={-1}\n aria-hidden=\"true\"\n value={/^\\d{4}-\\d{2}-\\d{2}$/.test(value.trim()) ? value.trim() : \"\"}\n onChange={(event) => onChange(event.target.value)}\n className=\"pointer-events-none absolute left-0 top-0 h-0 w-0 opacity-0\"\n />\n <button\n type=\"button\"\n aria-label={`Pick ${ariaLabel}`}\n onClick={() => pickerRef.current?.showPicker?.()}\n className=\"absolute inset-y-0 right-1 inline-flex w-5 items-center justify-center text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <Icon icon={UiCalendar} className=\"text-[12px]\" />\n </button>\n </>\n )}\n </div>\n );\n}\n\nfunction DraftTimeInput({\n ariaLabel,\n value,\n onChange,\n}: {\n ariaLabel: string;\n value: string;\n onChange: (value: string) => void;\n}) {\n return (\n <input\n type=\"time\"\n aria-label={ariaLabel}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n className=\"h-8 w-full rounded-md border border-input bg-background px-2 font-mono text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n );\n}\n\nfunction splitDateTime(value: string) {\n const trimmed = value.trim();\n const match = /^(\\d{4}-\\d{2}-\\d{2})?T?(\\d{2}:\\d{2})?/.exec(trimmed);\n if (!match || trimmed.startsWith(\"now\") || /^[+-]/.test(trimmed)) {\n return { date: trimmed, time: \"\" };\n }\n return { date: match[1] ?? \"\", time: match[2] ?? \"\" };\n}\n\nfunction ChipRowList({\n rows,\n activeToken,\n onApply,\n}: {\n rows: TimeRangeChipRow[];\n activeToken: string;\n onApply: (from: string, to: string) => void;\n}) {\n if (rows.length === 0) return null;\n\n return (\n <div className=\"px-3.5 py-3\" role=\"list\" aria-label=\"Time filters\">\n {rows.map((row, index) => (\n <div\n key={row.id}\n className={cn(\n \"grid grid-cols-[2rem_1fr] items-center gap-2\",\n index === rows.length - 1 ? \"\" : \"mb-1.5\",\n )}\n role=\"listitem\"\n >\n <span className=\"text-[9.5px] font-bold uppercase tracking-wider text-muted-foreground\">\n {row.label}\n </span>\n <div className=\"flex divide-x divide-border overflow-hidden rounded-[5px] border border-border\">\n {row.chips.map((chip) => {\n const active = chip.token === activeToken;\n return (\n <button\n key={chip.token}\n type=\"button\"\n title={chip.title ?? chip.ariaLabel ?? chip.token}\n aria-label={chip.ariaLabel ?? chip.token}\n aria-pressed={active}\n onClick={() => onApply(chip.token, chip.to ?? \"now\")}\n className={cn(\n \"flex-1 min-w-0 px-0 py-1.5 text-center font-mono text-[11.5px] leading-none transition-colors focus-visible:relative focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n active\n ? \"bg-foreground font-semibold text-background\"\n : \"bg-background font-medium text-muted-foreground hover:bg-muted\",\n )}\n >\n {chip.label}\n </button>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n );\n}\n\nfunction PresetList({\n presets,\n onApply,\n}: {\n presets: TimeRangePreset[];\n onApply: (from: string, to: string) => void;\n}) {\n if (presets.length === 0) return null;\n\n return (\n <div className=\"mb-3 space-y-1\" role=\"list\" aria-label=\"Time filters\">\n {presets.map((preset) => (\n <button\n key={`${preset.label}-${preset.from}-${preset.to}`}\n type=\"button\"\n className=\"flex h-8 w-full items-center justify-between gap-3 rounded-md border border-border bg-background px-2 text-left text-xs hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n onClick={() => onApply(preset.from, preset.to)}\n >\n <span className=\"min-w-0 truncate font-medium\">{preset.label}</span>\n <span className=\"shrink-0 font-mono text-[11px] text-muted-foreground\">\n {formatPresetRange(preset)}\n </span>\n </button>\n ))}\n </div>\n );\n}\n\nfunction formatSelectedPresetLabel(\n from: string,\n to: string,\n rows: TimeRangeChipRow[],\n presets: TimeRangePreset[],\n) {\n const normalizedFrom = from.trim();\n const normalizedTo = to.trim();\n if (!normalizedFrom && !normalizedTo) return undefined;\n\n for (const row of rows) {\n for (const chip of row.chips) {\n if (chip.token === normalizedFrom && (chip.to ?? \"now\") === normalizedTo) {\n return chip.ariaLabel ?? chip.title ?? chip.label;\n }\n }\n }\n\n return presets.find((preset) => preset.from === normalizedFrom && preset.to === normalizedTo)\n ?.label;\n}\n\nfunction formatPresetRange({ from, to }: TimeRangePreset) {\n if (!from) return to;\n if (!to || to === \"now\") return from;\n return `${from} -> ${to}`;\n}\n\nfunction useDismissablePopup(\n open: boolean,\n rootRef: RefObject<HTMLElement | null>,\n triggerRef: RefObject<HTMLElement | null>,\n onClose: () => void,\n) {\n useEffect(() => {\n if (!open) return;\n\n const onPointerDown = (event: MouseEvent) => {\n if (!rootRef.current?.contains(event.target as Node)) {\n onClose();\n }\n };\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n onClose();\n triggerRef.current?.focus();\n }\n };\n\n document.addEventListener(\"mousedown\", onPointerDown);\n document.addEventListener(\"keydown\", onKeyDown);\n return () => {\n document.removeEventListener(\"mousedown\", onPointerDown);\n document.removeEventListener(\"keydown\", onKeyDown);\n };\n }, [onClose, open, rootRef, triggerRef]);\n}\n\nfunction normalizeRangeValue(\n kind: TimeRangeKind,\n value: string,\n options: { timeEnabled?: boolean; timeZone?: string } = {},\n) {\n const trimmed = value.trim();\n if (!trimmed) return \"\";\n if (trimmed === \"now\" || trimmed.startsWith(\"now\")) return trimmed;\n if (kind === \"time\" && /^[+-]\\d/.test(trimmed)) return `now${trimmed}`;\n if (kind === \"time\" && options.timeEnabled) {\n return encodeDateTimeWithOffset(trimmed, options.timeZone ?? getDefaultTimeZone());\n }\n return trimmed;\n}\n\nfunction formatRangeLabel(kind: TimeRangeKind, from: string, to: string, emptyLabel?: string) {\n const trimmedFrom = from.trim();\n const trimmedTo = to.trim();\n\n if (!trimmedFrom && !trimmedTo) {\n return emptyLabel ?? (kind === \"date\" ? \"Any date\" : \"now-24h\");\n }\n\n if (kind === \"time\") {\n const normalizedFrom = normalizeRangeValue(kind, trimmedFrom);\n const normalizedTo = normalizeRangeValue(kind, trimmedTo || \"now\");\n if (!normalizedFrom) return normalizedTo;\n if (normalizedTo === \"now\") return normalizedFrom;\n return `${normalizedFrom} -> ${normalizedTo}`;\n }\n\n if (trimmedFrom && trimmedTo && trimmedFrom === trimmedTo) {\n return trimmedFrom;\n }\n\n if (!trimmedFrom) return trimmedTo;\n if (!trimmedTo) return trimmedFrom;\n return `${trimmedFrom} -> ${trimmedTo}`;\n}\n\nfunction getDefaultTimeZone() {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || \"UTC\";\n } catch {\n return \"UTC\";\n }\n}\n\nfunction resolveTimeZones(timeZones: string[] | undefined, selectedTimeZone: string) {\n const zones = timeZones && timeZones.length > 0 ? timeZones : [selectedTimeZone, \"UTC\"];\n return Array.from(new Set([selectedTimeZone, ...zones, \"UTC\"].filter(Boolean)));\n}\n\nfunction encodeDateTimeWithOffset(value: string, timeZone: string) {\n if (!isLocalDateTime(value) || hasExplicitOffset(value)) return value;\n\n const match = value.match(/^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::(\\d{2}))?(?:\\.\\d+)?$/);\n if (!match) return value;\n\n const [, year, month, day, hour, minute, second = \"00\"] = match;\n const localUtcMillis = Date.UTC(\n Number(year),\n Number(month) - 1,\n Number(day),\n Number(hour),\n Number(minute),\n Number(second),\n );\n const offsetMinutes = getTimeZoneOffsetMinutes(timeZone, localUtcMillis);\n return `${year}-${month}-${day}T${hour}:${minute}:${second}${formatOffset(offsetMinutes)}`;\n}\n\nfunction isLocalDateTime(value: string) {\n return /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2})?(?:\\.\\d+)?$/.test(value.trim());\n}\n\nfunction hasExplicitOffset(value: string) {\n return /(?:Z|[+-]\\d{2}:?\\d{2})$/i.test(value.trim());\n}\n\nfunction getTimeZoneOffsetMinutes(timeZone: string, localUtcMillis: number) {\n try {\n const formatter = new Intl.DateTimeFormat(\"en-US\", {\n timeZone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n });\n const parts = Object.fromEntries(\n formatter\n .formatToParts(new Date(localUtcMillis))\n .filter((part) => part.type !== \"literal\")\n .map((part) => [part.type, part.value]),\n );\n const zonedUtcMillis = Date.UTC(\n Number(parts.year),\n Number(parts.month) - 1,\n Number(parts.day),\n Number(parts.hour),\n Number(parts.minute),\n Number(parts.second),\n );\n return (zonedUtcMillis - localUtcMillis) / 60_000;\n } catch {\n return 0;\n }\n}\n\nfunction formatOffset(offsetMinutes: number) {\n const sign = offsetMinutes >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offsetMinutes);\n const hours = Math.floor(abs / 60)\n .toString()\n .padStart(2, \"0\");\n const minutes = Math.floor(abs % 60)\n .toString()\n .padStart(2, \"0\");\n return `${sign}${hours}:${minutes}`;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TimeRange.js","sources":["../../src/components/TimeRange.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type Ref } from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useRole,\n} from \"@floating-ui/react\";\nimport { Icon } from \"../data/Icon\";\nimport { UiArrowRight, UiCalendar, UiClose, UiWatch } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport { useEscapeLayer, useFloatingZIndex } from \"../overlay/modalStack\";\nimport { Button } from \"./button\";\nimport { Select } from \"./select\";\n\nexport type TimeRangeKind = \"time\" | \"date\";\n\nexport type TimeRangePresetGroup = \"min\" | \"hr\" | \"day\" | \"wk+\" | \"this\" | \"last\";\n\nexport type TimeRangePreset = {\n /** Visible label for the preset row. */\n label: string;\n /** Start value applied when the preset is selected, e.g. `now-1h`. */\n from: string;\n /** End value applied when the preset is selected, usually `now`. */\n to: string;\n};\n\nexport type TimeRangeChipRow = {\n /** Stable row id used as the React key. */\n id: string;\n /** Group label shown beside the chips. */\n label: string;\n /** Preset chips rendered in this row. */\n chips: {\n /** Compact label shown in the chip. */\n label: string;\n /** Start value applied when clicked, e.g. `now-15m`. */\n token: string;\n /** Optional end value; defaults to `now`. */\n to?: string;\n /** Accessible label for the chip button. */\n ariaLabel?: string;\n /** Optional browser tooltip. */\n title?: string;\n }[];\n};\n\nexport type TimeRangeProps = {\n /** Controls whether the inputs accept date-only or date-time values. */\n kind?: TimeRangeKind;\n /** Label shown in the trigger before the selected range summary. */\n label?: string;\n /** Current start value. Relative values such as `now-1h` are supported. */\n from?: string;\n /** Current end value. Relative values such as `now` are supported. */\n to?: string;\n /** Called when the user applies a typed value or preset. */\n onApply: (from: string, to: string) => void;\n /** Presets or preset groups to show in the popup. */\n presets?: Array<TimeRangePreset | TimeRangePresetGroup>;\n /** Explicit chip rows; overrides the built-in preset group layout. */\n chipRows?: TimeRangeChipRow[];\n /** Enables time fields for date-time ranges. */\n timeEnabled?: boolean;\n /** Selected timezone shown in the popup. */\n timeZone?: string;\n /** Available timezone options. */\n timeZones?: string[];\n /** Placeholder for the start input. */\n fromPlaceholder?: string;\n /** Placeholder for the end input. */\n toPlaceholder?: string;\n /** Trigger text when neither boundary is set. */\n emptyLabel?: string;\n /** Aligns the popup to the trigger's left or right edge. */\n align?: \"left\" | \"right\";\n /** Disables opening and applying changes. */\n disabled?: boolean;\n /** Classes applied to the root wrapper. */\n className?: string;\n /** Classes applied to the trigger button. */\n triggerClassName?: string;\n /** Classes applied to the popup panel. */\n panelClassName?: string;\n};\n\nconst defaultTimeChipRows: TimeRangeChipRow[] = [\n {\n id: \"minutes\",\n label: \"Min\",\n chips: [\n { label: \"5m\", token: \"now-5m\" },\n { label: \"15m\", token: \"now-15m\" },\n { label: \"30m\", token: \"now-30m\" },\n ],\n },\n {\n id: \"hours\",\n label: \"Hr\",\n chips: [\n { label: \"1h\", token: \"now-1h\" },\n { label: \"2h\", token: \"now-2h\" },\n { label: \"4h\", token: \"now-4h\" },\n { label: \"8h\", token: \"now-8h\" },\n { label: \"12h\", token: \"now-12h\" },\n ],\n },\n {\n id: \"days\",\n label: \"Day\",\n chips: [\n { label: \"1d\", token: \"now-1d\" },\n { label: \"2d\", token: \"now-2d\" },\n { label: \"3d\", token: \"now-3d\" },\n { label: \"1w\", token: \"now-1w\" },\n ],\n },\n {\n id: \"weeks\",\n label: \"Wk+\",\n chips: [\n { label: \"2w\", token: \"now-2w\" },\n { label: \"1M\", token: \"now-1M\" },\n { label: \"3M\", token: \"now-3M\" },\n { label: \"6M\", token: \"now-6M\" },\n { label: \"1y\", token: \"now-1y\" },\n ],\n },\n];\n\nconst timeChipRowsByGroup: Record<\n Exclude<TimeRangePresetGroup, \"this\" | \"last\">,\n TimeRangeChipRow\n> = {\n min: defaultTimeChipRows[0]!,\n hr: defaultTimeChipRows[1]!,\n day: defaultTimeChipRows[2]!,\n \"wk+\": defaultTimeChipRows[3]!,\n};\n\nconst defaultDateChipRows: TimeRangeChipRow[] = [\n {\n id: \"days\",\n label: \"Day\",\n chips: [\n { label: \"1d\", token: \"now-1d\" },\n { label: \"2d\", token: \"now-2d\" },\n { label: \"3d\", token: \"now-3d\" },\n { label: \"7d\", token: \"now-7d\" },\n ],\n },\n {\n id: \"weeks\",\n label: \"Wk\",\n chips: [\n { label: \"1w\", token: \"now-1w\" },\n { label: \"2w\", token: \"now-2w\" },\n { label: \"4w\", token: \"now-4w\" },\n ],\n },\n {\n id: \"months\",\n label: \"Mo\",\n chips: [\n { label: \"1M\", token: \"now-1M\" },\n { label: \"3M\", token: \"now-3M\" },\n { label: \"6M\", token: \"now-6M\" },\n { label: \"1y\", token: \"now-1y\" },\n ],\n },\n];\n\nconst thisLastChipRows: Record<Extract<TimeRangePresetGroup, \"this\" | \"last\">, TimeRangeChipRow> = {\n this: {\n id: \"this\",\n label: \"This\",\n chips: [\n { label: \"Week\", token: \"now/w\", to: \"now/w+1w\", ariaLabel: \"this week\" },\n {\n label: \"Month\",\n token: \"now/M\",\n to: \"now/M+1M\",\n ariaLabel: \"this month\",\n },\n {\n label: \"Quarter\",\n token: \"now/q\",\n to: \"now/q+1q\",\n ariaLabel: \"this quarter\",\n },\n { label: \"Year\", token: \"now/y\", to: \"now/y+1y\", ariaLabel: \"this year\" },\n ],\n },\n last: {\n id: \"last\",\n label: \"Last\",\n chips: [\n { label: \"Week\", token: \"now/w-1w\", to: \"now/w\", ariaLabel: \"last week\" },\n {\n label: \"Month\",\n token: \"now/M-1M\",\n to: \"now/M\",\n ariaLabel: \"last month\",\n },\n {\n label: \"Quarter\",\n token: \"now/q-1q\",\n to: \"now/q\",\n ariaLabel: \"last quarter\",\n },\n { label: \"Year\", token: \"now/y-1y\", to: \"now/y\", ariaLabel: \"last year\" },\n ],\n },\n};\n\nexport function TimeRange({\n kind = \"time\",\n label = \"Time range\",\n from = \"\",\n to = \"\",\n onApply,\n presets,\n chipRows,\n timeEnabled = false,\n timeZone,\n timeZones,\n fromPlaceholder,\n toPlaceholder,\n emptyLabel,\n align = \"right\",\n disabled = false,\n className,\n triggerClassName,\n panelClassName,\n}: TimeRangeProps) {\n const [open, setOpen] = useState(false);\n const [draftFrom, setDraftFrom] = useState(from);\n const [draftTo, setDraftTo] = useState(to);\n const resolvedDefaultTimeZone = timeZone ?? getDefaultTimeZone();\n const [selectedTimeZone, setSelectedTimeZone] = useState(resolvedDefaultTimeZone);\n\n const activeTimeZones = resolveTimeZones(timeZones, selectedTimeZone);\n const { rows: activeChipRows, presets: activePresets } = resolvePresetConfig(\n kind,\n presets,\n chipRows,\n );\n\n // The popup is portaled (floating-ui) so it escapes the `overflow` clipping of\n // scroll-container ancestors (e.g. AppLayout's `<main overflow-y-auto>`) — the\n // same approach Combobox/DropdownMenu use. `flip`/`shift` keep it on-screen.\n const floatingZ = useFloatingZIndex();\n const { refs, floatingStyles, context } = useFloating({\n open,\n onOpenChange: setOpen,\n placement: align === \"left\" ? \"bottom-start\" : \"bottom-end\",\n whileElementsMounted: autoUpdate,\n middleware: [offset(6), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n const { getReferenceProps, getFloatingProps } = useInteractions([\n useClick(context),\n useDismiss(context, { escapeKey: false }),\n useRole(context, { role: \"dialog\" }),\n ]);\n useEscapeLayer(open, () => {\n setOpen(false);\n if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();\n });\n\n useEffect(() => {\n if (open) {\n setDraftFrom(from);\n setDraftTo(to);\n setSelectedTimeZone(resolvedDefaultTimeZone);\n }\n }, [from, open, resolvedDefaultTimeZone, to]);\n\n const buttonLabel =\n formatSelectedPresetLabel(from, to, activeChipRows, activePresets) ??\n formatRangeLabel(kind, from, to, emptyLabel);\n const activeToken = normalizeRangeValue(kind, from, {\n timeEnabled,\n timeZone: selectedTimeZone,\n });\n\n function applyRange(nextFrom: string, nextTo: string) {\n onApply(\n normalizeRangeValue(kind, nextFrom, {\n timeEnabled,\n timeZone: selectedTimeZone,\n }),\n normalizeRangeValue(kind, nextTo, {\n timeEnabled,\n timeZone: selectedTimeZone,\n }),\n );\n setOpen(false);\n }\n\n function clearRange() {\n setDraftFrom(\"\");\n setDraftTo(\"\");\n onApply(\"\", \"\");\n setOpen(false);\n }\n\n return (\n <div className={cn(\"inline-flex\", className)}>\n <Button\n ref={refs.setReference as Ref<HTMLButtonElement>}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={`${label} filter`}\n disabled={disabled}\n className={cn(\n \"h-7 w-fit max-w-[12rem] min-w-0 gap-2 px-2 text-xs font-normal\",\n triggerClassName,\n )}\n {...getReferenceProps()}\n >\n <Icon\n icon={kind === \"time\" ? UiWatch : UiCalendar}\n className=\"text-muted-foreground text-[14px]\"\n />\n <span className=\"truncate font-normal tabular-nums\">{buttonLabel}</span>\n </Button>\n\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"dialog\"\n aria-label={label}\n style={{ ...floatingStyles, zIndex: floatingZ }}\n className={cn(\n \"flex max-h-[min(32rem,calc(100vh-2rem))] w-[22.5rem] max-w-[calc(100vw-1rem)] flex-col overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-lg shadow-black/10 outline-none\",\n panelClassName,\n )}\n {...getFloatingProps()}\n >\n <div className=\"flex items-center justify-between border-b border-border/60 px-3.5 py-2.5\">\n <span className=\"text-[10px] font-semibold uppercase tracking-wider text-muted-foreground\">\n {label}\n </span>\n <span className=\"font-mono text-[10px] text-muted-foreground\">\n {timeEnabled ? selectedTimeZone : \"Relative\"}\n </span>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {activeChipRows.length > 0 && (\n <ChipRowList rows={activeChipRows} activeToken={activeToken} onApply={applyRange} />\n )}\n {activePresets.length > 0 && (\n <div className={activeChipRows.length > 0 ? \"px-3.5 pb-3\" : \"px-3.5 pt-3\"}>\n <PresetList presets={activePresets} onApply={applyRange} />\n </div>\n )}\n\n <div className=\"border-t border-border/60 bg-muted/30 px-3.5 py-2.5\">\n <div className=\"mb-1.5 flex flex-wrap items-center justify-between gap-2\">\n <div className=\"text-[10px] font-semibold uppercase tracking-wider text-muted-foreground\">\n Custom\n </div>\n {timeEnabled && (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <span className=\"text-[10px] font-semibold uppercase tracking-wider text-muted-foreground\">\n TZ\n </span>\n <Select\n aria-label={`${label} timezone`}\n value={selectedTimeZone}\n options={activeTimeZones.map((zone) => ({\n value: zone,\n label: zone,\n }))}\n onChange={(event) => setSelectedTimeZone(event.target.value)}\n className=\"h-7 w-40 truncate px-2 pr-7 font-mono text-xs\"\n />\n </div>\n )}\n </div>\n <div className=\"grid gap-1.5 md:grid-cols-[1fr_auto_1fr] md:items-center\">\n <DraftRangeInput\n aria-label={`${label} from`}\n placeholder={\n fromPlaceholder ?? (kind === \"date\" ? \"YYYY-MM-DD or now-1d\" : \"now-24h\")\n }\n value={draftFrom}\n onChange={setDraftFrom}\n onClear={() => setDraftFrom(\"\")}\n pickerKind={kind}\n timeEnabled={timeEnabled}\n />\n <Icon\n icon={UiArrowRight}\n className=\"hidden text-muted-foreground text-[12px] md:block\"\n />\n <DraftRangeInput\n aria-label={`${label} to`}\n placeholder={toPlaceholder ?? (kind === \"date\" ? \"YYYY-MM-DD or now\" : \"now\")}\n value={draftTo}\n onChange={setDraftTo}\n onClear={() => setDraftTo(\"\")}\n pickerKind={kind}\n timeEnabled={timeEnabled}\n />\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between border-t border-border/60 px-3 py-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-7 px-2.5 text-xs\"\n disabled={!from && !to}\n onClick={clearRange}\n >\n Clear\n </Button>\n <div className=\"flex gap-1.5\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-7 px-2.5 text-xs\"\n onClick={() => setOpen(false)}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n className=\"h-7 px-3.5 text-xs\"\n onClick={() => applyRange(draftFrom, draftTo)}\n >\n Apply\n </Button>\n </div>\n </div>\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n\nfunction DraftRangeInput({\n value,\n onChange,\n onClear,\n placeholder,\n pickerKind,\n timeEnabled,\n ...rest\n}: {\n value: string;\n onChange: (value: string) => void;\n onClear: () => void;\n placeholder: string | undefined;\n pickerKind?: TimeRangeKind;\n timeEnabled: boolean;\n \"aria-label\": string;\n}) {\n const ariaLabel = rest[\"aria-label\"];\n const showCalendar = pickerKind !== undefined;\n const showTimeInput = pickerKind === \"time\" && timeEnabled;\n const { date: datePart, time: timePart } = splitDateTime(value);\n\n function setDatePart(nextDate: string) {\n if (!showTimeInput) {\n onChange(nextDate);\n return;\n }\n if (!nextDate) {\n onChange(timePart ? `T${timePart}` : \"\");\n return;\n }\n onChange(timePart ? `${nextDate}T${timePart}` : nextDate);\n }\n\n function setTimePart(nextTime: string) {\n if (!nextTime) {\n onChange(datePart);\n return;\n }\n onChange(datePart ? `${datePart}T${nextTime}` : `T${nextTime}`);\n }\n\n return (\n <div className=\"space-y-1\">\n <DraftTextInput\n ariaLabel={ariaLabel}\n placeholder={placeholder}\n value={showTimeInput ? datePart : value}\n onChange={showTimeInput ? setDatePart : onChange}\n onClear={onClear}\n showCalendar={showCalendar}\n />\n {showTimeInput && (\n <DraftTimeInput ariaLabel={`${ariaLabel} time`} value={timePart} onChange={setTimePart} />\n )}\n </div>\n );\n}\n\nfunction resolvePresetConfig(\n kind: TimeRangeKind,\n presets: TimeRangeProps[\"presets\"],\n chipRows: TimeRangeChipRow[] | undefined,\n) {\n if (presets === undefined) {\n return {\n rows: chipRows ?? (kind === \"date\" ? defaultDateChipRows : defaultTimeChipRows),\n presets: [] as TimeRangePreset[],\n };\n }\n\n const rows: TimeRangeChipRow[] = chipRows ? [...chipRows] : [];\n const presetList: TimeRangePreset[] = [];\n\n for (const preset of presets) {\n if (typeof preset !== \"string\") {\n presetList.push(preset);\n continue;\n }\n if (preset === \"this\" || preset === \"last\") {\n rows.push(thisLastChipRows[preset]);\n continue;\n }\n rows.push(timeChipRowsByGroup[preset]);\n }\n\n return { rows, presets: presetList };\n}\n\nfunction DraftTextInput({\n ariaLabel,\n value,\n onChange,\n onClear,\n placeholder,\n showCalendar,\n}: {\n ariaLabel: string;\n value: string;\n onChange: (value: string) => void;\n onClear: () => void;\n placeholder: string | undefined;\n showCalendar: boolean;\n}) {\n const pickerRef = useRef<HTMLInputElement | null>(null);\n const hasValue = value.length > 0;\n const paddingRight = showCalendar ? (hasValue ? \"pr-12\" : \"pr-7\") : \"pr-7\";\n\n return (\n <div className=\"relative\">\n <input\n type=\"text\"\n aria-label={ariaLabel}\n placeholder={placeholder}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n className={cn(\n \"h-8 w-full rounded-md border border-input bg-background px-2 font-mono text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n paddingRight,\n )}\n />\n {hasValue && (\n <button\n type=\"button\"\n aria-label={`Clear ${ariaLabel}`}\n onClick={onClear}\n className={cn(\n \"absolute inset-y-0 inline-flex w-5 items-center justify-center text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n showCalendar ? \"right-6\" : \"right-1\",\n )}\n >\n <Icon icon={UiClose} className=\"text-[12px]\" />\n </button>\n )}\n {showCalendar && (\n <>\n <input\n ref={pickerRef}\n type=\"date\"\n tabIndex={-1}\n aria-hidden=\"true\"\n value={/^\\d{4}-\\d{2}-\\d{2}$/.test(value.trim()) ? value.trim() : \"\"}\n onChange={(event) => onChange(event.target.value)}\n className=\"pointer-events-none absolute left-0 top-0 h-0 w-0 opacity-0\"\n />\n <button\n type=\"button\"\n aria-label={`Pick ${ariaLabel}`}\n onClick={() => pickerRef.current?.showPicker?.()}\n className=\"absolute inset-y-0 right-1 inline-flex w-5 items-center justify-center text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <Icon icon={UiCalendar} className=\"text-[12px]\" />\n </button>\n </>\n )}\n </div>\n );\n}\n\nfunction DraftTimeInput({\n ariaLabel,\n value,\n onChange,\n}: {\n ariaLabel: string;\n value: string;\n onChange: (value: string) => void;\n}) {\n return (\n <input\n type=\"time\"\n aria-label={ariaLabel}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n className=\"h-8 w-full rounded-md border border-input bg-background px-2 font-mono text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n );\n}\n\nfunction splitDateTime(value: string) {\n const trimmed = value.trim();\n const match = /^(\\d{4}-\\d{2}-\\d{2})?T?(\\d{2}:\\d{2})?/.exec(trimmed);\n if (!match || trimmed.startsWith(\"now\") || /^[+-]/.test(trimmed)) {\n return { date: trimmed, time: \"\" };\n }\n return { date: match[1] ?? \"\", time: match[2] ?? \"\" };\n}\n\nfunction ChipRowList({\n rows,\n activeToken,\n onApply,\n}: {\n rows: TimeRangeChipRow[];\n activeToken: string;\n onApply: (from: string, to: string) => void;\n}) {\n if (rows.length === 0) return null;\n\n return (\n <div className=\"px-3.5 py-3\" role=\"list\" aria-label=\"Time filters\">\n {rows.map((row, index) => (\n <div\n key={row.id}\n className={cn(\n \"grid grid-cols-[2rem_1fr] items-center gap-2\",\n index === rows.length - 1 ? \"\" : \"mb-1.5\",\n )}\n role=\"listitem\"\n >\n <span className=\"text-[9.5px] font-bold uppercase tracking-wider text-muted-foreground\">\n {row.label}\n </span>\n <div className=\"flex divide-x divide-border overflow-hidden rounded-[5px] border border-border\">\n {row.chips.map((chip) => {\n const active = chip.token === activeToken;\n return (\n <button\n key={chip.token}\n type=\"button\"\n title={chip.title ?? chip.ariaLabel ?? chip.token}\n aria-label={chip.ariaLabel ?? chip.token}\n aria-pressed={active}\n onClick={() => onApply(chip.token, chip.to ?? \"now\")}\n className={cn(\n \"flex-1 min-w-0 px-0 py-1.5 text-center font-mono text-[11.5px] leading-none transition-colors focus-visible:relative focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n active\n ? \"bg-foreground font-semibold text-background\"\n : \"bg-background font-medium text-muted-foreground hover:bg-muted\",\n )}\n >\n {chip.label}\n </button>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n );\n}\n\nfunction PresetList({\n presets,\n onApply,\n}: {\n presets: TimeRangePreset[];\n onApply: (from: string, to: string) => void;\n}) {\n if (presets.length === 0) return null;\n\n return (\n <div className=\"mb-3 space-y-1\" role=\"list\" aria-label=\"Time filters\">\n {presets.map((preset) => (\n <button\n key={`${preset.label}-${preset.from}-${preset.to}`}\n type=\"button\"\n className=\"flex h-8 w-full items-center justify-between gap-3 rounded-md border border-border bg-background px-2 text-left text-xs hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n onClick={() => onApply(preset.from, preset.to)}\n >\n <span className=\"min-w-0 truncate font-medium\">{preset.label}</span>\n <span className=\"shrink-0 font-mono text-[11px] text-muted-foreground\">\n {formatPresetRange(preset)}\n </span>\n </button>\n ))}\n </div>\n );\n}\n\nfunction formatSelectedPresetLabel(\n from: string,\n to: string,\n rows: TimeRangeChipRow[],\n presets: TimeRangePreset[],\n) {\n const normalizedFrom = from.trim();\n const normalizedTo = to.trim();\n if (!normalizedFrom && !normalizedTo) return undefined;\n\n for (const row of rows) {\n for (const chip of row.chips) {\n if (chip.token === normalizedFrom && (chip.to ?? \"now\") === normalizedTo) {\n return chip.ariaLabel ?? chip.title ?? chip.label;\n }\n }\n }\n\n return presets.find((preset) => preset.from === normalizedFrom && preset.to === normalizedTo)\n ?.label;\n}\n\nfunction formatPresetRange({ from, to }: TimeRangePreset) {\n if (!from) return to;\n if (!to || to === \"now\") return from;\n return `${from} -> ${to}`;\n}\n\nfunction normalizeRangeValue(\n kind: TimeRangeKind,\n value: string,\n options: { timeEnabled?: boolean; timeZone?: string } = {},\n) {\n const trimmed = value.trim();\n if (!trimmed) return \"\";\n if (trimmed === \"now\" || trimmed.startsWith(\"now\")) return trimmed;\n if (kind === \"time\" && /^[+-]\\d/.test(trimmed)) return `now${trimmed}`;\n if (kind === \"time\" && options.timeEnabled) {\n return encodeDateTimeWithOffset(trimmed, options.timeZone ?? getDefaultTimeZone());\n }\n return trimmed;\n}\n\nfunction formatRangeLabel(kind: TimeRangeKind, from: string, to: string, emptyLabel?: string) {\n const trimmedFrom = from.trim();\n const trimmedTo = to.trim();\n\n if (!trimmedFrom && !trimmedTo) {\n return emptyLabel ?? (kind === \"date\" ? \"Any date\" : \"now-24h\");\n }\n\n if (kind === \"time\") {\n const normalizedFrom = normalizeRangeValue(kind, trimmedFrom);\n const normalizedTo = normalizeRangeValue(kind, trimmedTo || \"now\");\n if (!normalizedFrom) return normalizedTo;\n if (normalizedTo === \"now\") return normalizedFrom;\n return `${normalizedFrom} -> ${normalizedTo}`;\n }\n\n if (trimmedFrom && trimmedTo && trimmedFrom === trimmedTo) {\n return trimmedFrom;\n }\n\n if (!trimmedFrom) return trimmedTo;\n if (!trimmedTo) return trimmedFrom;\n return `${trimmedFrom} -> ${trimmedTo}`;\n}\n\nfunction getDefaultTimeZone() {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || \"UTC\";\n } catch {\n return \"UTC\";\n }\n}\n\nfunction resolveTimeZones(timeZones: string[] | undefined, selectedTimeZone: string) {\n const zones = timeZones && timeZones.length > 0 ? timeZones : [selectedTimeZone, \"UTC\"];\n return Array.from(new Set([selectedTimeZone, ...zones, \"UTC\"].filter(Boolean)));\n}\n\nfunction encodeDateTimeWithOffset(value: string, timeZone: string) {\n if (!isLocalDateTime(value) || hasExplicitOffset(value)) return value;\n\n const match = value.match(/^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::(\\d{2}))?(?:\\.\\d+)?$/);\n if (!match) return value;\n\n const [, year, month, day, hour, minute, second = \"00\"] = match;\n const localUtcMillis = Date.UTC(\n Number(year),\n Number(month) - 1,\n Number(day),\n Number(hour),\n Number(minute),\n Number(second),\n );\n const offsetMinutes = getTimeZoneOffsetMinutes(timeZone, localUtcMillis);\n return `${year}-${month}-${day}T${hour}:${minute}:${second}${formatOffset(offsetMinutes)}`;\n}\n\nfunction isLocalDateTime(value: string) {\n return /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2})?(?:\\.\\d+)?$/.test(value.trim());\n}\n\nfunction hasExplicitOffset(value: string) {\n return /(?:Z|[+-]\\d{2}:?\\d{2})$/i.test(value.trim());\n}\n\nfunction getTimeZoneOffsetMinutes(timeZone: string, localUtcMillis: number) {\n try {\n const formatter = new Intl.DateTimeFormat(\"en-US\", {\n timeZone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n });\n const parts = Object.fromEntries(\n formatter\n .formatToParts(new Date(localUtcMillis))\n .filter((part) => part.type !== \"literal\")\n .map((part) => [part.type, part.value]),\n );\n const zonedUtcMillis = Date.UTC(\n Number(parts.year),\n Number(parts.month) - 1,\n Number(parts.day),\n Number(parts.hour),\n Number(parts.minute),\n Number(parts.second),\n );\n return (zonedUtcMillis - localUtcMillis) / 60_000;\n } catch {\n return 0;\n }\n}\n\nfunction formatOffset(offsetMinutes: number) {\n const sign = offsetMinutes >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offsetMinutes);\n const hours = Math.floor(abs / 60)\n .toString()\n .padStart(2, \"0\");\n const minutes = Math.floor(abs % 60)\n .toString()\n .padStart(2, \"0\");\n return `${sign}${hours}:${minutes}`;\n}\n"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@ const reactDom = require("react-dom");
6
6
  const utils = require("../lib/utils.cjs");
7
7
  const Icon = require("../data/Icon.cjs");
8
8
  const Tree = require("../data/Tree.cjs");
9
+ const modalStack = require("../overlay/modalStack.cjs");
9
10
  const jsonSchemaFormSize = require("./json-schema-form-size.cjs");
10
11
  const UiChevronDown = require("../icons/components/UiChevronDown.cjs");
11
12
  const PANEL_MAX_WIDTH_PX = 480;
@@ -33,6 +34,11 @@ function TreePickerField({
33
34
  const rootRef = react.useRef(null);
34
35
  const anchorRef = react.useRef(null);
35
36
  const panelRef = react.useRef(null);
37
+ modalStack.useEscapeLayer(open, () => {
38
+ var _a;
39
+ setOpen(false);
40
+ (_a = anchorRef.current) == null ? void 0 : _a.focus();
41
+ });
36
42
  react.useLayoutEffect(() => {
37
43
  if (!open) {
38
44
  setPos(null);
@@ -111,9 +117,6 @@ function TreePickerField({
111
117
  "z-50 flex max-h-[60vh] flex-col overflow-hidden rounded-md border border-border bg-popover shadow-md",
112
118
  panelClassName
113
119
  ),
114
- onKeyDown: (e) => {
115
- if (e.key === "Escape") setOpen(false);
116
- },
117
120
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
118
121
  Tree.Tree,
119
122
  {
@@ -1 +1 @@
1
- {"version":3,"file":"TreePickerField.cjs","sources":["../../src/components/TreePickerField.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\nimport { Tree, type TreeProps } from \"../data/Tree\";\nimport { inputSizeClass, type FormSize } from \"./json-schema-form-size\";\n\n// Upper bound the open panel grows to so wide trees show full labels before\n// truncating; the panel is never narrower than the trigger.\nconst PANEL_MAX_WIDTH_PX = 480;\n\nexport interface TreePickerFieldProps<T> {\n /** Root nodes of the tree to browse. */\n roots: T[];\n getKey: (node: T) => string | number;\n getChildren: (node: T) => T[] | undefined;\n renderRow: TreeProps<T>[\"renderRow\"];\n getSearchText?: (node: T) => string;\n defaultOpen?: (node: T, depth: number) => boolean;\n /**\n * Decides which nodes commit a selection when clicked. A click on a\n * non-selectable node still toggles its expansion (it never closes the\n * picker). Defaults to every node selectable.\n */\n isSelectable?: (node: T) => boolean;\n /** Fires for a selectable node, then closes the dropdown. */\n onSelect: (node: T) => void;\n /** Current-selection display in the trigger; falls back to `placeholder`. */\n label?: ReactNode;\n placeholder?: string;\n disabled?: boolean;\n size?: FormSize;\n className?: string;\n triggerClassName?: string;\n panelClassName?: string;\n}\n\n// TreePickerField is a form-field-styled trigger (matching Combobox's closed\n// input) that opens a portal-anchored dropdown holding a Tree. The dropdown is\n// positioned with fixed coordinates measured from the trigger so it escapes any\n// overflow-hidden / scroll ancestor (e.g. a modal body). A click on a selectable\n// node commits and closes; a non-selectable node only toggles expansion.\nexport function TreePickerField<T>({\n roots,\n getKey,\n getChildren,\n renderRow,\n getSearchText,\n defaultOpen,\n isSelectable,\n onSelect,\n label,\n placeholder = \"Select…\",\n disabled,\n size,\n className,\n triggerClassName,\n panelClassName,\n}: TreePickerFieldProps<T>) {\n const [open, setOpen] = useState(false);\n const [pos, setPos] = useState<{ top: number; left: number; width: number; maxWidth: number } | null>(\n null,\n );\n const rootRef = useRef<HTMLDivElement>(null);\n const anchorRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n\n // Position the portaled panel from the trigger rect, repositioning while open.\n useLayoutEffect(() => {\n if (!open) {\n setPos(null);\n return;\n }\n const update = () => {\n const anchor = anchorRef.current;\n if (!anchor) return;\n const rect = anchor.getBoundingClientRect();\n const viewportCap = window.innerWidth - rect.left - 8;\n const maxWidth = Math.max(rect.width, Math.min(PANEL_MAX_WIDTH_PX, viewportCap));\n setPos({ top: rect.bottom + 4, left: rect.left, width: rect.width, maxWidth });\n };\n update();\n window.addEventListener(\"scroll\", update, true);\n window.addEventListener(\"resize\", update);\n return () => {\n window.removeEventListener(\"scroll\", update, true);\n window.removeEventListener(\"resize\", update);\n };\n }, [open]);\n\n // Dismiss on an outside pointer-down (the trigger and panel are exempt).\n useEffect(() => {\n if (!open) return;\n const onPointerDown = (e: MouseEvent) => {\n const target = e.target as Node;\n if (!rootRef.current?.contains(target) && !panelRef.current?.contains(target)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", onPointerDown);\n return () => document.removeEventListener(\"mousedown\", onPointerDown);\n }, [open]);\n\n return (\n <div ref={rootRef} className={cn(\"relative\", className)}>\n <button\n ref={anchorRef}\n type=\"button\"\n disabled={disabled}\n aria-haspopup=\"tree\"\n aria-expanded={open}\n onClick={() => setOpen((o) => !o)}\n className={cn(\n \"relative flex w-full items-center rounded-md border border-input bg-background text-left text-foreground\",\n size ? inputSizeClass[size] : \"h-control-h px-control-px text-sm\",\n \"pr-8\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n triggerClassName,\n )}\n >\n <span className={cn(\"min-w-0 flex-1 truncate\", label == null && \"text-muted-foreground\")}>\n {label ?? placeholder}\n </span>\n <span className=\"pointer-events-none absolute right-0 flex h-full items-center px-2 text-muted-foreground\">\n <Icon icon={UiChevronDown} className=\"text-xs\" />\n </span>\n </button>\n\n {open &&\n pos &&\n createPortal(\n <div\n ref={panelRef}\n data-slot=\"tree-picker-popup\"\n style={{\n position: \"fixed\",\n top: pos.top,\n left: pos.left,\n // Grow to fit the widest row/toolbar, but never narrower than the\n // trigger nor wider than maxWidth — so labels show in full and the\n // toolbar's intrinsic-width search input never forces a scrollbar.\n minWidth: pos.width,\n width: \"max-content\",\n maxWidth: pos.maxWidth,\n }}\n className={cn(\n \"z-50 flex max-h-[60vh] flex-col overflow-hidden rounded-md border border-border bg-popover shadow-md\",\n panelClassName,\n )}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") setOpen(false);\n }}\n >\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <Tree<T>\n roots={roots}\n getKey={getKey}\n getChildren={getChildren}\n renderRow={renderRow}\n {...(getSearchText ? { getSearchText } : {})}\n {...(defaultOpen ? { defaultOpen } : {})}\n onSelect={(node) => {\n if (!isSelectable || isSelectable(node)) {\n onSelect(node);\n setOpen(false);\n }\n }}\n />\n </div>\n </div>,\n document.body,\n )}\n </div>\n );\n}\n"],"names":["useState","useRef","useLayoutEffect","useEffect","jsxs","cn","inputSizeClass","jsx","Icon","UiChevronDown","createPortal","Tree"],"mappings":"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"}
1
+ {"version":3,"file":"TreePickerField.cjs","sources":["../../src/components/TreePickerField.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\nimport { Tree, type TreeProps } from \"../data/Tree\";\nimport { useEscapeLayer } from \"../overlay/modalStack\";\nimport { inputSizeClass, type FormSize } from \"./json-schema-form-size\";\n\n// Upper bound the open panel grows to so wide trees show full labels before\n// truncating; the panel is never narrower than the trigger.\nconst PANEL_MAX_WIDTH_PX = 480;\n\nexport interface TreePickerFieldProps<T> {\n /** Root nodes of the tree to browse. */\n roots: T[];\n getKey: (node: T) => string | number;\n getChildren: (node: T) => T[] | undefined;\n renderRow: TreeProps<T>[\"renderRow\"];\n getSearchText?: (node: T) => string;\n defaultOpen?: (node: T, depth: number) => boolean;\n /**\n * Decides which nodes commit a selection when clicked. A click on a\n * non-selectable node still toggles its expansion (it never closes the\n * picker). Defaults to every node selectable.\n */\n isSelectable?: (node: T) => boolean;\n /** Fires for a selectable node, then closes the dropdown. */\n onSelect: (node: T) => void;\n /** Current-selection display in the trigger; falls back to `placeholder`. */\n label?: ReactNode;\n placeholder?: string;\n disabled?: boolean;\n size?: FormSize;\n className?: string;\n triggerClassName?: string;\n panelClassName?: string;\n}\n\n// TreePickerField is a form-field-styled trigger (matching Combobox's closed\n// input) that opens a portal-anchored dropdown holding a Tree. The dropdown is\n// positioned with fixed coordinates measured from the trigger so it escapes any\n// overflow-hidden / scroll ancestor (e.g. a modal body). A click on a selectable\n// node commits and closes; a non-selectable node only toggles expansion.\nexport function TreePickerField<T>({\n roots,\n getKey,\n getChildren,\n renderRow,\n getSearchText,\n defaultOpen,\n isSelectable,\n onSelect,\n label,\n placeholder = \"Select…\",\n disabled,\n size,\n className,\n triggerClassName,\n panelClassName,\n}: TreePickerFieldProps<T>) {\n const [open, setOpen] = useState(false);\n const [pos, setPos] = useState<{ top: number; left: number; width: number; maxWidth: number } | null>(\n null,\n );\n const rootRef = useRef<HTMLDivElement>(null);\n const anchorRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n\n useEscapeLayer(open, () => {\n setOpen(false);\n anchorRef.current?.focus();\n });\n\n // Position the portaled panel from the trigger rect, repositioning while open.\n useLayoutEffect(() => {\n if (!open) {\n setPos(null);\n return;\n }\n const update = () => {\n const anchor = anchorRef.current;\n if (!anchor) return;\n const rect = anchor.getBoundingClientRect();\n const viewportCap = window.innerWidth - rect.left - 8;\n const maxWidth = Math.max(rect.width, Math.min(PANEL_MAX_WIDTH_PX, viewportCap));\n setPos({ top: rect.bottom + 4, left: rect.left, width: rect.width, maxWidth });\n };\n update();\n window.addEventListener(\"scroll\", update, true);\n window.addEventListener(\"resize\", update);\n return () => {\n window.removeEventListener(\"scroll\", update, true);\n window.removeEventListener(\"resize\", update);\n };\n }, [open]);\n\n // Dismiss on an outside pointer-down (the trigger and panel are exempt).\n useEffect(() => {\n if (!open) return;\n const onPointerDown = (e: MouseEvent) => {\n const target = e.target as Node;\n if (!rootRef.current?.contains(target) && !panelRef.current?.contains(target)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", onPointerDown);\n return () => document.removeEventListener(\"mousedown\", onPointerDown);\n }, [open]);\n\n return (\n <div ref={rootRef} className={cn(\"relative\", className)}>\n <button\n ref={anchorRef}\n type=\"button\"\n disabled={disabled}\n aria-haspopup=\"tree\"\n aria-expanded={open}\n onClick={() => setOpen((o) => !o)}\n className={cn(\n \"relative flex w-full items-center rounded-md border border-input bg-background text-left text-foreground\",\n size ? inputSizeClass[size] : \"h-control-h px-control-px text-sm\",\n \"pr-8\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n triggerClassName,\n )}\n >\n <span className={cn(\"min-w-0 flex-1 truncate\", label == null && \"text-muted-foreground\")}>\n {label ?? placeholder}\n </span>\n <span className=\"pointer-events-none absolute right-0 flex h-full items-center px-2 text-muted-foreground\">\n <Icon icon={UiChevronDown} className=\"text-xs\" />\n </span>\n </button>\n\n {open &&\n pos &&\n createPortal(\n <div\n ref={panelRef}\n data-slot=\"tree-picker-popup\"\n style={{\n position: \"fixed\",\n top: pos.top,\n left: pos.left,\n // Grow to fit the widest row/toolbar, but never narrower than the\n // trigger nor wider than maxWidth — so labels show in full and the\n // toolbar's intrinsic-width search input never forces a scrollbar.\n minWidth: pos.width,\n width: \"max-content\",\n maxWidth: pos.maxWidth,\n }}\n className={cn(\n \"z-50 flex max-h-[60vh] flex-col overflow-hidden rounded-md border border-border bg-popover shadow-md\",\n panelClassName,\n )}\n >\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <Tree<T>\n roots={roots}\n getKey={getKey}\n getChildren={getChildren}\n renderRow={renderRow}\n {...(getSearchText ? { getSearchText } : {})}\n {...(defaultOpen ? { defaultOpen } : {})}\n onSelect={(node) => {\n if (!isSelectable || isSelectable(node)) {\n onSelect(node);\n setOpen(false);\n }\n }}\n />\n </div>\n </div>,\n document.body,\n )}\n </div>\n );\n}\n"],"names":["useState","useRef","useEscapeLayer","useLayoutEffect","useEffect","jsxs","cn","inputSizeClass","jsx","Icon","UiChevronDown","createPortal","Tree"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TreePickerField.d.ts","sourceRoot":"","sources":["../../src/components/TreePickerField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKrF,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAMxE,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,wCAAwC;IACxC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC;IACrC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC;IAC1C,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACrC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IACpC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAClD;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;IACpC,6DAA6D;IAC7D,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAOD,wBAAgB,eAAe,CAAC,CAAC,EAAE,EACjC,KAAK,EACL,MAAM,EACN,WAAW,EACX,SAAS,EACT,aAAa,EACb,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,WAAuB,EACvB,QAAQ,EACR,IAAI,EACJ,SAAS,EACT,gBAAgB,EAChB,cAAc,GACf,EAAE,oBAAoB,CAAC,CAAC,CAAC,2CAqHzB"}
1
+ {"version":3,"file":"TreePickerField.d.ts","sourceRoot":"","sources":["../../src/components/TreePickerField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKrF,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAMxE,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,wCAAwC;IACxC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC;IACrC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC;IAC1C,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACrC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IACpC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAClD;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;IACpC,6DAA6D;IAC7D,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAOD,wBAAgB,eAAe,CAAC,CAAC,EAAE,EACjC,KAAK,EACL,MAAM,EACN,WAAW,EACX,SAAS,EACT,aAAa,EACb,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,WAAuB,EACvB,QAAQ,EACR,IAAI,EACJ,SAAS,EACT,gBAAgB,EAChB,cAAc,GACf,EAAE,oBAAoB,CAAC,CAAC,CAAC,2CAuHzB"}
@@ -4,6 +4,7 @@ import { createPortal } from "react-dom";
4
4
  import { cn } from "../lib/utils.js";
5
5
  import { Icon } from "../data/Icon.js";
6
6
  import { Tree } from "../data/Tree.js";
7
+ import { useEscapeLayer } from "../overlay/modalStack.js";
7
8
  import { inputSizeClass } from "./json-schema-form-size.js";
8
9
  import { UiChevronDown } from "../icons/components/UiChevronDown.js";
9
10
  const PANEL_MAX_WIDTH_PX = 480;
@@ -31,6 +32,11 @@ function TreePickerField({
31
32
  const rootRef = useRef(null);
32
33
  const anchorRef = useRef(null);
33
34
  const panelRef = useRef(null);
35
+ useEscapeLayer(open, () => {
36
+ var _a;
37
+ setOpen(false);
38
+ (_a = anchorRef.current) == null ? void 0 : _a.focus();
39
+ });
34
40
  useLayoutEffect(() => {
35
41
  if (!open) {
36
42
  setPos(null);
@@ -109,9 +115,6 @@ function TreePickerField({
109
115
  "z-50 flex max-h-[60vh] flex-col overflow-hidden rounded-md border border-border bg-popover shadow-md",
110
116
  panelClassName
111
117
  ),
112
- onKeyDown: (e) => {
113
- if (e.key === "Escape") setOpen(false);
114
- },
115
118
  children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: /* @__PURE__ */ jsx(
116
119
  Tree,
117
120
  {
@@ -1 +1 @@
1
- {"version":3,"file":"TreePickerField.js","sources":["../../src/components/TreePickerField.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\nimport { Tree, type TreeProps } from \"../data/Tree\";\nimport { inputSizeClass, type FormSize } from \"./json-schema-form-size\";\n\n// Upper bound the open panel grows to so wide trees show full labels before\n// truncating; the panel is never narrower than the trigger.\nconst PANEL_MAX_WIDTH_PX = 480;\n\nexport interface TreePickerFieldProps<T> {\n /** Root nodes of the tree to browse. */\n roots: T[];\n getKey: (node: T) => string | number;\n getChildren: (node: T) => T[] | undefined;\n renderRow: TreeProps<T>[\"renderRow\"];\n getSearchText?: (node: T) => string;\n defaultOpen?: (node: T, depth: number) => boolean;\n /**\n * Decides which nodes commit a selection when clicked. A click on a\n * non-selectable node still toggles its expansion (it never closes the\n * picker). Defaults to every node selectable.\n */\n isSelectable?: (node: T) => boolean;\n /** Fires for a selectable node, then closes the dropdown. */\n onSelect: (node: T) => void;\n /** Current-selection display in the trigger; falls back to `placeholder`. */\n label?: ReactNode;\n placeholder?: string;\n disabled?: boolean;\n size?: FormSize;\n className?: string;\n triggerClassName?: string;\n panelClassName?: string;\n}\n\n// TreePickerField is a form-field-styled trigger (matching Combobox's closed\n// input) that opens a portal-anchored dropdown holding a Tree. The dropdown is\n// positioned with fixed coordinates measured from the trigger so it escapes any\n// overflow-hidden / scroll ancestor (e.g. a modal body). A click on a selectable\n// node commits and closes; a non-selectable node only toggles expansion.\nexport function TreePickerField<T>({\n roots,\n getKey,\n getChildren,\n renderRow,\n getSearchText,\n defaultOpen,\n isSelectable,\n onSelect,\n label,\n placeholder = \"Select…\",\n disabled,\n size,\n className,\n triggerClassName,\n panelClassName,\n}: TreePickerFieldProps<T>) {\n const [open, setOpen] = useState(false);\n const [pos, setPos] = useState<{ top: number; left: number; width: number; maxWidth: number } | null>(\n null,\n );\n const rootRef = useRef<HTMLDivElement>(null);\n const anchorRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n\n // Position the portaled panel from the trigger rect, repositioning while open.\n useLayoutEffect(() => {\n if (!open) {\n setPos(null);\n return;\n }\n const update = () => {\n const anchor = anchorRef.current;\n if (!anchor) return;\n const rect = anchor.getBoundingClientRect();\n const viewportCap = window.innerWidth - rect.left - 8;\n const maxWidth = Math.max(rect.width, Math.min(PANEL_MAX_WIDTH_PX, viewportCap));\n setPos({ top: rect.bottom + 4, left: rect.left, width: rect.width, maxWidth });\n };\n update();\n window.addEventListener(\"scroll\", update, true);\n window.addEventListener(\"resize\", update);\n return () => {\n window.removeEventListener(\"scroll\", update, true);\n window.removeEventListener(\"resize\", update);\n };\n }, [open]);\n\n // Dismiss on an outside pointer-down (the trigger and panel are exempt).\n useEffect(() => {\n if (!open) return;\n const onPointerDown = (e: MouseEvent) => {\n const target = e.target as Node;\n if (!rootRef.current?.contains(target) && !panelRef.current?.contains(target)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", onPointerDown);\n return () => document.removeEventListener(\"mousedown\", onPointerDown);\n }, [open]);\n\n return (\n <div ref={rootRef} className={cn(\"relative\", className)}>\n <button\n ref={anchorRef}\n type=\"button\"\n disabled={disabled}\n aria-haspopup=\"tree\"\n aria-expanded={open}\n onClick={() => setOpen((o) => !o)}\n className={cn(\n \"relative flex w-full items-center rounded-md border border-input bg-background text-left text-foreground\",\n size ? inputSizeClass[size] : \"h-control-h px-control-px text-sm\",\n \"pr-8\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n triggerClassName,\n )}\n >\n <span className={cn(\"min-w-0 flex-1 truncate\", label == null && \"text-muted-foreground\")}>\n {label ?? placeholder}\n </span>\n <span className=\"pointer-events-none absolute right-0 flex h-full items-center px-2 text-muted-foreground\">\n <Icon icon={UiChevronDown} className=\"text-xs\" />\n </span>\n </button>\n\n {open &&\n pos &&\n createPortal(\n <div\n ref={panelRef}\n data-slot=\"tree-picker-popup\"\n style={{\n position: \"fixed\",\n top: pos.top,\n left: pos.left,\n // Grow to fit the widest row/toolbar, but never narrower than the\n // trigger nor wider than maxWidth — so labels show in full and the\n // toolbar's intrinsic-width search input never forces a scrollbar.\n minWidth: pos.width,\n width: \"max-content\",\n maxWidth: pos.maxWidth,\n }}\n className={cn(\n \"z-50 flex max-h-[60vh] flex-col overflow-hidden rounded-md border border-border bg-popover shadow-md\",\n panelClassName,\n )}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") setOpen(false);\n }}\n >\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <Tree<T>\n roots={roots}\n getKey={getKey}\n getChildren={getChildren}\n renderRow={renderRow}\n {...(getSearchText ? { getSearchText } : {})}\n {...(defaultOpen ? { defaultOpen } : {})}\n onSelect={(node) => {\n if (!isSelectable || isSelectable(node)) {\n onSelect(node);\n setOpen(false);\n }\n }}\n />\n </div>\n </div>,\n document.body,\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TreePickerField.js","sources":["../../src/components/TreePickerField.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\nimport { Tree, type TreeProps } from \"../data/Tree\";\nimport { useEscapeLayer } from \"../overlay/modalStack\";\nimport { inputSizeClass, type FormSize } from \"./json-schema-form-size\";\n\n// Upper bound the open panel grows to so wide trees show full labels before\n// truncating; the panel is never narrower than the trigger.\nconst PANEL_MAX_WIDTH_PX = 480;\n\nexport interface TreePickerFieldProps<T> {\n /** Root nodes of the tree to browse. */\n roots: T[];\n getKey: (node: T) => string | number;\n getChildren: (node: T) => T[] | undefined;\n renderRow: TreeProps<T>[\"renderRow\"];\n getSearchText?: (node: T) => string;\n defaultOpen?: (node: T, depth: number) => boolean;\n /**\n * Decides which nodes commit a selection when clicked. A click on a\n * non-selectable node still toggles its expansion (it never closes the\n * picker). Defaults to every node selectable.\n */\n isSelectable?: (node: T) => boolean;\n /** Fires for a selectable node, then closes the dropdown. */\n onSelect: (node: T) => void;\n /** Current-selection display in the trigger; falls back to `placeholder`. */\n label?: ReactNode;\n placeholder?: string;\n disabled?: boolean;\n size?: FormSize;\n className?: string;\n triggerClassName?: string;\n panelClassName?: string;\n}\n\n// TreePickerField is a form-field-styled trigger (matching Combobox's closed\n// input) that opens a portal-anchored dropdown holding a Tree. The dropdown is\n// positioned with fixed coordinates measured from the trigger so it escapes any\n// overflow-hidden / scroll ancestor (e.g. a modal body). A click on a selectable\n// node commits and closes; a non-selectable node only toggles expansion.\nexport function TreePickerField<T>({\n roots,\n getKey,\n getChildren,\n renderRow,\n getSearchText,\n defaultOpen,\n isSelectable,\n onSelect,\n label,\n placeholder = \"Select…\",\n disabled,\n size,\n className,\n triggerClassName,\n panelClassName,\n}: TreePickerFieldProps<T>) {\n const [open, setOpen] = useState(false);\n const [pos, setPos] = useState<{ top: number; left: number; width: number; maxWidth: number } | null>(\n null,\n );\n const rootRef = useRef<HTMLDivElement>(null);\n const anchorRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n\n useEscapeLayer(open, () => {\n setOpen(false);\n anchorRef.current?.focus();\n });\n\n // Position the portaled panel from the trigger rect, repositioning while open.\n useLayoutEffect(() => {\n if (!open) {\n setPos(null);\n return;\n }\n const update = () => {\n const anchor = anchorRef.current;\n if (!anchor) return;\n const rect = anchor.getBoundingClientRect();\n const viewportCap = window.innerWidth - rect.left - 8;\n const maxWidth = Math.max(rect.width, Math.min(PANEL_MAX_WIDTH_PX, viewportCap));\n setPos({ top: rect.bottom + 4, left: rect.left, width: rect.width, maxWidth });\n };\n update();\n window.addEventListener(\"scroll\", update, true);\n window.addEventListener(\"resize\", update);\n return () => {\n window.removeEventListener(\"scroll\", update, true);\n window.removeEventListener(\"resize\", update);\n };\n }, [open]);\n\n // Dismiss on an outside pointer-down (the trigger and panel are exempt).\n useEffect(() => {\n if (!open) return;\n const onPointerDown = (e: MouseEvent) => {\n const target = e.target as Node;\n if (!rootRef.current?.contains(target) && !panelRef.current?.contains(target)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", onPointerDown);\n return () => document.removeEventListener(\"mousedown\", onPointerDown);\n }, [open]);\n\n return (\n <div ref={rootRef} className={cn(\"relative\", className)}>\n <button\n ref={anchorRef}\n type=\"button\"\n disabled={disabled}\n aria-haspopup=\"tree\"\n aria-expanded={open}\n onClick={() => setOpen((o) => !o)}\n className={cn(\n \"relative flex w-full items-center rounded-md border border-input bg-background text-left text-foreground\",\n size ? inputSizeClass[size] : \"h-control-h px-control-px text-sm\",\n \"pr-8\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n triggerClassName,\n )}\n >\n <span className={cn(\"min-w-0 flex-1 truncate\", label == null && \"text-muted-foreground\")}>\n {label ?? placeholder}\n </span>\n <span className=\"pointer-events-none absolute right-0 flex h-full items-center px-2 text-muted-foreground\">\n <Icon icon={UiChevronDown} className=\"text-xs\" />\n </span>\n </button>\n\n {open &&\n pos &&\n createPortal(\n <div\n ref={panelRef}\n data-slot=\"tree-picker-popup\"\n style={{\n position: \"fixed\",\n top: pos.top,\n left: pos.left,\n // Grow to fit the widest row/toolbar, but never narrower than the\n // trigger nor wider than maxWidth — so labels show in full and the\n // toolbar's intrinsic-width search input never forces a scrollbar.\n minWidth: pos.width,\n width: \"max-content\",\n maxWidth: pos.maxWidth,\n }}\n className={cn(\n \"z-50 flex max-h-[60vh] flex-col overflow-hidden rounded-md border border-border bg-popover shadow-md\",\n panelClassName,\n )}\n >\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <Tree<T>\n roots={roots}\n getKey={getKey}\n getChildren={getChildren}\n renderRow={renderRow}\n {...(getSearchText ? { getSearchText } : {})}\n {...(defaultOpen ? { defaultOpen } : {})}\n onSelect={(node) => {\n if (!isSelectable || isSelectable(node)) {\n onSelect(node);\n setOpen(false);\n }\n }}\n />\n </div>\n </div>,\n document.body,\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAWA,MAAM,qBAAqB;AAiCpB,SAAS,gBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4B;AAC1B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,KAAK,MAAM,IAAI;AAAA,IACpB;AAAA,EAAA;AAEF,QAAM,UAAU,OAAuB,IAAI;AAC3C,QAAM,YAAY,OAA0B,IAAI;AAChD,QAAM,WAAW,OAAuB,IAAI;AAE5C,iBAAe,MAAM,MAAM;;AACzB,YAAQ,KAAK;AACb,oBAAU,YAAV,mBAAmB;AAAA,EACrB,CAAC;AAGD,kBAAgB,MAAM;AACpB,QAAI,CAAC,MAAM;AACT,aAAO,IAAI;AACX;AAAA,IACF;AACA,UAAM,SAAS,MAAM;AACnB,YAAM,SAAS,UAAU;AACzB,UAAI,CAAC,OAAQ;AACb,YAAM,OAAO,OAAO,sBAAA;AACpB,YAAM,cAAc,OAAO,aAAa,KAAK,OAAO;AACpD,YAAM,WAAW,KAAK,IAAI,KAAK,OAAO,KAAK,IAAI,oBAAoB,WAAW,CAAC;AAC/E,aAAO,EAAE,KAAK,KAAK,SAAS,GAAG,MAAM,KAAK,MAAM,OAAO,KAAK,OAAO,SAAA,CAAU;AAAA,IAC/E;AACA,WAAA;AACA,WAAO,iBAAiB,UAAU,QAAQ,IAAI;AAC9C,WAAO,iBAAiB,UAAU,MAAM;AACxC,WAAO,MAAM;AACX,aAAO,oBAAoB,UAAU,QAAQ,IAAI;AACjD,aAAO,oBAAoB,UAAU,MAAM;AAAA,IAC7C;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAGT,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,gBAAgB,CAAC,MAAkB;;AACvC,YAAM,SAAS,EAAE;AACjB,UAAI,GAAC,aAAQ,YAAR,mBAAiB,SAAS,YAAW,GAAC,cAAS,YAAT,mBAAkB,SAAS,UAAS;AAC7E,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF;AACA,aAAS,iBAAiB,aAAa,aAAa;AACpD,WAAO,MAAM,SAAS,oBAAoB,aAAa,aAAa;AAAA,EACtE,GAAG,CAAC,IAAI,CAAC;AAET,SACE,qBAAC,SAAI,KAAK,SAAS,WAAW,GAAG,YAAY,SAAS,GACpD,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAK;AAAA,QACL;AAAA,QACA,iBAAc;AAAA,QACd,iBAAe;AAAA,QACf,SAAS,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;AAAA,QAChC,WAAW;AAAA,UACT;AAAA,UACA,OAAO,eAAe,IAAI,IAAI;AAAA,UAC9B;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAGF,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAK,WAAW,GAAG,2BAA2B,SAAS,QAAQ,uBAAuB,GACpF,UAAA,SAAS,YAAA,CACZ;AAAA,UACA,oBAAC,QAAA,EAAK,WAAU,4FACd,UAAA,oBAAC,QAAK,MAAM,eAAe,WAAU,UAAA,CAAU,EAAA,CACjD;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAGD,QACC,OACA;AAAA,MACE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAK;AAAA,UACL,aAAU;AAAA,UACV,OAAO;AAAA,YACL,UAAU;AAAA,YACV,KAAK,IAAI;AAAA,YACT,MAAM,IAAI;AAAA;AAAA;AAAA;AAAA,YAIV,UAAU,IAAI;AAAA,YACd,OAAO;AAAA,YACP,UAAU,IAAI;AAAA,UAAA;AAAA,UAEhB,WAAW;AAAA,YACT;AAAA,YACA;AAAA,UAAA;AAAA,UAGF,UAAA,oBAAC,OAAA,EAAI,WAAU,gCACb,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACC,GAAI,gBAAgB,EAAE,cAAA,IAAkB,CAAA;AAAA,cACxC,GAAI,cAAc,EAAE,YAAA,IAAgB,CAAA;AAAA,cACrC,UAAU,CAAC,SAAS;AAClB,oBAAI,CAAC,gBAAgB,aAAa,IAAI,GAAG;AACvC,2BAAS,IAAI;AACb,0BAAQ,KAAK;AAAA,gBACf;AAAA,cACF;AAAA,YAAA;AAAA,UAAA,EACF,CACF;AAAA,QAAA;AAAA,MAAA;AAAA,MAEF,SAAS;AAAA,IAAA;AAAA,EACX,GACJ;AAEJ;"}
@@ -5,6 +5,7 @@ const react = require("react");
5
5
  const react$1 = require("@floating-ui/react");
6
6
  const utils = require("../lib/utils.cjs");
7
7
  const Icon = require("../data/Icon.cjs");
8
+ const modalStack = require("../overlay/modalStack.cjs");
8
9
  const button = require("./button.cjs");
9
10
  const IconButton = require("./IconButton.cjs");
10
11
  const UiChevronDown = require("../icons/components/UiChevronDown.cjs");
@@ -22,6 +23,7 @@ function IconMenuPickerInner({
22
23
  menuClassName
23
24
  }, forwardedRef) {
24
25
  const [open, setOpen] = react.useState(false);
26
+ const floatingZ = modalStack.useFloatingZIndex();
25
27
  const [activeIndex, setActiveIndex] = react.useState(null);
26
28
  const listRef = react.useRef([]);
27
29
  const labelsRef = react.useRef(options.map((o) => o.label));
@@ -41,7 +43,7 @@ function IconMenuPickerInner({
41
43
  middleware: [react$1.offset(6), react$1.flip({ padding: 8 }), react$1.shift({ padding: 8 })]
42
44
  });
43
45
  const click = react$1.useClick(context);
44
- const dismiss = react$1.useDismiss(context);
46
+ const dismiss = react$1.useDismiss(context, { escapeKey: false });
45
47
  const role = react$1.useRole(context, { role: "menu" });
46
48
  const listNav = react$1.useListNavigation(context, {
47
49
  listRef,
@@ -62,6 +64,10 @@ function IconMenuPickerInner({
62
64
  listNav,
63
65
  typeahead
64
66
  ]);
67
+ modalStack.useEscapeLayer(open, () => {
68
+ setOpen(false);
69
+ if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();
70
+ });
65
71
  const referenceRef = react$1.useMergeRefs([refs.setReference, forwardedRef]);
66
72
  const select = (next) => {
67
73
  onChange(next);
@@ -112,9 +118,9 @@ function IconMenuPickerInner({
112
118
  ref: refs.setFloating,
113
119
  role: "menu",
114
120
  "aria-label": ariaLabel,
115
- style: floatingStyles,
121
+ style: { ...floatingStyles, zIndex: floatingZ },
116
122
  className: utils.cn(
117
- "z-[9999] min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5",
123
+ "min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5",
118
124
  menuClassName
119
125
  ),
120
126
  ...getFloatingProps(),
@@ -1 +1 @@
1
- {"version":3,"file":"icon-menu-picker.cjs","sources":["../../src/components/icon-menu-picker.tsx"],"sourcesContent":["import {\n forwardRef,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useMergeRefs,\n useRole,\n useTypeahead,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiCheck, UiChevronDown } from \"../icons\";\nimport { Button } from \"./button\";\nimport { IconButton } from \"./IconButton\";\n\nexport type IconMenuOption<T extends string> = {\n value: T;\n icon: StaticIconComponent;\n label: string;\n description?: string;\n};\n\nexport type IconMenuPickerProps<T extends string> = {\n value: T;\n onChange: (next: T) => void;\n options: IconMenuOption<T>[];\n ariaLabel: string;\n triggerTitle?: string | undefined;\n footer?: ReactNode | undefined;\n showLabel?: boolean | undefined;\n className?: string | undefined;\n triggerClassName?: string | undefined;\n menuClassName?: string | undefined;\n};\n\nfunction IconMenuPickerInner<T extends string>(\n {\n value,\n onChange,\n options,\n ariaLabel,\n triggerTitle,\n footer,\n showLabel,\n className,\n triggerClassName,\n menuClassName,\n }: IconMenuPickerProps<T>,\n forwardedRef: React.Ref<HTMLDivElement>,\n) {\n const [open, setOpen] = useState(false);\n // Index of the item highlighted by keyboard navigation. Null until the user\n // arrows/typeaheads, so a pointer-opened menu starts with the selected row\n // focused (selectedIndex below) rather than the first.\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const listRef = useRef<(HTMLElement | null)[]>([]);\n const labelsRef = useRef<string[]>(options.map((o) => o.label));\n labelsRef.current = options.map((o) => o.label);\n\n const selectedIndex = useMemo(\n () => options.findIndex((option) => option.value === value),\n [options, value],\n );\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n placement: \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(6) reproduces the previous 0.375rem gap; flip opens the menu\n // upward when the trigger is near the bottom edge; shift slides it back\n // on-screen at the left/right edges. The 8px padding keeps a gutter.\n middleware: [offset(6), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n const dismiss = useDismiss(context);\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n selectedIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n const typeahead = useTypeahead(context, {\n listRef: labelsRef,\n activeIndex,\n onMatch: setActiveIndex,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n typeahead,\n ]);\n\n // Keep the trigger wrapper available both to floating-ui (as the positioning\n // reference) and to the consumer's forwarded ref.\n const referenceRef = useMergeRefs([refs.setReference, forwardedRef]);\n\n const select = (next: T) => {\n onChange(next);\n setOpen(false);\n };\n\n const selected = options[selectedIndex];\n if (!selected) {\n throw new Error(`IconMenuPicker: value \"${value}\" not found in options for ${ariaLabel}`);\n }\n\n const triggerTitleText = triggerTitle ?? `${ariaLabel}: ${selected.label}`;\n\n return (\n <div ref={referenceRef} className={cn(\"relative inline-flex\", showLabel && \"w-full\", className)}>\n {showLabel ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n aria-label={ariaLabel}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title={triggerTitleText}\n className={cn(\n \"w-full justify-start text-muted-foreground hover:text-foreground\",\n triggerClassName,\n )}\n {...getReferenceProps()}\n >\n <Icon icon={selected.icon} className=\"shrink-0 text-foreground\" />\n <span className=\"min-w-0 flex-1 truncate text-left capitalize\">\n {`${ariaLabel}: ${selected.label}`}\n </span>\n <Icon icon={UiChevronDown} className=\"shrink-0 text-muted-foreground\" />\n </Button>\n ) : (\n <IconButton\n icon={selected.icon}\n label={ariaLabel}\n title={triggerTitleText}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n className={triggerClassName}\n {...getReferenceProps()}\n />\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false} initialFocus={selectedIndex}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={ariaLabel}\n style={floatingStyles}\n className={cn(\n \"z-[9999] min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {options.map((option, index) => {\n const active = option.value === value;\n return (\n <button\n key={option.value}\n ref={(node) => {\n listRef.current[index] = node;\n }}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n tabIndex={index === activeIndex ? 0 : -1}\n className={cn(\n \"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm transition-colors\",\n \"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none\",\n active && \"text-foreground\",\n )}\n {...getItemProps({\n onClick: () => select(option.value),\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n select(option.value);\n }\n },\n })}\n >\n <Icon icon={option.icon} className=\"shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate capitalize\">{option.label}</span>\n {active ? (\n <Icon icon={UiCheck} className=\"shrink-0 text-foreground\" />\n ) : (\n <span className=\"inline-block size-4 shrink-0\" aria-hidden />\n )}\n </button>\n );\n })}\n {footer ? (\n <div className=\"mt-1 border-t border-border/60 px-2 py-1.5 text-[11px] text-muted-foreground\">\n {footer}\n </div>\n ) : null}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n\nexport const IconMenuPicker = forwardRef(IconMenuPickerInner) as <T extends string>(\n props: IconMenuPickerProps<T> & { ref?: React.Ref<HTMLDivElement> },\n) => JSX.Element;\n"],"names":["useState","useRef","useMemo","useFloating","autoUpdate","offset","flip","shift","useClick","useDismiss","useRole","useListNavigation","useTypeahead","useInteractions","useMergeRefs","jsxs","cn","Button","jsx","Icon","UiChevronDown","IconButton","FloatingPortal","FloatingFocusManager","UiCheck","forwardRef"],"mappings":"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"}
1
+ {"version":3,"file":"icon-menu-picker.cjs","sources":["../../src/components/icon-menu-picker.tsx"],"sourcesContent":["import {\n forwardRef,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useMergeRefs,\n useRole,\n useTypeahead,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { useEscapeLayer, useFloatingZIndex } from \"../overlay/modalStack\";\nimport { UiCheck, UiChevronDown } from \"../icons\";\nimport { Button } from \"./button\";\nimport { IconButton } from \"./IconButton\";\n\nexport type IconMenuOption<T extends string> = {\n value: T;\n icon: StaticIconComponent;\n label: string;\n description?: string;\n};\n\nexport type IconMenuPickerProps<T extends string> = {\n value: T;\n onChange: (next: T) => void;\n options: IconMenuOption<T>[];\n ariaLabel: string;\n triggerTitle?: string | undefined;\n footer?: ReactNode | undefined;\n showLabel?: boolean | undefined;\n className?: string | undefined;\n triggerClassName?: string | undefined;\n menuClassName?: string | undefined;\n};\n\nfunction IconMenuPickerInner<T extends string>(\n {\n value,\n onChange,\n options,\n ariaLabel,\n triggerTitle,\n footer,\n showLabel,\n className,\n triggerClassName,\n menuClassName,\n }: IconMenuPickerProps<T>,\n forwardedRef: React.Ref<HTMLDivElement>,\n) {\n const [open, setOpen] = useState(false);\n const floatingZ = useFloatingZIndex();\n // Index of the item highlighted by keyboard navigation. Null until the user\n // arrows/typeaheads, so a pointer-opened menu starts with the selected row\n // focused (selectedIndex below) rather than the first.\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const listRef = useRef<(HTMLElement | null)[]>([]);\n const labelsRef = useRef<string[]>(options.map((o) => o.label));\n labelsRef.current = options.map((o) => o.label);\n\n const selectedIndex = useMemo(\n () => options.findIndex((option) => option.value === value),\n [options, value],\n );\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n placement: \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(6) reproduces the previous 0.375rem gap; flip opens the menu\n // upward when the trigger is near the bottom edge; shift slides it back\n // on-screen at the left/right edges. The 8px padding keeps a gutter.\n middleware: [offset(6), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n const dismiss = useDismiss(context, { escapeKey: false });\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n selectedIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n const typeahead = useTypeahead(context, {\n listRef: labelsRef,\n activeIndex,\n onMatch: setActiveIndex,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n typeahead,\n ]);\n useEscapeLayer(open, () => {\n setOpen(false);\n if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();\n });\n\n // Keep the trigger wrapper available both to floating-ui (as the positioning\n // reference) and to the consumer's forwarded ref.\n const referenceRef = useMergeRefs([refs.setReference, forwardedRef]);\n\n const select = (next: T) => {\n onChange(next);\n setOpen(false);\n };\n\n const selected = options[selectedIndex];\n if (!selected) {\n throw new Error(`IconMenuPicker: value \"${value}\" not found in options for ${ariaLabel}`);\n }\n\n const triggerTitleText = triggerTitle ?? `${ariaLabel}: ${selected.label}`;\n\n return (\n <div ref={referenceRef} className={cn(\"relative inline-flex\", showLabel && \"w-full\", className)}>\n {showLabel ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n aria-label={ariaLabel}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title={triggerTitleText}\n className={cn(\n \"w-full justify-start text-muted-foreground hover:text-foreground\",\n triggerClassName,\n )}\n {...getReferenceProps()}\n >\n <Icon icon={selected.icon} className=\"shrink-0 text-foreground\" />\n <span className=\"min-w-0 flex-1 truncate text-left capitalize\">\n {`${ariaLabel}: ${selected.label}`}\n </span>\n <Icon icon={UiChevronDown} className=\"shrink-0 text-muted-foreground\" />\n </Button>\n ) : (\n <IconButton\n icon={selected.icon}\n label={ariaLabel}\n title={triggerTitleText}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n className={triggerClassName}\n {...getReferenceProps()}\n />\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false} initialFocus={selectedIndex}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={ariaLabel}\n style={{ ...floatingStyles, zIndex: floatingZ }}\n className={cn(\n \"min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {options.map((option, index) => {\n const active = option.value === value;\n return (\n <button\n key={option.value}\n ref={(node) => {\n listRef.current[index] = node;\n }}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n tabIndex={index === activeIndex ? 0 : -1}\n className={cn(\n \"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm transition-colors\",\n \"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none\",\n active && \"text-foreground\",\n )}\n {...getItemProps({\n onClick: () => select(option.value),\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n select(option.value);\n }\n },\n })}\n >\n <Icon icon={option.icon} className=\"shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate capitalize\">{option.label}</span>\n {active ? (\n <Icon icon={UiCheck} className=\"shrink-0 text-foreground\" />\n ) : (\n <span className=\"inline-block size-4 shrink-0\" aria-hidden />\n )}\n </button>\n );\n })}\n {footer ? (\n <div className=\"mt-1 border-t border-border/60 px-2 py-1.5 text-[11px] text-muted-foreground\">\n {footer}\n </div>\n ) : null}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n\nexport const IconMenuPicker = forwardRef(IconMenuPickerInner) as <T extends string>(\n props: IconMenuPickerProps<T> & { ref?: React.Ref<HTMLDivElement> },\n) => JSX.Element;\n"],"names":["useState","useFloatingZIndex","useRef","useMemo","useFloating","autoUpdate","offset","flip","shift","useClick","useDismiss","useRole","useListNavigation","useTypeahead","useInteractions","useEscapeLayer","useMergeRefs","jsxs","cn","Button","jsx","Icon","UiChevronDown","IconButton","FloatingPortal","FloatingFocusManager","UiCheck","forwardRef"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"icon-menu-picker.d.ts","sourceRoot":"","sources":["../../src/components/icon-menu-picker.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAkBf,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAK9D,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,IAAI;IAC7C,KAAK,EAAE,CAAC,CAAC;IACT,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,MAAM,IAAI;IAClD,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAoLF,eAAO,MAAM,cAAc,EAAsC,CAAC,CAAC,SAAS,MAAM,EAChF,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;CAAE,KAChE,GAAG,CAAC,OAAO,CAAC"}
1
+ {"version":3,"file":"icon-menu-picker.d.ts","sourceRoot":"","sources":["../../src/components/icon-menu-picker.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAkBf,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAM9D,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,IAAI;IAC7C,KAAK,EAAE,CAAC,CAAC;IACT,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,MAAM,IAAI;IAClD,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,CAAC;AAyLF,eAAO,MAAM,cAAc,EAAsC,CAAC,CAAC,SAAS,MAAM,EAChF,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;CAAE,KAChE,GAAG,CAAC,OAAO,CAAC"}
@@ -3,6 +3,7 @@ import { forwardRef, useState, useRef, useMemo } from "react";
3
3
  import { useFloating, autoUpdate, offset, flip, shift, useClick, useDismiss, useRole, useListNavigation, useTypeahead, useInteractions, useMergeRefs, FloatingPortal, FloatingFocusManager } from "@floating-ui/react";
4
4
  import { cn } from "../lib/utils.js";
5
5
  import { Icon } from "../data/Icon.js";
6
+ import { useFloatingZIndex, useEscapeLayer } from "../overlay/modalStack.js";
6
7
  import { Button } from "./button.js";
7
8
  import { IconButton } from "./IconButton.js";
8
9
  import { UiChevronDown } from "../icons/components/UiChevronDown.js";
@@ -20,6 +21,7 @@ function IconMenuPickerInner({
20
21
  menuClassName
21
22
  }, forwardedRef) {
22
23
  const [open, setOpen] = useState(false);
24
+ const floatingZ = useFloatingZIndex();
23
25
  const [activeIndex, setActiveIndex] = useState(null);
24
26
  const listRef = useRef([]);
25
27
  const labelsRef = useRef(options.map((o) => o.label));
@@ -39,7 +41,7 @@ function IconMenuPickerInner({
39
41
  middleware: [offset(6), flip({ padding: 8 }), shift({ padding: 8 })]
40
42
  });
41
43
  const click = useClick(context);
42
- const dismiss = useDismiss(context);
44
+ const dismiss = useDismiss(context, { escapeKey: false });
43
45
  const role = useRole(context, { role: "menu" });
44
46
  const listNav = useListNavigation(context, {
45
47
  listRef,
@@ -60,6 +62,10 @@ function IconMenuPickerInner({
60
62
  listNav,
61
63
  typeahead
62
64
  ]);
65
+ useEscapeLayer(open, () => {
66
+ setOpen(false);
67
+ if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();
68
+ });
63
69
  const referenceRef = useMergeRefs([refs.setReference, forwardedRef]);
64
70
  const select = (next) => {
65
71
  onChange(next);
@@ -110,9 +116,9 @@ function IconMenuPickerInner({
110
116
  ref: refs.setFloating,
111
117
  role: "menu",
112
118
  "aria-label": ariaLabel,
113
- style: floatingStyles,
119
+ style: { ...floatingStyles, zIndex: floatingZ },
114
120
  className: cn(
115
- "z-[9999] min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5",
121
+ "min-w-[12rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5",
116
122
  menuClassName
117
123
  ),
118
124
  ...getFloatingProps(),