@flanksource/clicky-ui 0.3.30 → 0.3.31

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 (130) hide show
  1. package/dist/comments/CommentCard.cjs +110 -262
  2. package/dist/comments/CommentCard.cjs.map +1 -1
  3. package/dist/comments/CommentCard.d.ts +3 -1
  4. package/dist/comments/CommentCard.d.ts.map +1 -1
  5. package/dist/comments/CommentCard.js +105 -257
  6. package/dist/comments/CommentCard.js.map +1 -1
  7. package/dist/comments/CommentCardParts.cjs +95 -0
  8. package/dist/comments/CommentCardParts.cjs.map +1 -0
  9. package/dist/comments/CommentCardParts.d.ts +17 -0
  10. package/dist/comments/CommentCardParts.d.ts.map +1 -0
  11. package/dist/comments/CommentCardParts.js +95 -0
  12. package/dist/comments/CommentCardParts.js.map +1 -0
  13. package/dist/comments/CommentSidePanel.cjs +14 -7
  14. package/dist/comments/CommentSidePanel.cjs.map +1 -1
  15. package/dist/comments/CommentSidePanel.d.ts +3 -1
  16. package/dist/comments/CommentSidePanel.d.ts.map +1 -1
  17. package/dist/comments/CommentSidePanel.js +14 -7
  18. package/dist/comments/CommentSidePanel.js.map +1 -1
  19. package/dist/comments/CommentThread.cjs +2 -0
  20. package/dist/comments/CommentThread.cjs.map +1 -1
  21. package/dist/comments/CommentThread.d.ts +2 -1
  22. package/dist/comments/CommentThread.d.ts.map +1 -1
  23. package/dist/comments/CommentThread.js +2 -0
  24. package/dist/comments/CommentThread.js.map +1 -1
  25. package/dist/comments/CommentThreadList.cjs +50 -34
  26. package/dist/comments/CommentThreadList.cjs.map +1 -1
  27. package/dist/comments/CommentThreadList.d.ts +3 -1
  28. package/dist/comments/CommentThreadList.d.ts.map +1 -1
  29. package/dist/comments/CommentThreadList.js +50 -34
  30. package/dist/comments/CommentThreadList.js.map +1 -1
  31. package/dist/components/ComboboxMenu.cjs +4 -1
  32. package/dist/components/ComboboxMenu.cjs.map +1 -1
  33. package/dist/components/ComboboxMenu.d.ts.map +1 -1
  34. package/dist/components/ComboboxMenu.js +5 -2
  35. package/dist/components/ComboboxMenu.js.map +1 -1
  36. package/dist/components/ComboboxTags.cjs +5 -1
  37. package/dist/components/ComboboxTags.cjs.map +1 -1
  38. package/dist/components/ComboboxTags.d.ts.map +1 -1
  39. package/dist/components/ComboboxTags.js +6 -2
  40. package/dist/components/ComboboxTags.js.map +1 -1
  41. package/dist/components/ConnectionLoggingPolicy.cjs +24 -35
  42. package/dist/components/ConnectionLoggingPolicy.cjs.map +1 -1
  43. package/dist/components/ConnectionLoggingPolicy.d.ts.map +1 -1
  44. package/dist/components/ConnectionLoggingPolicy.js +24 -35
  45. package/dist/components/ConnectionLoggingPolicy.js.map +1 -1
  46. package/dist/components/MultiSelect.cjs.map +1 -1
  47. package/dist/components/MultiSelect.d.ts +7 -0
  48. package/dist/components/MultiSelect.d.ts.map +1 -1
  49. package/dist/components/MultiSelect.js.map +1 -1
  50. package/dist/components/filter-bar-multi.cjs +10 -1
  51. package/dist/components/filter-bar-multi.cjs.map +1 -1
  52. package/dist/components/filter-bar-multi.d.ts.map +1 -1
  53. package/dist/components/filter-bar-multi.js +11 -2
  54. package/dist/components/filter-bar-multi.js.map +1 -1
  55. package/dist/data/ai/SessionViewer.model.cjs +23 -8
  56. package/dist/data/ai/SessionViewer.model.cjs.map +1 -1
  57. package/dist/data/ai/SessionViewer.model.d.ts +8 -0
  58. package/dist/data/ai/SessionViewer.model.d.ts.map +1 -1
  59. package/dist/data/ai/SessionViewer.model.js +23 -8
  60. package/dist/data/ai/SessionViewer.model.js.map +1 -1
  61. package/dist/data/ai/SessionViewer.rows.cjs +9 -1
  62. package/dist/data/ai/SessionViewer.rows.cjs.map +1 -1
  63. package/dist/data/ai/SessionViewer.rows.js +9 -1
  64. package/dist/data/ai/SessionViewer.rows.js.map +1 -1
  65. package/dist/data/ai/SpecRuntimeEditor/VerifySection.cjs +24 -2
  66. package/dist/data/ai/SpecRuntimeEditor/VerifySection.cjs.map +1 -1
  67. package/dist/data/ai/SpecRuntimeEditor/VerifySection.d.ts.map +1 -1
  68. package/dist/data/ai/SpecRuntimeEditor/VerifySection.js +25 -3
  69. package/dist/data/ai/SpecRuntimeEditor/VerifySection.js.map +1 -1
  70. package/dist/data/ai/SpecRuntimeEditor/summaries.cjs.map +1 -1
  71. package/dist/data/ai/SpecRuntimeEditor/summaries.d.ts.map +1 -1
  72. package/dist/data/ai/SpecRuntimeEditor/summaries.js.map +1 -1
  73. package/dist/data/ai/SpecRuntimeEditor.model.cjs +4 -0
  74. package/dist/data/ai/SpecRuntimeEditor.model.cjs.map +1 -1
  75. package/dist/data/ai/SpecRuntimeEditor.model.d.ts +4 -0
  76. package/dist/data/ai/SpecRuntimeEditor.model.d.ts.map +1 -1
  77. package/dist/data/ai/SpecRuntimeEditor.model.js +4 -0
  78. package/dist/data/ai/SpecRuntimeEditor.model.js.map +1 -1
  79. package/dist/data/test-runner/TestRunner.cjs +6 -4
  80. package/dist/data/test-runner/TestRunner.cjs.map +1 -1
  81. package/dist/data/test-runner/TestRunner.d.ts +10 -1
  82. package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
  83. package/dist/data/test-runner/TestRunner.js +6 -4
  84. package/dist/data/test-runner/TestRunner.js.map +1 -1
  85. package/dist/data/verification/VerificationResults.cjs +99 -0
  86. package/dist/data/verification/VerificationResults.cjs.map +1 -0
  87. package/dist/data/verification/VerificationResults.d.ts +27 -0
  88. package/dist/data/verification/VerificationResults.d.ts.map +1 -0
  89. package/dist/data/verification/VerificationResults.js +99 -0
  90. package/dist/data/verification/VerificationResults.js.map +1 -0
  91. package/dist/data/verification/adapterViews.cjs +116 -0
  92. package/dist/data/verification/adapterViews.cjs.map +1 -0
  93. package/dist/data/verification/adapterViews.d.ts +13 -0
  94. package/dist/data/verification/adapterViews.d.ts.map +1 -0
  95. package/dist/data/verification/adapterViews.js +116 -0
  96. package/dist/data/verification/adapterViews.js.map +1 -0
  97. package/dist/data/verification/adapters.cjs +26 -0
  98. package/dist/data/verification/adapters.cjs.map +1 -0
  99. package/dist/data/verification/adapters.d.ts +6 -0
  100. package/dist/data/verification/adapters.d.ts.map +1 -0
  101. package/dist/data/verification/adapters.js +26 -0
  102. package/dist/data/verification/adapters.js.map +1 -0
  103. package/dist/data/verification/index.d.ts +5 -0
  104. package/dist/data/verification/index.d.ts.map +1 -0
  105. package/dist/data/verification/verify-report.cjs +58 -0
  106. package/dist/data/verification/verify-report.cjs.map +1 -0
  107. package/dist/data/verification/verify-report.d.ts +96 -0
  108. package/dist/data/verification/verify-report.d.ts.map +1 -0
  109. package/dist/data/verification/verify-report.js +58 -0
  110. package/dist/data/verification/verify-report.js.map +1 -0
  111. package/dist/data/verification/verifyReportTests.cjs +83 -0
  112. package/dist/data/verification/verifyReportTests.cjs.map +1 -0
  113. package/dist/data/verification/verifyReportTests.d.ts +13 -0
  114. package/dist/data/verification/verifyReportTests.d.ts.map +1 -0
  115. package/dist/data/verification/verifyReportTests.js +83 -0
  116. package/dist/data/verification/verifyReportTests.js.map +1 -0
  117. package/dist/data/version-info.cjs +3 -3
  118. package/dist/data/version-info.js +3 -3
  119. package/dist/data.cjs +10 -0
  120. package/dist/data.cjs.map +1 -1
  121. package/dist/data.d.ts +1 -0
  122. package/dist/data.d.ts.map +1 -1
  123. package/dist/data.js +10 -0
  124. package/dist/data.js.map +1 -1
  125. package/dist/index.cjs +10 -0
  126. package/dist/index.cjs.map +1 -1
  127. package/dist/index.js +10 -0
  128. package/dist/index.js.map +1 -1
  129. package/dist/styles.css +1 -1
  130. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"MultiSelect.cjs","sources":["../../src/components/MultiSelect.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode, 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 { Button } from \"./button\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiChevronDown, UiChevronUp } from \"../icons\";\nimport { useEscapeLayer, useFloatingZIndex } from \"../overlay/modalStack\";\n\nexport type MultiSelectOption = {\n /** Stable option value written into the selected value array. */\n value: string;\n /** Visible option label in the menu and trigger summary. */\n label: ReactNode;\n /** Prevents selecting this option while still showing it in the menu. */\n disabled?: boolean;\n /** Optional browser tooltip for truncated or explanatory labels. */\n title?: string;\n};\n\nexport type MultiSelectProps = {\n /** Available options, rendered in the order provided. */\n options: MultiSelectOption[];\n /** Controlled selected option values. */\n value: string[];\n /** Called with the complete next value array after each toggle. */\n onChange: (next: string[]) => void;\n /** Trigger text when nothing is selected. Also labels the menu. */\n placeholder?: string;\n /** Accessible name for the trigger. Use when the field is labelled\n * elsewhere so the trigger doesn't have to borrow `placeholder` for its\n * name (which would force a synthesized placeholder). */\n ariaLabel?: string;\n /** Disables opening and changing selections. */\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 popover menu. */\n menuClassName?: string;\n};\n\nexport function MultiSelect({\n options,\n value,\n onChange,\n placeholder = \"Select options\",\n ariaLabel,\n disabled,\n className,\n triggerClassName,\n menuClassName,\n}: MultiSelectProps) {\n const [open, setOpen] = useState(false);\n\n // Portaled (floating-ui) so the menu escapes `overflow` clipping from\n // scroll-container ancestors, matching Combobox/DropdownMenu.\n const floatingZ = useFloatingZIndex();\n const { refs, floatingStyles, context } = useFloating({\n open,\n onOpenChange: setOpen,\n placement: \"bottom-start\",\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: \"menu\" }),\n ]);\n useEscapeLayer(open, () => {\n setOpen(false);\n if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();\n });\n\n const selected = useMemo(() => {\n const selectedOptions = options.filter((option) => value.includes(option.value));\n const labels = selectedOptions\n .map((option) => option.label)\n .filter((label): label is string => typeof label === \"string\");\n\n if (selectedOptions.length === 0) return placeholder;\n if (labels.length === 0) return `${selectedOptions.length} selected`;\n if (labels.length <= 2) return labels.join(\", \");\n return `${labels.length} selected`;\n }, [options, placeholder, value]);\n\n function toggleOption(nextValue: string) {\n if (value.includes(nextValue)) {\n onChange(value.filter((current) => current !== nextValue));\n return;\n }\n onChange([...value, nextValue]);\n }\n\n // `min-w-0` here, and a shrinkable trigger below, let the summary ellipsize\n // inside a width-capped flex parent (a FilterBar chip) rather than overflow\n // onto its neighbour.\n return (\n <div className={cn(\"inline-flex min-w-0\", className)}>\n <Button\n ref={refs.setReference as Ref<HTMLButtonElement>}\n type=\"button\"\n variant=\"outline\"\n disabled={disabled}\n aria-label={ariaLabel ?? `${placeholder} filter`}\n className={cn(\n \"w-fit max-w-[15rem] min-w-0 justify-between gap-3 text-left font-normal\",\n triggerClassName,\n // Empty means the trigger is showing its placeholder, so it reads at\n // placeholder weight rather than at helper-text weight.\n value.length === 0 && \"text-placeholder\",\n )}\n {...getReferenceProps()}\n >\n <span className=\"truncate\">{selected}</span>\n <Icon icon={open ? UiChevronUp : UiChevronDown} className=\"shrink-0 text-muted-foreground\" />\n </Button>\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={placeholder}\n style={{ ...floatingStyles, zIndex: floatingZ }}\n className={cn(\n \"min-w-[14rem] max-w-[20rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n <div className=\"mb-1 flex items-center justify-between gap-2 px-2 py-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n <span>{placeholder}</span>\n <button\n type=\"button\"\n className=\"text-[10px] text-primary disabled:text-muted-foreground\"\n onClick={() => onChange([])}\n disabled={value.length === 0}\n >\n Clear all\n </button>\n </div>\n <div className=\"max-h-64 overflow-auto\">\n {options.map((option) => {\n const checked = value.includes(option.value);\n return (\n <label\n key={option.value}\n className={cn(\n \"flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent\",\n option.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <input\n type=\"checkbox\"\n role=\"menuitemcheckbox\"\n className=\"size-4 rounded border border-input\"\n checked={checked}\n disabled={option.disabled}\n onChange={() => toggleOption(option.value)}\n />\n <span className=\"min-w-0 truncate\">{option.label}</span>\n </label>\n );\n })}\n </div>\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":["useState","useFloatingZIndex","useFloating","autoUpdate","offset","flip","shift","useInteractions","useClick","useDismiss","useRole","useEscapeLayer","useMemo","cn","jsxs","Button","jsx","Icon","UiChevronUp","UiChevronDown","FloatingPortal","FloatingFocusManager"],"mappings":"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"}
1
+ {"version":3,"file":"MultiSelect.cjs","sources":["../../src/components/MultiSelect.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode, 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 { Button } from \"./button\";\nimport { cn } from \"../lib/utils\";\nimport { Icon, type LabelIconSpec } from \"../data/Icon\";\nimport { UiChevronDown, UiChevronUp } from \"../icons\";\nimport { useEscapeLayer, useFloatingZIndex } from \"../overlay/modalStack\";\n\nexport type MultiSelectOption = {\n /** Stable option value written into the selected value array. */\n value: string;\n /** Visible option label in the menu and trigger summary. */\n label: ReactNode;\n /** Secondary text rendered below the option label. */\n description?: string;\n /** Optional compact label used after the option is selected. */\n selectedLabel?: string;\n /** Leading glyph rendered with the option label. */\n icon?: LabelIconSpec;\n /** Prevents selecting this option while still showing it in the menu. */\n disabled?: boolean;\n /** Optional browser tooltip for truncated or explanatory labels. */\n title?: string;\n};\n\nexport type MultiSelectProps = {\n /** Available options, rendered in the order provided. */\n options: MultiSelectOption[];\n /** Controlled selected option values. */\n value: string[];\n /** Called with the complete next value array after each toggle. */\n onChange: (next: string[]) => void;\n /** Trigger text when nothing is selected. Also labels the menu. */\n placeholder?: string;\n /** Accessible name for the trigger. Use when the field is labelled\n * elsewhere so the trigger doesn't have to borrow `placeholder` for its\n * name (which would force a synthesized placeholder). */\n ariaLabel?: string;\n /** Disables opening and changing selections. */\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 popover menu. */\n menuClassName?: string;\n};\n\nexport function MultiSelect({\n options,\n value,\n onChange,\n placeholder = \"Select options\",\n ariaLabel,\n disabled,\n className,\n triggerClassName,\n menuClassName,\n}: MultiSelectProps) {\n const [open, setOpen] = useState(false);\n\n // Portaled (floating-ui) so the menu escapes `overflow` clipping from\n // scroll-container ancestors, matching Combobox/DropdownMenu.\n const floatingZ = useFloatingZIndex();\n const { refs, floatingStyles, context } = useFloating({\n open,\n onOpenChange: setOpen,\n placement: \"bottom-start\",\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: \"menu\" }),\n ]);\n useEscapeLayer(open, () => {\n setOpen(false);\n if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();\n });\n\n const selected = useMemo(() => {\n const selectedOptions = options.filter((option) => value.includes(option.value));\n const labels = selectedOptions\n .map((option) => option.label)\n .filter((label): label is string => typeof label === \"string\");\n\n if (selectedOptions.length === 0) return placeholder;\n if (labels.length === 0) return `${selectedOptions.length} selected`;\n if (labels.length <= 2) return labels.join(\", \");\n return `${labels.length} selected`;\n }, [options, placeholder, value]);\n\n function toggleOption(nextValue: string) {\n if (value.includes(nextValue)) {\n onChange(value.filter((current) => current !== nextValue));\n return;\n }\n onChange([...value, nextValue]);\n }\n\n // `min-w-0` here, and a shrinkable trigger below, let the summary ellipsize\n // inside a width-capped flex parent (a FilterBar chip) rather than overflow\n // onto its neighbour.\n return (\n <div className={cn(\"inline-flex min-w-0\", className)}>\n <Button\n ref={refs.setReference as Ref<HTMLButtonElement>}\n type=\"button\"\n variant=\"outline\"\n disabled={disabled}\n aria-label={ariaLabel ?? `${placeholder} filter`}\n className={cn(\n \"w-fit max-w-[15rem] min-w-0 justify-between gap-3 text-left font-normal\",\n triggerClassName,\n // Empty means the trigger is showing its placeholder, so it reads at\n // placeholder weight rather than at helper-text weight.\n value.length === 0 && \"text-placeholder\",\n )}\n {...getReferenceProps()}\n >\n <span className=\"truncate\">{selected}</span>\n <Icon icon={open ? UiChevronUp : UiChevronDown} className=\"shrink-0 text-muted-foreground\" />\n </Button>\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={placeholder}\n style={{ ...floatingStyles, zIndex: floatingZ }}\n className={cn(\n \"min-w-[14rem] max-w-[20rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n <div className=\"mb-1 flex items-center justify-between gap-2 px-2 py-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n <span>{placeholder}</span>\n <button\n type=\"button\"\n className=\"text-[10px] text-primary disabled:text-muted-foreground\"\n onClick={() => onChange([])}\n disabled={value.length === 0}\n >\n Clear all\n </button>\n </div>\n <div className=\"max-h-64 overflow-auto\">\n {options.map((option) => {\n const checked = value.includes(option.value);\n return (\n <label\n key={option.value}\n className={cn(\n \"flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent\",\n option.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <input\n type=\"checkbox\"\n role=\"menuitemcheckbox\"\n className=\"size-4 rounded border border-input\"\n checked={checked}\n disabled={option.disabled}\n onChange={() => toggleOption(option.value)}\n />\n <span className=\"min-w-0 truncate\">{option.label}</span>\n </label>\n );\n })}\n </div>\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":["useState","useFloatingZIndex","useFloating","autoUpdate","offset","flip","shift","useInteractions","useClick","useDismiss","useRole","useEscapeLayer","useMemo","cn","jsxs","Button","jsx","Icon","UiChevronUp","UiChevronDown","FloatingPortal","FloatingFocusManager"],"mappings":"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"}
@@ -1,9 +1,16 @@
1
1
  import { ReactNode } from '../../../../node_modules/@types/react/index.d.ts';
2
+ import { LabelIconSpec } from '../data/Icon';
2
3
  export type MultiSelectOption = {
3
4
  /** Stable option value written into the selected value array. */
4
5
  value: string;
5
6
  /** Visible option label in the menu and trigger summary. */
6
7
  label: ReactNode;
8
+ /** Secondary text rendered below the option label. */
9
+ description?: string;
10
+ /** Optional compact label used after the option is selected. */
11
+ selectedLabel?: string;
12
+ /** Leading glyph rendered with the option label. */
13
+ icon?: LabelIconSpec;
7
14
  /** Prevents selecting this option while still showing it in the menu. */
8
15
  disabled?: boolean;
9
16
  /** Optional browser tooltip for truncated or explanatory labels. */
@@ -1 +1 @@
1
- {"version":3,"file":"MultiSelect.d.ts","sourceRoot":"","sources":["../../src/components/MultiSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAY,MAAM,OAAO,CAAC;AAoBpE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,KAAK,EAAE,SAAS,CAAC;IACjB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,yDAAyD;IACzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,yCAAyC;IACzC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,mEAAmE;IACnE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACnC,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;8DAE0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAA8B,EAC9B,SAAS,EACT,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,aAAa,GACd,EAAE,gBAAgB,+BAyHlB"}
1
+ {"version":3,"file":"MultiSelect.d.ts","sourceRoot":"","sources":["../../src/components/MultiSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAY,MAAM,OAAO,CAAC;AAgBpE,OAAO,EAAQ,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAIxD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,KAAK,EAAE,SAAS,CAAC;IACjB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,yDAAyD;IACzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,yCAAyC;IACzC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,mEAAmE;IACnE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACnC,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;8DAE0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,WAA8B,EAC9B,SAAS,EACT,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,aAAa,GACd,EAAE,gBAAgB,+BAyHlB"}
@@ -1 +1 @@
1
- {"version":3,"file":"MultiSelect.js","sources":["../../src/components/MultiSelect.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode, 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 { Button } from \"./button\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiChevronDown, UiChevronUp } from \"../icons\";\nimport { useEscapeLayer, useFloatingZIndex } from \"../overlay/modalStack\";\n\nexport type MultiSelectOption = {\n /** Stable option value written into the selected value array. */\n value: string;\n /** Visible option label in the menu and trigger summary. */\n label: ReactNode;\n /** Prevents selecting this option while still showing it in the menu. */\n disabled?: boolean;\n /** Optional browser tooltip for truncated or explanatory labels. */\n title?: string;\n};\n\nexport type MultiSelectProps = {\n /** Available options, rendered in the order provided. */\n options: MultiSelectOption[];\n /** Controlled selected option values. */\n value: string[];\n /** Called with the complete next value array after each toggle. */\n onChange: (next: string[]) => void;\n /** Trigger text when nothing is selected. Also labels the menu. */\n placeholder?: string;\n /** Accessible name for the trigger. Use when the field is labelled\n * elsewhere so the trigger doesn't have to borrow `placeholder` for its\n * name (which would force a synthesized placeholder). */\n ariaLabel?: string;\n /** Disables opening and changing selections. */\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 popover menu. */\n menuClassName?: string;\n};\n\nexport function MultiSelect({\n options,\n value,\n onChange,\n placeholder = \"Select options\",\n ariaLabel,\n disabled,\n className,\n triggerClassName,\n menuClassName,\n}: MultiSelectProps) {\n const [open, setOpen] = useState(false);\n\n // Portaled (floating-ui) so the menu escapes `overflow` clipping from\n // scroll-container ancestors, matching Combobox/DropdownMenu.\n const floatingZ = useFloatingZIndex();\n const { refs, floatingStyles, context } = useFloating({\n open,\n onOpenChange: setOpen,\n placement: \"bottom-start\",\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: \"menu\" }),\n ]);\n useEscapeLayer(open, () => {\n setOpen(false);\n if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();\n });\n\n const selected = useMemo(() => {\n const selectedOptions = options.filter((option) => value.includes(option.value));\n const labels = selectedOptions\n .map((option) => option.label)\n .filter((label): label is string => typeof label === \"string\");\n\n if (selectedOptions.length === 0) return placeholder;\n if (labels.length === 0) return `${selectedOptions.length} selected`;\n if (labels.length <= 2) return labels.join(\", \");\n return `${labels.length} selected`;\n }, [options, placeholder, value]);\n\n function toggleOption(nextValue: string) {\n if (value.includes(nextValue)) {\n onChange(value.filter((current) => current !== nextValue));\n return;\n }\n onChange([...value, nextValue]);\n }\n\n // `min-w-0` here, and a shrinkable trigger below, let the summary ellipsize\n // inside a width-capped flex parent (a FilterBar chip) rather than overflow\n // onto its neighbour.\n return (\n <div className={cn(\"inline-flex min-w-0\", className)}>\n <Button\n ref={refs.setReference as Ref<HTMLButtonElement>}\n type=\"button\"\n variant=\"outline\"\n disabled={disabled}\n aria-label={ariaLabel ?? `${placeholder} filter`}\n className={cn(\n \"w-fit max-w-[15rem] min-w-0 justify-between gap-3 text-left font-normal\",\n triggerClassName,\n // Empty means the trigger is showing its placeholder, so it reads at\n // placeholder weight rather than at helper-text weight.\n value.length === 0 && \"text-placeholder\",\n )}\n {...getReferenceProps()}\n >\n <span className=\"truncate\">{selected}</span>\n <Icon icon={open ? UiChevronUp : UiChevronDown} className=\"shrink-0 text-muted-foreground\" />\n </Button>\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={placeholder}\n style={{ ...floatingStyles, zIndex: floatingZ }}\n className={cn(\n \"min-w-[14rem] max-w-[20rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n <div className=\"mb-1 flex items-center justify-between gap-2 px-2 py-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n <span>{placeholder}</span>\n <button\n type=\"button\"\n className=\"text-[10px] text-primary disabled:text-muted-foreground\"\n onClick={() => onChange([])}\n disabled={value.length === 0}\n >\n Clear all\n </button>\n </div>\n <div className=\"max-h-64 overflow-auto\">\n {options.map((option) => {\n const checked = value.includes(option.value);\n return (\n <label\n key={option.value}\n className={cn(\n \"flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent\",\n option.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <input\n type=\"checkbox\"\n role=\"menuitemcheckbox\"\n className=\"size-4 rounded border border-input\"\n checked={checked}\n disabled={option.disabled}\n onChange={() => toggleOption(option.value)}\n />\n <span className=\"min-w-0 truncate\">{option.label}</span>\n </label>\n );\n })}\n </div>\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MultiSelect.js","sources":["../../src/components/MultiSelect.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode, 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 { Button } from \"./button\";\nimport { cn } from \"../lib/utils\";\nimport { Icon, type LabelIconSpec } from \"../data/Icon\";\nimport { UiChevronDown, UiChevronUp } from \"../icons\";\nimport { useEscapeLayer, useFloatingZIndex } from \"../overlay/modalStack\";\n\nexport type MultiSelectOption = {\n /** Stable option value written into the selected value array. */\n value: string;\n /** Visible option label in the menu and trigger summary. */\n label: ReactNode;\n /** Secondary text rendered below the option label. */\n description?: string;\n /** Optional compact label used after the option is selected. */\n selectedLabel?: string;\n /** Leading glyph rendered with the option label. */\n icon?: LabelIconSpec;\n /** Prevents selecting this option while still showing it in the menu. */\n disabled?: boolean;\n /** Optional browser tooltip for truncated or explanatory labels. */\n title?: string;\n};\n\nexport type MultiSelectProps = {\n /** Available options, rendered in the order provided. */\n options: MultiSelectOption[];\n /** Controlled selected option values. */\n value: string[];\n /** Called with the complete next value array after each toggle. */\n onChange: (next: string[]) => void;\n /** Trigger text when nothing is selected. Also labels the menu. */\n placeholder?: string;\n /** Accessible name for the trigger. Use when the field is labelled\n * elsewhere so the trigger doesn't have to borrow `placeholder` for its\n * name (which would force a synthesized placeholder). */\n ariaLabel?: string;\n /** Disables opening and changing selections. */\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 popover menu. */\n menuClassName?: string;\n};\n\nexport function MultiSelect({\n options,\n value,\n onChange,\n placeholder = \"Select options\",\n ariaLabel,\n disabled,\n className,\n triggerClassName,\n menuClassName,\n}: MultiSelectProps) {\n const [open, setOpen] = useState(false);\n\n // Portaled (floating-ui) so the menu escapes `overflow` clipping from\n // scroll-container ancestors, matching Combobox/DropdownMenu.\n const floatingZ = useFloatingZIndex();\n const { refs, floatingStyles, context } = useFloating({\n open,\n onOpenChange: setOpen,\n placement: \"bottom-start\",\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: \"menu\" }),\n ]);\n useEscapeLayer(open, () => {\n setOpen(false);\n if (refs.domReference.current instanceof HTMLElement) refs.domReference.current.focus();\n });\n\n const selected = useMemo(() => {\n const selectedOptions = options.filter((option) => value.includes(option.value));\n const labels = selectedOptions\n .map((option) => option.label)\n .filter((label): label is string => typeof label === \"string\");\n\n if (selectedOptions.length === 0) return placeholder;\n if (labels.length === 0) return `${selectedOptions.length} selected`;\n if (labels.length <= 2) return labels.join(\", \");\n return `${labels.length} selected`;\n }, [options, placeholder, value]);\n\n function toggleOption(nextValue: string) {\n if (value.includes(nextValue)) {\n onChange(value.filter((current) => current !== nextValue));\n return;\n }\n onChange([...value, nextValue]);\n }\n\n // `min-w-0` here, and a shrinkable trigger below, let the summary ellipsize\n // inside a width-capped flex parent (a FilterBar chip) rather than overflow\n // onto its neighbour.\n return (\n <div className={cn(\"inline-flex min-w-0\", className)}>\n <Button\n ref={refs.setReference as Ref<HTMLButtonElement>}\n type=\"button\"\n variant=\"outline\"\n disabled={disabled}\n aria-label={ariaLabel ?? `${placeholder} filter`}\n className={cn(\n \"w-fit max-w-[15rem] min-w-0 justify-between gap-3 text-left font-normal\",\n triggerClassName,\n // Empty means the trigger is showing its placeholder, so it reads at\n // placeholder weight rather than at helper-text weight.\n value.length === 0 && \"text-placeholder\",\n )}\n {...getReferenceProps()}\n >\n <span className=\"truncate\">{selected}</span>\n <Icon icon={open ? UiChevronUp : UiChevronDown} className=\"shrink-0 text-muted-foreground\" />\n </Button>\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={placeholder}\n style={{ ...floatingStyles, zIndex: floatingZ }}\n className={cn(\n \"min-w-[14rem] max-w-[20rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n <div className=\"mb-1 flex items-center justify-between gap-2 px-2 py-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n <span>{placeholder}</span>\n <button\n type=\"button\"\n className=\"text-[10px] text-primary disabled:text-muted-foreground\"\n onClick={() => onChange([])}\n disabled={value.length === 0}\n >\n Clear all\n </button>\n </div>\n <div className=\"max-h-64 overflow-auto\">\n {options.map((option) => {\n const checked = value.includes(option.value);\n return (\n <label\n key={option.value}\n className={cn(\n \"flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent\",\n option.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <input\n type=\"checkbox\"\n role=\"menuitemcheckbox\"\n className=\"size-4 rounded border border-input\"\n checked={checked}\n disabled={option.disabled}\n onChange={() => toggleOption(option.value)}\n />\n <span className=\"min-w-0 truncate\">{option.label}</span>\n </label>\n );\n })}\n </div>\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AA4DO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AAItC,QAAM,YAAY,kBAAA;AAClB,QAAM,EAAE,MAAM,gBAAgB,QAAA,IAAY,YAAY;AAAA,IACpD;AAAA,IACA,cAAc;AAAA,IACd,WAAW;AAAA,IACX,sBAAsB;AAAA,IACtB,YAAY,CAAC,OAAO,CAAC,GAAG,KAAK,EAAE,SAAS,EAAA,CAAG,GAAG,MAAM,EAAE,SAAS,EAAA,CAAG,CAAC;AAAA,EAAA,CACpE;AACD,QAAM,EAAE,mBAAmB,iBAAA,IAAqB,gBAAgB;AAAA,IAC9D,SAAS,OAAO;AAAA,IAChB,WAAW,SAAS,EAAE,WAAW,OAAO;AAAA,IACxC,QAAQ,SAAS,EAAE,MAAM,QAAQ;AAAA,EAAA,CAClC;AACD,iBAAe,MAAM,MAAM;AACzB,YAAQ,KAAK;AACb,QAAI,KAAK,aAAa,mBAAmB,YAAa,MAAK,aAAa,QAAQ,MAAA;AAAA,EAClF,CAAC;AAED,QAAM,WAAW,QAAQ,MAAM;AAC7B,UAAM,kBAAkB,QAAQ,OAAO,CAAC,WAAW,MAAM,SAAS,OAAO,KAAK,CAAC;AAC/E,UAAM,SAAS,gBACZ,IAAI,CAAC,WAAW,OAAO,KAAK,EAC5B,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ;AAE/D,QAAI,gBAAgB,WAAW,EAAG,QAAO;AACzC,QAAI,OAAO,WAAW,EAAG,QAAO,GAAG,gBAAgB,MAAM;AACzD,QAAI,OAAO,UAAU,EAAG,QAAO,OAAO,KAAK,IAAI;AAC/C,WAAO,GAAG,OAAO,MAAM;AAAA,EACzB,GAAG,CAAC,SAAS,aAAa,KAAK,CAAC;AAEhC,WAAS,aAAa,WAAmB;AACvC,QAAI,MAAM,SAAS,SAAS,GAAG;AAC7B,eAAS,MAAM,OAAO,CAAC,YAAY,YAAY,SAAS,CAAC;AACzD;AAAA,IACF;AACA,aAAS,CAAC,GAAG,OAAO,SAAS,CAAC;AAAA,EAChC;AAKA,8BACG,OAAA,EAAI,WAAW,GAAG,uBAAuB,SAAS,GACjD,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK,KAAK;AAAA,QACV,MAAK;AAAA,QACL,SAAQ;AAAA,QACR;AAAA,QACA,cAAY,aAAa,GAAG,WAAW;AAAA,QACvC,WAAW;AAAA,UACT;AAAA,UACA;AAAA;AAAA;AAAA,UAGA,MAAM,WAAW,KAAK;AAAA,QAAA;AAAA,QAEvB,GAAG,kBAAA;AAAA,QAEJ,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAK,WAAU,YAAY,UAAA,UAAS;AAAA,8BACpC,MAAA,EAAK,MAAM,OAAO,cAAc,eAAe,WAAU,iCAAA,CAAiC;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAE5F,QACC,oBAAC,gBAAA,EACC,8BAAC,sBAAA,EAAqB,SAAkB,OAAO,OAC7C,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK,KAAK;AAAA,QACV,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,OAAO,EAAE,GAAG,gBAAgB,QAAQ,UAAA;AAAA,QACpC,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG,iBAAA;AAAA,QAER,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,gIACb,UAAA;AAAA,YAAA,oBAAC,UAAM,UAAA,YAAA,CAAY;AAAA,YACnB;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,SAAS,MAAM,SAAS,EAAE;AAAA,gBAC1B,UAAU,MAAM,WAAW;AAAA,gBAC5B,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UAED,GACF;AAAA,8BACC,OAAA,EAAI,WAAU,0BACZ,UAAA,QAAQ,IAAI,CAAC,WAAW;AACvB,kBAAM,UAAU,MAAM,SAAS,OAAO,KAAK;AAC3C,mBACE;AAAA,cAAC;AAAA,cAAA;AAAA,gBAEC,WAAW;AAAA,kBACT;AAAA,kBACA,OAAO,YAAY;AAAA,gBAAA;AAAA,gBAGrB,UAAA;AAAA,kBAAA;AAAA,oBAAC;AAAA,oBAAA;AAAA,sBACC,MAAK;AAAA,sBACL,MAAK;AAAA,sBACL,WAAU;AAAA,sBACV;AAAA,sBACA,UAAU,OAAO;AAAA,sBACjB,UAAU,MAAM,aAAa,OAAO,KAAK;AAAA,oBAAA;AAAA,kBAAA;AAAA,kBAE3C,oBAAC,QAAA,EAAK,WAAU,oBAAoB,iBAAO,MAAA,CAAM;AAAA,gBAAA;AAAA,cAAA;AAAA,cAd5C,OAAO;AAAA,YAAA;AAAA,UAiBlB,CAAC,EAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,GAEA,EAAA,CACF;AAAA,EAAA,GAEJ;AAEJ;"}
@@ -89,6 +89,9 @@ function toComboboxOptions(options) {
89
89
  // the human-readable label — instead of the raw value.
90
90
  label: typeof option.label === "string" ? option.label : title,
91
91
  title,
92
+ ...option.description !== void 0 ? { description: option.description } : {},
93
+ ...option.selectedLabel !== void 0 ? { selectedLabel: option.selectedLabel } : {},
94
+ ...option.icon !== void 0 ? { icon: option.icon } : {},
92
95
  ...option.disabled !== void 0 ? { disabled: option.disabled } : {}
93
96
  };
94
97
  });
@@ -170,7 +173,13 @@ function MultiFilterPanel({
170
173
  FilterPill.FilterPill,
171
174
  {
172
175
  className: "w-full justify-between",
173
- label: option.label,
176
+ label: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-0 items-start gap-1.5", children: [
177
+ /* @__PURE__ */ jsxRuntime.jsx(Icon.LabelIcon, { icon: option.icon, className: "mt-0.5 shrink-0 text-sm text-muted-foreground" }),
178
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 text-left", children: [
179
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate", children: option.label }),
180
+ option.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5 block truncate text-xs font-normal text-muted-foreground", children: option.description })
181
+ ] })
182
+ ] }),
174
183
  mode,
175
184
  title,
176
185
  togglePosition: "right",
@@ -1 +1 @@
1
- {"version":3,"file":"filter-bar-multi.cjs","sources":["../../src/components/filter-bar-multi.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from \"react\";\nimport { FilterPill } from \"../data/FilterPill\";\nimport { Icon, type LabelIconSpec } from \"../data/Icon\";\nimport { UiSearch } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport { Combobox, type ComboboxOption } from \"./Combobox\";\nimport { FilterBarContext } from \"./filter-bar-context\";\nimport {\n comboboxLabelProps,\n lookupFieldWidthClass,\n mergeMultiSelectOptions,\n multiSelectOptionText,\n nextFilterMode,\n updateMultiFilterValue,\n useDebouncedMultiDraft,\n type FilterBarMultiFilterMode,\n} from \"./filter-bar-field-utils\";\nimport type { MultiSelectOption } from \"./MultiSelect\";\n\nexport type { FilterBarMultiFilterMode } from \"./filter-bar-field-utils\";\n\nexport type FilterBarMultiFilter = {\n key: string;\n /** Renders include/exclude chips for each option. */\n kind: \"multi\";\n label: string;\n /** Leading glyph shown before the label: a runtime icon name or a node. */\n icon?: LabelIconSpec;\n description?: string;\n /** Map of option value to include/exclude state. */\n value: Record<string, FilterBarMultiFilterMode>;\n /** Available chip options. When `truncated`, this is only the head set. */\n options: MultiSelectOption[];\n onChange: (value: Record<string, FilterBarMultiFilterMode>) => void;\n /** Ghost text shown in the field when no options are selected. */\n placeholder?: string;\n /**\n * True when more options exist than are in `options` (the option set was\n * capped server-side). Renders an \"… and N more\" hint and, with `onSearch`,\n * lets the user search the full set.\n */\n truncated?: boolean;\n /** True distinct count behind a truncated option set; drives the \"N more\" label. */\n total?: number;\n /**\n * Optional async fetch invoked (debounced) as the user types in the option\n * search box. Returns the options matching the query, which are merged into\n * the displayed list so values beyond the head become selectable. When\n * absent, the search box filters the static `options` client-side only.\n */\n onSearch?: (query: string) => Promise<MultiSelectOption[]> | void;\n /**\n * Lets the user commit a value absent from `options` (a free-typed name or a\n * `*` wildcard). The option search box stays visible and an \"Add\" row appears\n * when the query matches no listed option; committed customs pin to the top so\n * they stay toggleable. Use for MatchItem-style filters (table/login patterns).\n */\n allowCustomValue?: boolean;\n disabled?: boolean;\n className?: string;\n};\n\n// MultiFilterField renders a \"multi\" filter as a tristate Combobox so the bar\n// stays one control family with the lookup fields: same shell, inline label,\n// typeahead input, and portaled listbox of FilterPill rows.\nexport function MultiFilterField({\n filter,\n grow,\n}: {\n filter: FilterBarMultiFilter;\n grow: boolean;\n}) {\n const [draft, setDraft] = useDebouncedMultiDraft(filter.value, filter.onChange);\n // Server-search state: the matches for the active query (they replace the\n // head while searching), plus the in-flight flag. Combobox owns the 250ms\n // debounce; this adapter only resolves the Promise, latest query wins.\n const [searchOptions, setSearchOptions] = useState<MultiSelectOption[]>([]);\n const [loading, setLoading] = useState(false);\n const [activeQuery, setActiveQuery] = useState(\"\");\n const latestQuery = useRef(\"\");\n\n const onSearchProp = filter.onSearch;\n const handleSearch = useCallback(\n (query: string) => {\n latestQuery.current = query;\n setActiveQuery(query);\n if (!query) {\n setSearchOptions([]);\n setLoading(false);\n return;\n }\n setLoading(true);\n Promise.resolve(onSearchProp?.(query))\n .then((opts) => {\n if (latestQuery.current !== query) return;\n setLoading(false);\n setSearchOptions(Array.isArray(opts) ? opts : []);\n })\n .catch(() => {\n if (latestQuery.current !== query) return;\n setLoading(false);\n setSearchOptions([]);\n });\n },\n [onSearchProp],\n );\n\n // allowCustomValue: any toggled value absent from `options` (a free-typed name\n // or a `*` wildcard) is synthesised as a pinned option so it renders and stays\n // toggleable across reopens.\n const allowCustomValue = Boolean(filter.allowCustomValue);\n const baseOptions = useMemo(() => {\n if (!allowCustomValue) return filter.options;\n const known = new Set(filter.options.map((o) => o.value));\n const pinned = Object.keys(draft)\n .filter((v) => !known.has(v))\n .map((v) => ({ value: v, label: v }));\n return mergeMultiSelectOptions(pinned, filter.options);\n }, [allowCustomValue, filter.options, draft]);\n\n // Server search active → the matches REPLACE the head, but any head option\n // the user has already toggled (include/exclude) stays pinned so their\n // selection remains visible and changeable. Selected first, then matches.\n // Without a query (or without onSearch) the head set renders and Combobox\n // client-filters it.\n const visibleOptions = useMemo(() => {\n if (!onSearchProp || !activeQuery) return baseOptions;\n const selectedHead = baseOptions.filter((o) => draft[o.value] !== undefined);\n return mergeMultiSelectOptions(selectedHead, searchOptions);\n }, [onSearchProp, activeQuery, baseOptions, searchOptions, draft]);\n\n const moreCount =\n filter.truncated && filter.total ? Math.max(filter.total - filter.options.length, 0) : 0;\n const footer =\n moreCount > 0 && !activeQuery ? (\n <>\n … and {moreCount.toLocaleString()} more\n {onSearchProp ? \" — type to search all\" : \"\"}\n </>\n ) : undefined;\n\n return (\n <Combobox\n multiple\n tristate\n {...comboboxLabelProps(filter)}\n options={toComboboxOptions(visibleOptions)}\n value={draft}\n onChange={setDraft}\n allowCustomValue={allowCustomValue}\n size=\"sm\"\n className={cn(lookupFieldWidthClass(grow), filter.className)}\n {...(filter.placeholder !== undefined ? { placeholder: filter.placeholder } : {})}\n {...(onSearchProp !== undefined ? { onSearch: handleSearch, loading } : {})}\n {...(filter.disabled !== undefined ? { disabled: filter.disabled } : {})}\n {...(footer !== undefined ? { footer } : {})}\n />\n );\n}\n\nfunction toComboboxOptions(options: MultiSelectOption[]): ComboboxOption[] {\n return options.map((option) => {\n const title = option.title ?? multiSelectOptionText(option);\n return {\n value: option.value,\n // A node label (e.g. an icon + text span) can't render as the combobox's\n // option row or closed summary, so fall back to the option's title text —\n // the human-readable label — instead of the raw value.\n label: typeof option.label === \"string\" ? option.label : title,\n title,\n ...(option.disabled !== undefined ? { disabled: option.disabled } : {}),\n };\n });\n}\n\nexport type TriStateMultiSelectProps = {\n /** Trigger label and popover heading. */\n label: string;\n /** Map of option value → include/exclude. Neutral options are absent. */\n value: Record<string, FilterBarMultiFilterMode>;\n onChange: (value: Record<string, FilterBarMultiFilterMode>) => void;\n /** Chip options. When `truncated`, this is only the head set. */\n options: MultiSelectOption[];\n icon?: LabelIconSpec;\n description?: string;\n truncated?: boolean;\n total?: number;\n onSearch?: (query: string) => Promise<MultiSelectOption[]> | void;\n /** Allow committing free-typed / `*`-wildcard values absent from `options`. */\n allowCustomValue?: boolean;\n disabled?: boolean;\n className?: string;\n /** Fill the available width (form field) vs. shrink to content. Default true. */\n grow?: boolean;\n};\n\n// TriStateMultiSelect is the standalone include/exclude/neutral multi-select used\n// by FilterBar's \"multi\" kind, exposed for hand-written forms. It renders a\n// tristate combobox whose rows cycle neutral → include → exclude. It commits\n// changes immediately (autoSubmit:false) since a controlled form owns the value.\nexport function TriStateMultiSelect({ grow = true, ...rest }: TriStateMultiSelectProps) {\n const filter: FilterBarMultiFilter = { key: rest.label, kind: \"multi\", ...rest };\n return (\n <FilterBarContext.Provider value={{ autoSubmit: false }}>\n <MultiFilterField filter={filter} grow={grow} />\n </FilterBarContext.Provider>\n );\n}\n\n// MultiFilterPanel is the always-open embedded form of the multi filter (the\n// FilterBarFilterPanel / DataTable column-popover surface). Unlike the inline\n// field it is a panel, not an input, so it keeps the FilterPill row list.\nexport function MultiFilterPanel({\n filter,\n chrome = \"full\",\n}: {\n filter: FilterBarMultiFilter;\n chrome?: \"full\" | \"embedded\";\n}) {\n const [optionQuery, setOptionQuery] = useState(\"\");\n const [draft, setDraft] = useDebouncedMultiDraft(filter.value, filter.onChange);\n const showOptionFilter = filter.options.length > 7;\n const embedded = chrome === \"embedded\";\n const visibleOptions = useMemo(() => {\n const query = optionQuery.trim().toLowerCase();\n if (!query) return filter.options;\n return filter.options.filter((option) =>\n multiSelectOptionText(option).toLowerCase().includes(query),\n );\n }, [filter.options, optionQuery]);\n\n return (\n <div\n data-filter-panel-chrome={chrome}\n className={cn(\n \"min-w-[18rem] max-w-[22rem] text-popover-foreground\",\n embedded\n ? \"p-0\"\n : \"rounded-md border border-border bg-popover p-2 shadow-sm shadow-black/5\",\n )}\n >\n {!embedded && (\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <div className=\"text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n {filter.label}\n </div>\n <button\n type=\"button\"\n className=\"text-[10px] text-primary disabled:text-muted-foreground\"\n onClick={() => setDraft({})}\n disabled={Object.keys(draft).length === 0}\n >\n Clear all\n </button>\n </div>\n )}\n\n {showOptionFilter && (\n <div className=\"mb-2 flex items-center gap-2 rounded-md border border-input bg-background px-2\">\n <Icon icon={UiSearch} className=\"shrink-0 text-muted-foreground\" />\n <input\n type=\"search\"\n aria-label={`Filter ${filter.label} options`}\n className=\"h-8 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-placeholder\"\n placeholder={`Filter ${filter.label.toLowerCase()}`}\n value={optionQuery}\n onChange={(event) => setOptionQuery(event.target.value)}\n />\n </div>\n )}\n\n <div className=\"max-h-72 space-y-0.5 overflow-auto\">\n {visibleOptions.map((option) => {\n const mode = draft[option.value] ?? \"neutral\";\n const title = option.title ?? multiSelectOptionText(option);\n\n return (\n <div\n key={option.value}\n role=\"button\"\n tabIndex={0}\n data-filter-option={option.value}\n className=\"rounded-md px-1.5 py-0.5 hover:bg-accent/50 focus-visible:bg-accent/50 focus-visible:outline-none\"\n onClick={() =>\n setDraft(updateMultiFilterValue(draft, option.value, nextFilterMode(mode)))\n }\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n setDraft(updateMultiFilterValue(draft, option.value, nextFilterMode(mode)));\n }\n }}\n >\n <FilterPill\n className=\"w-full justify-between\"\n label={option.label}\n mode={mode}\n title={title}\n togglePosition=\"right\"\n onModeChange={(next) => setDraft(updateMultiFilterValue(draft, option.value, next))}\n />\n </div>\n );\n })}\n {visibleOptions.length === 0 && (\n <div className=\"px-2 py-3 text-sm text-muted-foreground\">No options found</div>\n )}\n </div>\n </div>\n );\n}\n"],"names":["useDebouncedMultiDraft","useState","useRef","useCallback","useMemo","mergeMultiSelectOptions","jsxs","Fragment","jsx","Combobox","comboboxLabelProps","cn","lookupFieldWidthClass","multiSelectOptionText","FilterBarContext","Icon","UiSearch","updateMultiFilterValue","nextFilterMode","FilterPill"],"mappings":"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"}
1
+ {"version":3,"file":"filter-bar-multi.cjs","sources":["../../src/components/filter-bar-multi.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from \"react\";\nimport { FilterPill } from \"../data/FilterPill\";\nimport { Icon, LabelIcon, type LabelIconSpec } from \"../data/Icon\";\nimport { UiSearch } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport { Combobox, type ComboboxOption } from \"./Combobox\";\nimport { FilterBarContext } from \"./filter-bar-context\";\nimport {\n comboboxLabelProps,\n lookupFieldWidthClass,\n mergeMultiSelectOptions,\n multiSelectOptionText,\n nextFilterMode,\n updateMultiFilterValue,\n useDebouncedMultiDraft,\n type FilterBarMultiFilterMode,\n} from \"./filter-bar-field-utils\";\nimport type { MultiSelectOption } from \"./MultiSelect\";\n\nexport type { FilterBarMultiFilterMode } from \"./filter-bar-field-utils\";\n\nexport type FilterBarMultiFilter = {\n key: string;\n /** Renders include/exclude chips for each option. */\n kind: \"multi\";\n label: string;\n /** Leading glyph shown before the label: a runtime icon name or a node. */\n icon?: LabelIconSpec;\n description?: string;\n /** Map of option value to include/exclude state. */\n value: Record<string, FilterBarMultiFilterMode>;\n /** Available chip options. When `truncated`, this is only the head set. */\n options: MultiSelectOption[];\n onChange: (value: Record<string, FilterBarMultiFilterMode>) => void;\n /** Ghost text shown in the field when no options are selected. */\n placeholder?: string;\n /**\n * True when more options exist than are in `options` (the option set was\n * capped server-side). Renders an \"… and N more\" hint and, with `onSearch`,\n * lets the user search the full set.\n */\n truncated?: boolean;\n /** True distinct count behind a truncated option set; drives the \"N more\" label. */\n total?: number;\n /**\n * Optional async fetch invoked (debounced) as the user types in the option\n * search box. Returns the options matching the query, which are merged into\n * the displayed list so values beyond the head become selectable. When\n * absent, the search box filters the static `options` client-side only.\n */\n onSearch?: (query: string) => Promise<MultiSelectOption[]> | void;\n /**\n * Lets the user commit a value absent from `options` (a free-typed name or a\n * `*` wildcard). The option search box stays visible and an \"Add\" row appears\n * when the query matches no listed option; committed customs pin to the top so\n * they stay toggleable. Use for MatchItem-style filters (table/login patterns).\n */\n allowCustomValue?: boolean;\n disabled?: boolean;\n className?: string;\n};\n\n// MultiFilterField renders a \"multi\" filter as a tristate Combobox so the bar\n// stays one control family with the lookup fields: same shell, inline label,\n// typeahead input, and portaled listbox of FilterPill rows.\nexport function MultiFilterField({\n filter,\n grow,\n}: {\n filter: FilterBarMultiFilter;\n grow: boolean;\n}) {\n const [draft, setDraft] = useDebouncedMultiDraft(filter.value, filter.onChange);\n // Server-search state: the matches for the active query (they replace the\n // head while searching), plus the in-flight flag. Combobox owns the 250ms\n // debounce; this adapter only resolves the Promise, latest query wins.\n const [searchOptions, setSearchOptions] = useState<MultiSelectOption[]>([]);\n const [loading, setLoading] = useState(false);\n const [activeQuery, setActiveQuery] = useState(\"\");\n const latestQuery = useRef(\"\");\n\n const onSearchProp = filter.onSearch;\n const handleSearch = useCallback(\n (query: string) => {\n latestQuery.current = query;\n setActiveQuery(query);\n if (!query) {\n setSearchOptions([]);\n setLoading(false);\n return;\n }\n setLoading(true);\n Promise.resolve(onSearchProp?.(query))\n .then((opts) => {\n if (latestQuery.current !== query) return;\n setLoading(false);\n setSearchOptions(Array.isArray(opts) ? opts : []);\n })\n .catch(() => {\n if (latestQuery.current !== query) return;\n setLoading(false);\n setSearchOptions([]);\n });\n },\n [onSearchProp],\n );\n\n // allowCustomValue: any toggled value absent from `options` (a free-typed name\n // or a `*` wildcard) is synthesised as a pinned option so it renders and stays\n // toggleable across reopens.\n const allowCustomValue = Boolean(filter.allowCustomValue);\n const baseOptions = useMemo(() => {\n if (!allowCustomValue) return filter.options;\n const known = new Set(filter.options.map((o) => o.value));\n const pinned = Object.keys(draft)\n .filter((v) => !known.has(v))\n .map((v) => ({ value: v, label: v }));\n return mergeMultiSelectOptions(pinned, filter.options);\n }, [allowCustomValue, filter.options, draft]);\n\n // Server search active → the matches REPLACE the head, but any head option\n // the user has already toggled (include/exclude) stays pinned so their\n // selection remains visible and changeable. Selected first, then matches.\n // Without a query (or without onSearch) the head set renders and Combobox\n // client-filters it.\n const visibleOptions = useMemo(() => {\n if (!onSearchProp || !activeQuery) return baseOptions;\n const selectedHead = baseOptions.filter((o) => draft[o.value] !== undefined);\n return mergeMultiSelectOptions(selectedHead, searchOptions);\n }, [onSearchProp, activeQuery, baseOptions, searchOptions, draft]);\n\n const moreCount =\n filter.truncated && filter.total ? Math.max(filter.total - filter.options.length, 0) : 0;\n const footer =\n moreCount > 0 && !activeQuery ? (\n <>\n … and {moreCount.toLocaleString()} more\n {onSearchProp ? \" — type to search all\" : \"\"}\n </>\n ) : undefined;\n\n return (\n <Combobox\n multiple\n tristate\n {...comboboxLabelProps(filter)}\n options={toComboboxOptions(visibleOptions)}\n value={draft}\n onChange={setDraft}\n allowCustomValue={allowCustomValue}\n size=\"sm\"\n className={cn(lookupFieldWidthClass(grow), filter.className)}\n {...(filter.placeholder !== undefined ? { placeholder: filter.placeholder } : {})}\n {...(onSearchProp !== undefined ? { onSearch: handleSearch, loading } : {})}\n {...(filter.disabled !== undefined ? { disabled: filter.disabled } : {})}\n {...(footer !== undefined ? { footer } : {})}\n />\n );\n}\n\nfunction toComboboxOptions(options: MultiSelectOption[]): ComboboxOption[] {\n return options.map((option) => {\n const title = option.title ?? multiSelectOptionText(option);\n return {\n value: option.value,\n // A node label (e.g. an icon + text span) can't render as the combobox's\n // option row or closed summary, so fall back to the option's title text —\n // the human-readable label — instead of the raw value.\n label: typeof option.label === \"string\" ? option.label : title,\n title,\n ...(option.description !== undefined ? { description: option.description } : {}),\n ...(option.selectedLabel !== undefined ? { selectedLabel: option.selectedLabel } : {}),\n ...(option.icon !== undefined ? { icon: option.icon } : {}),\n ...(option.disabled !== undefined ? { disabled: option.disabled } : {}),\n };\n });\n}\n\nexport type TriStateMultiSelectProps = {\n /** Trigger label and popover heading. */\n label: string;\n /** Map of option value → include/exclude. Neutral options are absent. */\n value: Record<string, FilterBarMultiFilterMode>;\n onChange: (value: Record<string, FilterBarMultiFilterMode>) => void;\n /** Chip options. When `truncated`, this is only the head set. */\n options: MultiSelectOption[];\n icon?: LabelIconSpec;\n description?: string;\n truncated?: boolean;\n total?: number;\n onSearch?: (query: string) => Promise<MultiSelectOption[]> | void;\n /** Allow committing free-typed / `*`-wildcard values absent from `options`. */\n allowCustomValue?: boolean;\n disabled?: boolean;\n className?: string;\n /** Fill the available width (form field) vs. shrink to content. Default true. */\n grow?: boolean;\n};\n\n// TriStateMultiSelect is the standalone include/exclude/neutral multi-select used\n// by FilterBar's \"multi\" kind, exposed for hand-written forms. It renders a\n// tristate combobox whose rows cycle neutral → include → exclude. It commits\n// changes immediately (autoSubmit:false) since a controlled form owns the value.\nexport function TriStateMultiSelect({ grow = true, ...rest }: TriStateMultiSelectProps) {\n const filter: FilterBarMultiFilter = { key: rest.label, kind: \"multi\", ...rest };\n return (\n <FilterBarContext.Provider value={{ autoSubmit: false }}>\n <MultiFilterField filter={filter} grow={grow} />\n </FilterBarContext.Provider>\n );\n}\n\n// MultiFilterPanel is the always-open embedded form of the multi filter (the\n// FilterBarFilterPanel / DataTable column-popover surface). Unlike the inline\n// field it is a panel, not an input, so it keeps the FilterPill row list.\nexport function MultiFilterPanel({\n filter,\n chrome = \"full\",\n}: {\n filter: FilterBarMultiFilter;\n chrome?: \"full\" | \"embedded\";\n}) {\n const [optionQuery, setOptionQuery] = useState(\"\");\n const [draft, setDraft] = useDebouncedMultiDraft(filter.value, filter.onChange);\n const showOptionFilter = filter.options.length > 7;\n const embedded = chrome === \"embedded\";\n const visibleOptions = useMemo(() => {\n const query = optionQuery.trim().toLowerCase();\n if (!query) return filter.options;\n return filter.options.filter((option) =>\n multiSelectOptionText(option).toLowerCase().includes(query),\n );\n }, [filter.options, optionQuery]);\n\n return (\n <div\n data-filter-panel-chrome={chrome}\n className={cn(\n \"min-w-[18rem] max-w-[22rem] text-popover-foreground\",\n embedded\n ? \"p-0\"\n : \"rounded-md border border-border bg-popover p-2 shadow-sm shadow-black/5\",\n )}\n >\n {!embedded && (\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <div className=\"text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n {filter.label}\n </div>\n <button\n type=\"button\"\n className=\"text-[10px] text-primary disabled:text-muted-foreground\"\n onClick={() => setDraft({})}\n disabled={Object.keys(draft).length === 0}\n >\n Clear all\n </button>\n </div>\n )}\n\n {showOptionFilter && (\n <div className=\"mb-2 flex items-center gap-2 rounded-md border border-input bg-background px-2\">\n <Icon icon={UiSearch} className=\"shrink-0 text-muted-foreground\" />\n <input\n type=\"search\"\n aria-label={`Filter ${filter.label} options`}\n className=\"h-8 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-placeholder\"\n placeholder={`Filter ${filter.label.toLowerCase()}`}\n value={optionQuery}\n onChange={(event) => setOptionQuery(event.target.value)}\n />\n </div>\n )}\n\n <div className=\"max-h-72 space-y-0.5 overflow-auto\">\n {visibleOptions.map((option) => {\n const mode = draft[option.value] ?? \"neutral\";\n const title = option.title ?? multiSelectOptionText(option);\n\n return (\n <div\n key={option.value}\n role=\"button\"\n tabIndex={0}\n data-filter-option={option.value}\n className=\"rounded-md px-1.5 py-0.5 hover:bg-accent/50 focus-visible:bg-accent/50 focus-visible:outline-none\"\n onClick={() =>\n setDraft(updateMultiFilterValue(draft, option.value, nextFilterMode(mode)))\n }\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n setDraft(updateMultiFilterValue(draft, option.value, nextFilterMode(mode)));\n }\n }}\n >\n <FilterPill\n className=\"w-full justify-between\"\n label={\n <span className=\"inline-flex min-w-0 items-start gap-1.5\">\n <LabelIcon icon={option.icon} className=\"mt-0.5 shrink-0 text-sm text-muted-foreground\" />\n <span className=\"min-w-0 text-left\">\n <span className=\"block truncate\">{option.label}</span>\n {option.description && (\n <span className=\"mt-0.5 block truncate text-xs font-normal text-muted-foreground\">\n {option.description}\n </span>\n )}\n </span>\n </span>\n }\n mode={mode}\n title={title}\n togglePosition=\"right\"\n onModeChange={(next) => setDraft(updateMultiFilterValue(draft, option.value, next))}\n />\n </div>\n );\n })}\n {visibleOptions.length === 0 && (\n <div className=\"px-2 py-3 text-sm text-muted-foreground\">No options found</div>\n )}\n </div>\n </div>\n );\n}\n"],"names":["useDebouncedMultiDraft","useState","useRef","useCallback","useMemo","mergeMultiSelectOptions","jsxs","Fragment","jsx","Combobox","comboboxLabelProps","cn","lookupFieldWidthClass","multiSelectOptionText","FilterBarContext","Icon","UiSearch","updateMultiFilterValue","nextFilterMode","FilterPill","LabelIcon"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"filter-bar-multi.d.ts","sourceRoot":"","sources":["../../src/components/filter-bar-multi.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAQ,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAKxD,OAAO,EAQL,KAAK,wBAAwB,EAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,qDAAqD;IACrD,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAChD,2EAA2E;IAC3E,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,KAAK,IAAI,CAAC;IACpE,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,EAAE,CAAC,GAAG,IAAI,CAAC;IAClE;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAKF,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,IAAI,GACL,EAAE;IACD,MAAM,EAAE,oBAAoB,CAAC;IAC7B,IAAI,EAAE,OAAO,CAAC;CACf,+BAuFA;AAiBD,MAAM,MAAM,wBAAwB,GAAG;IACrC,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAChD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,KAAK,IAAI,CAAC;IACpE,iEAAiE;IACjE,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,EAAE,CAAC,GAAG,IAAI,CAAC;IAClE,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAMF,wBAAgB,mBAAmB,CAAC,EAAE,IAAW,EAAE,GAAG,IAAI,EAAE,EAAE,wBAAwB,+BAOrF;AAKD,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,MAAe,GAChB,EAAE;IACD,MAAM,EAAE,oBAAoB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;CAC9B,+BA4FA"}
1
+ {"version":3,"file":"filter-bar-multi.d.ts","sourceRoot":"","sources":["../../src/components/filter-bar-multi.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAKnE,OAAO,EAQL,KAAK,wBAAwB,EAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,qDAAqD;IACrD,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAChD,2EAA2E;IAC3E,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,KAAK,IAAI,CAAC;IACpE,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,EAAE,CAAC,GAAG,IAAI,CAAC;IAClE;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAKF,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,IAAI,GACL,EAAE;IACD,MAAM,EAAE,oBAAoB,CAAC;IAC7B,IAAI,EAAE,OAAO,CAAC;CACf,+BAuFA;AAoBD,MAAM,MAAM,wBAAwB,GAAG;IACrC,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAChD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,KAAK,IAAI,CAAC;IACpE,iEAAiE;IACjE,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,EAAE,CAAC,GAAG,IAAI,CAAC;IAClE,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAMF,wBAAgB,mBAAmB,CAAC,EAAE,IAAW,EAAE,GAAG,IAAI,EAAE,EAAE,wBAAwB,+BAOrF;AAKD,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,MAAe,GAChB,EAAE;IACD,MAAM,EAAE,oBAAoB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;CAC9B,+BAwGA"}
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useState, useRef, useCallback, useMemo } from "react";
3
3
  import { FilterPill } from "../data/FilterPill.js";
4
- import { Icon } from "../data/Icon.js";
4
+ import { Icon, LabelIcon } from "../data/Icon.js";
5
5
  import { cn } from "../lib/utils.js";
6
6
  import { Combobox } from "./Combobox.js";
7
7
  import { FilterBarContext } from "./filter-bar-context.js";
@@ -87,6 +87,9 @@ function toComboboxOptions(options) {
87
87
  // the human-readable label — instead of the raw value.
88
88
  label: typeof option.label === "string" ? option.label : title,
89
89
  title,
90
+ ...option.description !== void 0 ? { description: option.description } : {},
91
+ ...option.selectedLabel !== void 0 ? { selectedLabel: option.selectedLabel } : {},
92
+ ...option.icon !== void 0 ? { icon: option.icon } : {},
90
93
  ...option.disabled !== void 0 ? { disabled: option.disabled } : {}
91
94
  };
92
95
  });
@@ -168,7 +171,13 @@ function MultiFilterPanel({
168
171
  FilterPill,
169
172
  {
170
173
  className: "w-full justify-between",
171
- label: option.label,
174
+ label: /* @__PURE__ */ jsxs("span", { className: "inline-flex min-w-0 items-start gap-1.5", children: [
175
+ /* @__PURE__ */ jsx(LabelIcon, { icon: option.icon, className: "mt-0.5 shrink-0 text-sm text-muted-foreground" }),
176
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 text-left", children: [
177
+ /* @__PURE__ */ jsx("span", { className: "block truncate", children: option.label }),
178
+ option.description && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block truncate text-xs font-normal text-muted-foreground", children: option.description })
179
+ ] })
180
+ ] }),
172
181
  mode,
173
182
  title,
174
183
  togglePosition: "right",
@@ -1 +1 @@
1
- {"version":3,"file":"filter-bar-multi.js","sources":["../../src/components/filter-bar-multi.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from \"react\";\nimport { FilterPill } from \"../data/FilterPill\";\nimport { Icon, type LabelIconSpec } from \"../data/Icon\";\nimport { UiSearch } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport { Combobox, type ComboboxOption } from \"./Combobox\";\nimport { FilterBarContext } from \"./filter-bar-context\";\nimport {\n comboboxLabelProps,\n lookupFieldWidthClass,\n mergeMultiSelectOptions,\n multiSelectOptionText,\n nextFilterMode,\n updateMultiFilterValue,\n useDebouncedMultiDraft,\n type FilterBarMultiFilterMode,\n} from \"./filter-bar-field-utils\";\nimport type { MultiSelectOption } from \"./MultiSelect\";\n\nexport type { FilterBarMultiFilterMode } from \"./filter-bar-field-utils\";\n\nexport type FilterBarMultiFilter = {\n key: string;\n /** Renders include/exclude chips for each option. */\n kind: \"multi\";\n label: string;\n /** Leading glyph shown before the label: a runtime icon name or a node. */\n icon?: LabelIconSpec;\n description?: string;\n /** Map of option value to include/exclude state. */\n value: Record<string, FilterBarMultiFilterMode>;\n /** Available chip options. When `truncated`, this is only the head set. */\n options: MultiSelectOption[];\n onChange: (value: Record<string, FilterBarMultiFilterMode>) => void;\n /** Ghost text shown in the field when no options are selected. */\n placeholder?: string;\n /**\n * True when more options exist than are in `options` (the option set was\n * capped server-side). Renders an \"… and N more\" hint and, with `onSearch`,\n * lets the user search the full set.\n */\n truncated?: boolean;\n /** True distinct count behind a truncated option set; drives the \"N more\" label. */\n total?: number;\n /**\n * Optional async fetch invoked (debounced) as the user types in the option\n * search box. Returns the options matching the query, which are merged into\n * the displayed list so values beyond the head become selectable. When\n * absent, the search box filters the static `options` client-side only.\n */\n onSearch?: (query: string) => Promise<MultiSelectOption[]> | void;\n /**\n * Lets the user commit a value absent from `options` (a free-typed name or a\n * `*` wildcard). The option search box stays visible and an \"Add\" row appears\n * when the query matches no listed option; committed customs pin to the top so\n * they stay toggleable. Use for MatchItem-style filters (table/login patterns).\n */\n allowCustomValue?: boolean;\n disabled?: boolean;\n className?: string;\n};\n\n// MultiFilterField renders a \"multi\" filter as a tristate Combobox so the bar\n// stays one control family with the lookup fields: same shell, inline label,\n// typeahead input, and portaled listbox of FilterPill rows.\nexport function MultiFilterField({\n filter,\n grow,\n}: {\n filter: FilterBarMultiFilter;\n grow: boolean;\n}) {\n const [draft, setDraft] = useDebouncedMultiDraft(filter.value, filter.onChange);\n // Server-search state: the matches for the active query (they replace the\n // head while searching), plus the in-flight flag. Combobox owns the 250ms\n // debounce; this adapter only resolves the Promise, latest query wins.\n const [searchOptions, setSearchOptions] = useState<MultiSelectOption[]>([]);\n const [loading, setLoading] = useState(false);\n const [activeQuery, setActiveQuery] = useState(\"\");\n const latestQuery = useRef(\"\");\n\n const onSearchProp = filter.onSearch;\n const handleSearch = useCallback(\n (query: string) => {\n latestQuery.current = query;\n setActiveQuery(query);\n if (!query) {\n setSearchOptions([]);\n setLoading(false);\n return;\n }\n setLoading(true);\n Promise.resolve(onSearchProp?.(query))\n .then((opts) => {\n if (latestQuery.current !== query) return;\n setLoading(false);\n setSearchOptions(Array.isArray(opts) ? opts : []);\n })\n .catch(() => {\n if (latestQuery.current !== query) return;\n setLoading(false);\n setSearchOptions([]);\n });\n },\n [onSearchProp],\n );\n\n // allowCustomValue: any toggled value absent from `options` (a free-typed name\n // or a `*` wildcard) is synthesised as a pinned option so it renders and stays\n // toggleable across reopens.\n const allowCustomValue = Boolean(filter.allowCustomValue);\n const baseOptions = useMemo(() => {\n if (!allowCustomValue) return filter.options;\n const known = new Set(filter.options.map((o) => o.value));\n const pinned = Object.keys(draft)\n .filter((v) => !known.has(v))\n .map((v) => ({ value: v, label: v }));\n return mergeMultiSelectOptions(pinned, filter.options);\n }, [allowCustomValue, filter.options, draft]);\n\n // Server search active → the matches REPLACE the head, but any head option\n // the user has already toggled (include/exclude) stays pinned so their\n // selection remains visible and changeable. Selected first, then matches.\n // Without a query (or without onSearch) the head set renders and Combobox\n // client-filters it.\n const visibleOptions = useMemo(() => {\n if (!onSearchProp || !activeQuery) return baseOptions;\n const selectedHead = baseOptions.filter((o) => draft[o.value] !== undefined);\n return mergeMultiSelectOptions(selectedHead, searchOptions);\n }, [onSearchProp, activeQuery, baseOptions, searchOptions, draft]);\n\n const moreCount =\n filter.truncated && filter.total ? Math.max(filter.total - filter.options.length, 0) : 0;\n const footer =\n moreCount > 0 && !activeQuery ? (\n <>\n … and {moreCount.toLocaleString()} more\n {onSearchProp ? \" — type to search all\" : \"\"}\n </>\n ) : undefined;\n\n return (\n <Combobox\n multiple\n tristate\n {...comboboxLabelProps(filter)}\n options={toComboboxOptions(visibleOptions)}\n value={draft}\n onChange={setDraft}\n allowCustomValue={allowCustomValue}\n size=\"sm\"\n className={cn(lookupFieldWidthClass(grow), filter.className)}\n {...(filter.placeholder !== undefined ? { placeholder: filter.placeholder } : {})}\n {...(onSearchProp !== undefined ? { onSearch: handleSearch, loading } : {})}\n {...(filter.disabled !== undefined ? { disabled: filter.disabled } : {})}\n {...(footer !== undefined ? { footer } : {})}\n />\n );\n}\n\nfunction toComboboxOptions(options: MultiSelectOption[]): ComboboxOption[] {\n return options.map((option) => {\n const title = option.title ?? multiSelectOptionText(option);\n return {\n value: option.value,\n // A node label (e.g. an icon + text span) can't render as the combobox's\n // option row or closed summary, so fall back to the option's title text —\n // the human-readable label — instead of the raw value.\n label: typeof option.label === \"string\" ? option.label : title,\n title,\n ...(option.disabled !== undefined ? { disabled: option.disabled } : {}),\n };\n });\n}\n\nexport type TriStateMultiSelectProps = {\n /** Trigger label and popover heading. */\n label: string;\n /** Map of option value → include/exclude. Neutral options are absent. */\n value: Record<string, FilterBarMultiFilterMode>;\n onChange: (value: Record<string, FilterBarMultiFilterMode>) => void;\n /** Chip options. When `truncated`, this is only the head set. */\n options: MultiSelectOption[];\n icon?: LabelIconSpec;\n description?: string;\n truncated?: boolean;\n total?: number;\n onSearch?: (query: string) => Promise<MultiSelectOption[]> | void;\n /** Allow committing free-typed / `*`-wildcard values absent from `options`. */\n allowCustomValue?: boolean;\n disabled?: boolean;\n className?: string;\n /** Fill the available width (form field) vs. shrink to content. Default true. */\n grow?: boolean;\n};\n\n// TriStateMultiSelect is the standalone include/exclude/neutral multi-select used\n// by FilterBar's \"multi\" kind, exposed for hand-written forms. It renders a\n// tristate combobox whose rows cycle neutral → include → exclude. It commits\n// changes immediately (autoSubmit:false) since a controlled form owns the value.\nexport function TriStateMultiSelect({ grow = true, ...rest }: TriStateMultiSelectProps) {\n const filter: FilterBarMultiFilter = { key: rest.label, kind: \"multi\", ...rest };\n return (\n <FilterBarContext.Provider value={{ autoSubmit: false }}>\n <MultiFilterField filter={filter} grow={grow} />\n </FilterBarContext.Provider>\n );\n}\n\n// MultiFilterPanel is the always-open embedded form of the multi filter (the\n// FilterBarFilterPanel / DataTable column-popover surface). Unlike the inline\n// field it is a panel, not an input, so it keeps the FilterPill row list.\nexport function MultiFilterPanel({\n filter,\n chrome = \"full\",\n}: {\n filter: FilterBarMultiFilter;\n chrome?: \"full\" | \"embedded\";\n}) {\n const [optionQuery, setOptionQuery] = useState(\"\");\n const [draft, setDraft] = useDebouncedMultiDraft(filter.value, filter.onChange);\n const showOptionFilter = filter.options.length > 7;\n const embedded = chrome === \"embedded\";\n const visibleOptions = useMemo(() => {\n const query = optionQuery.trim().toLowerCase();\n if (!query) return filter.options;\n return filter.options.filter((option) =>\n multiSelectOptionText(option).toLowerCase().includes(query),\n );\n }, [filter.options, optionQuery]);\n\n return (\n <div\n data-filter-panel-chrome={chrome}\n className={cn(\n \"min-w-[18rem] max-w-[22rem] text-popover-foreground\",\n embedded\n ? \"p-0\"\n : \"rounded-md border border-border bg-popover p-2 shadow-sm shadow-black/5\",\n )}\n >\n {!embedded && (\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <div className=\"text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n {filter.label}\n </div>\n <button\n type=\"button\"\n className=\"text-[10px] text-primary disabled:text-muted-foreground\"\n onClick={() => setDraft({})}\n disabled={Object.keys(draft).length === 0}\n >\n Clear all\n </button>\n </div>\n )}\n\n {showOptionFilter && (\n <div className=\"mb-2 flex items-center gap-2 rounded-md border border-input bg-background px-2\">\n <Icon icon={UiSearch} className=\"shrink-0 text-muted-foreground\" />\n <input\n type=\"search\"\n aria-label={`Filter ${filter.label} options`}\n className=\"h-8 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-placeholder\"\n placeholder={`Filter ${filter.label.toLowerCase()}`}\n value={optionQuery}\n onChange={(event) => setOptionQuery(event.target.value)}\n />\n </div>\n )}\n\n <div className=\"max-h-72 space-y-0.5 overflow-auto\">\n {visibleOptions.map((option) => {\n const mode = draft[option.value] ?? \"neutral\";\n const title = option.title ?? multiSelectOptionText(option);\n\n return (\n <div\n key={option.value}\n role=\"button\"\n tabIndex={0}\n data-filter-option={option.value}\n className=\"rounded-md px-1.5 py-0.5 hover:bg-accent/50 focus-visible:bg-accent/50 focus-visible:outline-none\"\n onClick={() =>\n setDraft(updateMultiFilterValue(draft, option.value, nextFilterMode(mode)))\n }\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n setDraft(updateMultiFilterValue(draft, option.value, nextFilterMode(mode)));\n }\n }}\n >\n <FilterPill\n className=\"w-full justify-between\"\n label={option.label}\n mode={mode}\n title={title}\n togglePosition=\"right\"\n onModeChange={(next) => setDraft(updateMultiFilterValue(draft, option.value, next))}\n />\n </div>\n );\n })}\n {visibleOptions.length === 0 && (\n <div className=\"px-2 py-3 text-sm text-muted-foreground\">No options found</div>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"filter-bar-multi.js","sources":["../../src/components/filter-bar-multi.tsx"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from \"react\";\nimport { FilterPill } from \"../data/FilterPill\";\nimport { Icon, LabelIcon, type LabelIconSpec } from \"../data/Icon\";\nimport { UiSearch } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport { Combobox, type ComboboxOption } from \"./Combobox\";\nimport { FilterBarContext } from \"./filter-bar-context\";\nimport {\n comboboxLabelProps,\n lookupFieldWidthClass,\n mergeMultiSelectOptions,\n multiSelectOptionText,\n nextFilterMode,\n updateMultiFilterValue,\n useDebouncedMultiDraft,\n type FilterBarMultiFilterMode,\n} from \"./filter-bar-field-utils\";\nimport type { MultiSelectOption } from \"./MultiSelect\";\n\nexport type { FilterBarMultiFilterMode } from \"./filter-bar-field-utils\";\n\nexport type FilterBarMultiFilter = {\n key: string;\n /** Renders include/exclude chips for each option. */\n kind: \"multi\";\n label: string;\n /** Leading glyph shown before the label: a runtime icon name or a node. */\n icon?: LabelIconSpec;\n description?: string;\n /** Map of option value to include/exclude state. */\n value: Record<string, FilterBarMultiFilterMode>;\n /** Available chip options. When `truncated`, this is only the head set. */\n options: MultiSelectOption[];\n onChange: (value: Record<string, FilterBarMultiFilterMode>) => void;\n /** Ghost text shown in the field when no options are selected. */\n placeholder?: string;\n /**\n * True when more options exist than are in `options` (the option set was\n * capped server-side). Renders an \"… and N more\" hint and, with `onSearch`,\n * lets the user search the full set.\n */\n truncated?: boolean;\n /** True distinct count behind a truncated option set; drives the \"N more\" label. */\n total?: number;\n /**\n * Optional async fetch invoked (debounced) as the user types in the option\n * search box. Returns the options matching the query, which are merged into\n * the displayed list so values beyond the head become selectable. When\n * absent, the search box filters the static `options` client-side only.\n */\n onSearch?: (query: string) => Promise<MultiSelectOption[]> | void;\n /**\n * Lets the user commit a value absent from `options` (a free-typed name or a\n * `*` wildcard). The option search box stays visible and an \"Add\" row appears\n * when the query matches no listed option; committed customs pin to the top so\n * they stay toggleable. Use for MatchItem-style filters (table/login patterns).\n */\n allowCustomValue?: boolean;\n disabled?: boolean;\n className?: string;\n};\n\n// MultiFilterField renders a \"multi\" filter as a tristate Combobox so the bar\n// stays one control family with the lookup fields: same shell, inline label,\n// typeahead input, and portaled listbox of FilterPill rows.\nexport function MultiFilterField({\n filter,\n grow,\n}: {\n filter: FilterBarMultiFilter;\n grow: boolean;\n}) {\n const [draft, setDraft] = useDebouncedMultiDraft(filter.value, filter.onChange);\n // Server-search state: the matches for the active query (they replace the\n // head while searching), plus the in-flight flag. Combobox owns the 250ms\n // debounce; this adapter only resolves the Promise, latest query wins.\n const [searchOptions, setSearchOptions] = useState<MultiSelectOption[]>([]);\n const [loading, setLoading] = useState(false);\n const [activeQuery, setActiveQuery] = useState(\"\");\n const latestQuery = useRef(\"\");\n\n const onSearchProp = filter.onSearch;\n const handleSearch = useCallback(\n (query: string) => {\n latestQuery.current = query;\n setActiveQuery(query);\n if (!query) {\n setSearchOptions([]);\n setLoading(false);\n return;\n }\n setLoading(true);\n Promise.resolve(onSearchProp?.(query))\n .then((opts) => {\n if (latestQuery.current !== query) return;\n setLoading(false);\n setSearchOptions(Array.isArray(opts) ? opts : []);\n })\n .catch(() => {\n if (latestQuery.current !== query) return;\n setLoading(false);\n setSearchOptions([]);\n });\n },\n [onSearchProp],\n );\n\n // allowCustomValue: any toggled value absent from `options` (a free-typed name\n // or a `*` wildcard) is synthesised as a pinned option so it renders and stays\n // toggleable across reopens.\n const allowCustomValue = Boolean(filter.allowCustomValue);\n const baseOptions = useMemo(() => {\n if (!allowCustomValue) return filter.options;\n const known = new Set(filter.options.map((o) => o.value));\n const pinned = Object.keys(draft)\n .filter((v) => !known.has(v))\n .map((v) => ({ value: v, label: v }));\n return mergeMultiSelectOptions(pinned, filter.options);\n }, [allowCustomValue, filter.options, draft]);\n\n // Server search active → the matches REPLACE the head, but any head option\n // the user has already toggled (include/exclude) stays pinned so their\n // selection remains visible and changeable. Selected first, then matches.\n // Without a query (or without onSearch) the head set renders and Combobox\n // client-filters it.\n const visibleOptions = useMemo(() => {\n if (!onSearchProp || !activeQuery) return baseOptions;\n const selectedHead = baseOptions.filter((o) => draft[o.value] !== undefined);\n return mergeMultiSelectOptions(selectedHead, searchOptions);\n }, [onSearchProp, activeQuery, baseOptions, searchOptions, draft]);\n\n const moreCount =\n filter.truncated && filter.total ? Math.max(filter.total - filter.options.length, 0) : 0;\n const footer =\n moreCount > 0 && !activeQuery ? (\n <>\n … and {moreCount.toLocaleString()} more\n {onSearchProp ? \" — type to search all\" : \"\"}\n </>\n ) : undefined;\n\n return (\n <Combobox\n multiple\n tristate\n {...comboboxLabelProps(filter)}\n options={toComboboxOptions(visibleOptions)}\n value={draft}\n onChange={setDraft}\n allowCustomValue={allowCustomValue}\n size=\"sm\"\n className={cn(lookupFieldWidthClass(grow), filter.className)}\n {...(filter.placeholder !== undefined ? { placeholder: filter.placeholder } : {})}\n {...(onSearchProp !== undefined ? { onSearch: handleSearch, loading } : {})}\n {...(filter.disabled !== undefined ? { disabled: filter.disabled } : {})}\n {...(footer !== undefined ? { footer } : {})}\n />\n );\n}\n\nfunction toComboboxOptions(options: MultiSelectOption[]): ComboboxOption[] {\n return options.map((option) => {\n const title = option.title ?? multiSelectOptionText(option);\n return {\n value: option.value,\n // A node label (e.g. an icon + text span) can't render as the combobox's\n // option row or closed summary, so fall back to the option's title text —\n // the human-readable label — instead of the raw value.\n label: typeof option.label === \"string\" ? option.label : title,\n title,\n ...(option.description !== undefined ? { description: option.description } : {}),\n ...(option.selectedLabel !== undefined ? { selectedLabel: option.selectedLabel } : {}),\n ...(option.icon !== undefined ? { icon: option.icon } : {}),\n ...(option.disabled !== undefined ? { disabled: option.disabled } : {}),\n };\n });\n}\n\nexport type TriStateMultiSelectProps = {\n /** Trigger label and popover heading. */\n label: string;\n /** Map of option value → include/exclude. Neutral options are absent. */\n value: Record<string, FilterBarMultiFilterMode>;\n onChange: (value: Record<string, FilterBarMultiFilterMode>) => void;\n /** Chip options. When `truncated`, this is only the head set. */\n options: MultiSelectOption[];\n icon?: LabelIconSpec;\n description?: string;\n truncated?: boolean;\n total?: number;\n onSearch?: (query: string) => Promise<MultiSelectOption[]> | void;\n /** Allow committing free-typed / `*`-wildcard values absent from `options`. */\n allowCustomValue?: boolean;\n disabled?: boolean;\n className?: string;\n /** Fill the available width (form field) vs. shrink to content. Default true. */\n grow?: boolean;\n};\n\n// TriStateMultiSelect is the standalone include/exclude/neutral multi-select used\n// by FilterBar's \"multi\" kind, exposed for hand-written forms. It renders a\n// tristate combobox whose rows cycle neutral → include → exclude. It commits\n// changes immediately (autoSubmit:false) since a controlled form owns the value.\nexport function TriStateMultiSelect({ grow = true, ...rest }: TriStateMultiSelectProps) {\n const filter: FilterBarMultiFilter = { key: rest.label, kind: \"multi\", ...rest };\n return (\n <FilterBarContext.Provider value={{ autoSubmit: false }}>\n <MultiFilterField filter={filter} grow={grow} />\n </FilterBarContext.Provider>\n );\n}\n\n// MultiFilterPanel is the always-open embedded form of the multi filter (the\n// FilterBarFilterPanel / DataTable column-popover surface). Unlike the inline\n// field it is a panel, not an input, so it keeps the FilterPill row list.\nexport function MultiFilterPanel({\n filter,\n chrome = \"full\",\n}: {\n filter: FilterBarMultiFilter;\n chrome?: \"full\" | \"embedded\";\n}) {\n const [optionQuery, setOptionQuery] = useState(\"\");\n const [draft, setDraft] = useDebouncedMultiDraft(filter.value, filter.onChange);\n const showOptionFilter = filter.options.length > 7;\n const embedded = chrome === \"embedded\";\n const visibleOptions = useMemo(() => {\n const query = optionQuery.trim().toLowerCase();\n if (!query) return filter.options;\n return filter.options.filter((option) =>\n multiSelectOptionText(option).toLowerCase().includes(query),\n );\n }, [filter.options, optionQuery]);\n\n return (\n <div\n data-filter-panel-chrome={chrome}\n className={cn(\n \"min-w-[18rem] max-w-[22rem] text-popover-foreground\",\n embedded\n ? \"p-0\"\n : \"rounded-md border border-border bg-popover p-2 shadow-sm shadow-black/5\",\n )}\n >\n {!embedded && (\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <div className=\"text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n {filter.label}\n </div>\n <button\n type=\"button\"\n className=\"text-[10px] text-primary disabled:text-muted-foreground\"\n onClick={() => setDraft({})}\n disabled={Object.keys(draft).length === 0}\n >\n Clear all\n </button>\n </div>\n )}\n\n {showOptionFilter && (\n <div className=\"mb-2 flex items-center gap-2 rounded-md border border-input bg-background px-2\">\n <Icon icon={UiSearch} className=\"shrink-0 text-muted-foreground\" />\n <input\n type=\"search\"\n aria-label={`Filter ${filter.label} options`}\n className=\"h-8 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-placeholder\"\n placeholder={`Filter ${filter.label.toLowerCase()}`}\n value={optionQuery}\n onChange={(event) => setOptionQuery(event.target.value)}\n />\n </div>\n )}\n\n <div className=\"max-h-72 space-y-0.5 overflow-auto\">\n {visibleOptions.map((option) => {\n const mode = draft[option.value] ?? \"neutral\";\n const title = option.title ?? multiSelectOptionText(option);\n\n return (\n <div\n key={option.value}\n role=\"button\"\n tabIndex={0}\n data-filter-option={option.value}\n className=\"rounded-md px-1.5 py-0.5 hover:bg-accent/50 focus-visible:bg-accent/50 focus-visible:outline-none\"\n onClick={() =>\n setDraft(updateMultiFilterValue(draft, option.value, nextFilterMode(mode)))\n }\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n setDraft(updateMultiFilterValue(draft, option.value, nextFilterMode(mode)));\n }\n }}\n >\n <FilterPill\n className=\"w-full justify-between\"\n label={\n <span className=\"inline-flex min-w-0 items-start gap-1.5\">\n <LabelIcon icon={option.icon} className=\"mt-0.5 shrink-0 text-sm text-muted-foreground\" />\n <span className=\"min-w-0 text-left\">\n <span className=\"block truncate\">{option.label}</span>\n {option.description && (\n <span className=\"mt-0.5 block truncate text-xs font-normal text-muted-foreground\">\n {option.description}\n </span>\n )}\n </span>\n </span>\n }\n mode={mode}\n title={title}\n togglePosition=\"right\"\n onModeChange={(next) => setDraft(updateMultiFilterValue(draft, option.value, next))}\n />\n </div>\n );\n })}\n {visibleOptions.length === 0 && (\n <div className=\"px-2 py-3 text-sm text-muted-foreground\">No options found</div>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const agentActionIcons = require("./agent-action-icons.cjs");
3
4
  const UiClose = require("../../icons/components/UiClose.cjs");
4
5
  const UiHourglass = require("../../icons/components/UiHourglass.cjs");
5
6
  const UiShare = require("../../icons/components/UiShare.cjs");
@@ -55,7 +56,7 @@ function parseEntries(input) {
55
56
  });
56
57
  }
57
58
  function roleFromType(type) {
58
- return type === "system" || type === "user" || type === "assistant" ? type : "assistant";
59
+ return type === "system" || type === "user" || type === "assistant" || type === "verified" || type === "verify_failed" ? type : "assistant";
59
60
  }
60
61
  function toolEvent(id, tool, input, response, timestamp, meta = {}) {
61
62
  return {
@@ -80,7 +81,7 @@ function toolEvent(id, tool, input, response, timestamp, meta = {}) {
80
81
  ...meta.raw !== void 0 ? { raw: meta.raw } : {}
81
82
  };
82
83
  }
83
- function blockEvent(block, role, id, timestamp, cwd) {
84
+ function blockEvent(block, role, id, timestamp, cwd, verifyPassed) {
84
85
  if (block.type === "tool_use" || block.name && block.input) {
85
86
  if (!block.name) return null;
86
87
  return toolEvent(id, block.name, block.input, void 0, timestamp, {
@@ -95,9 +96,14 @@ function blockEvent(block, role, id, timestamp, cwd) {
95
96
  if (block.text) {
96
97
  return {
97
98
  id,
98
- kind: role === "system" ? "system" : role === "user" ? "user" : "assistant",
99
+ kind: role === "system" || verifyPassed !== void 0 ? "system" : role === "user" ? "user" : "assistant",
99
100
  text: block.text,
100
- ...timestamp ? { timestamp } : {}
101
+ ...timestamp ? { timestamp } : {},
102
+ ...verifyPassed !== void 0 ? {
103
+ verifyPassed,
104
+ tone: verifyPassed ? "emerald" : "rose",
105
+ icon: agentActionIcons.WORKFLOW_PHASES.verify.icon
106
+ } : {}
101
107
  };
102
108
  }
103
109
  return null;
@@ -150,13 +156,15 @@ function normalizeSession(input) {
150
156
  return;
151
157
  }
152
158
  const role = roleFromType(((_a = entry.message) == null ? void 0 : _a.role) ?? entry.type);
159
+ const verifyPassed = role === "verified" ? true : role === "verify_failed" ? false : void 0;
153
160
  for (const [i, block] of (((_b = entry.message) == null ? void 0 : _b.content) ?? []).entries()) {
154
161
  const ev = blockEvent(
155
162
  block,
156
163
  role,
157
164
  `${baseId}-${i}`,
158
165
  entry.timestamp,
159
- entry.cwd
166
+ entry.cwd,
167
+ verifyPassed
160
168
  );
161
169
  if (ev) events.push(ev);
162
170
  }
@@ -248,7 +256,12 @@ function partEvent(part, role, id, meta) {
248
256
  ...meta.timestamp ? { timestamp: meta.timestamp } : {},
249
257
  ...meta.turnId ? { turnId: meta.turnId } : {},
250
258
  ...meta.agentId ? { agentId: meta.agentId } : {},
251
- ...meta.raw !== void 0 ? { raw: meta.raw } : {}
259
+ ...meta.raw !== void 0 ? { raw: meta.raw } : {},
260
+ ...meta.verifyPassed !== void 0 ? {
261
+ verifyPassed: meta.verifyPassed,
262
+ tone: meta.verifyPassed ? "emerald" : "rose",
263
+ icon: agentActionIcons.WORKFLOW_PHASES.verify.icon
264
+ } : {}
252
265
  } : null;
253
266
  }
254
267
  return null;
@@ -280,7 +293,8 @@ function normalizeMessages(messages) {
280
293
  });
281
294
  return;
282
295
  }
283
- const role = msg.role === "system" ? "system" : msg.role === "user" ? "user" : "assistant";
296
+ const verifyPassed = msg.role === "verified" ? true : msg.role === "verify_failed" ? false : void 0;
297
+ const role = msg.role === "system" || verifyPassed !== void 0 ? "system" : msg.role === "user" ? "user" : "assistant";
284
298
  const meta = {
285
299
  ...(prov == null ? void 0 : prov.timestamp) ? { timestamp: prov.timestamp } : {},
286
300
  ...(prov == null ? void 0 : prov.cwd) ? { cwd: prov.cwd } : {},
@@ -290,7 +304,8 @@ function normalizeMessages(messages) {
290
304
  ...msg.turnId ? { turnId: msg.turnId } : {},
291
305
  ...(prov == null ? void 0 : prov.agentId) ? { agentId: prov.agentId } : {},
292
306
  ...(prov == null ? void 0 : prov.sessionId) ? { sessionId: prov.sessionId } : {},
293
- ...msg.raw !== void 0 ? { raw: msg.raw } : {}
307
+ ...msg.raw !== void 0 ? { raw: msg.raw } : {},
308
+ ...verifyPassed !== void 0 ? { verifyPassed } : {}
294
309
  };
295
310
  msg.parts.forEach((part, i) => {
296
311
  const ev = partEvent(part, role, `${baseId}-${i}`, meta);