@persona/design-system 0.10.0 → 0.10.2

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 (267) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/AdvancedFilterRuleRow.d.mts +2 -2
  3. package/dist/AdvancedFilterRulesPopover.d.mts +2 -2
  4. package/dist/ArrayValueControl.d.mts +1 -1
  5. package/dist/BooleanValueControl.d.mts +1 -1
  6. package/dist/{Calendar-DAfQCLOJ.d.mts → Calendar-CkjYG7Mj.d.mts} +1 -1
  7. package/dist/Calendar.d.mts +1 -1
  8. package/dist/{CheckboxGroupField-BYB4OBlW.d.mts → CheckboxGroupField-BrPGNIyD.d.mts} +2 -2
  9. package/dist/CheckboxGroupField.d.mts +1 -1
  10. package/dist/ChoicesValueControl.d.mts +1 -1
  11. package/dist/{Combobox-CcFJo68g.mjs → Combobox-BXHCfVpE.mjs} +2 -2
  12. package/dist/{Combobox-CcFJo68g.mjs.map → Combobox-BXHCfVpE.mjs.map} +1 -1
  13. package/dist/{Combobox-DH5ycYlG.d.mts → Combobox-D0dMN5eY.d.mts} +3 -3
  14. package/dist/Combobox.d.mts +2 -2
  15. package/dist/{ComboboxBase-BQQSKUBU.d.mts → ComboboxBase-BlNi9OnF.d.mts} +2 -2
  16. package/dist/ComboboxBase.d.mts +1 -1
  17. package/dist/{ComboboxMultiSelect-Da3Gv1k5.d.mts → ComboboxMultiSelect-DbTYO-Yz.d.mts} +2 -2
  18. package/dist/ComboboxMultiSelect.d.mts +1 -1
  19. package/dist/{ComboboxSingleSelect-BUDovONU.d.mts → ComboboxSingleSelect-Cd6VYdwr.d.mts} +3 -3
  20. package/dist/ComboboxSingleSelect.d.mts +1 -1
  21. package/dist/{CompactBar-D12R7Nm2.d.mts → CompactBar-CfrrZ69k.d.mts} +1 -1
  22. package/dist/CompactBar.d.mts +1 -1
  23. package/dist/{ConfigFormGroup-CidT_cE9.mjs → ConfigFormGroup-BE3yR_VU.mjs} +1 -1
  24. package/dist/{ConfigFormGroup-CidT_cE9.mjs.map → ConfigFormGroup-BE3yR_VU.mjs.map} +1 -1
  25. package/dist/{DataTable-fZ7SNbIH.d.mts → DataTable-BdKUCgcZ.d.mts} +7 -5
  26. package/dist/{DataTable-95ZxiPlV.mjs → DataTable-F9R-aQCg.mjs} +8 -8
  27. package/dist/DataTable-F9R-aQCg.mjs.map +1 -0
  28. package/dist/DataTable.d.mts +1 -1
  29. package/dist/{DateField-DLz13Ksl.d.mts → DateField-CwSq77Ri.d.mts} +1 -1
  30. package/dist/DateField.d.mts +1 -1
  31. package/dist/{DatePicker-D0cvVbzq.mjs → DatePicker-CkNQjfCN.mjs} +2 -2
  32. package/dist/{DatePicker-D0cvVbzq.mjs.map → DatePicker-CkNQjfCN.mjs.map} +1 -1
  33. package/dist/{DatePicker-DYVWmbrb.d.mts → DatePicker-DRhY_0st.d.mts} +1 -1
  34. package/dist/DatePicker.d.mts +1 -1
  35. package/dist/{DateRangePicker-Be8TYtjz.d.mts → DateRangePicker-CLuBLF1q.d.mts} +2 -2
  36. package/dist/DateRangePicker.d.mts +1 -1
  37. package/dist/DateRangeValueControl.d.mts +2 -2
  38. package/dist/DateValueControl.d.mts +1 -1
  39. package/dist/DropdownButton.d.mts +1 -1
  40. package/dist/{DropdownMenu-DAa2kNrt.d.mts → DropdownMenu-nxFNS1kH.d.mts} +2 -2
  41. package/dist/DropdownMenu.d.mts +1 -1
  42. package/dist/{FieldRegistry-d5tGpSch.d.mts → FieldRegistry-B5E-GN91.d.mts} +2 -2
  43. package/dist/FieldRegistry.d.mts +1 -1
  44. package/dist/{FilterBar-Bh18VyOK.d.mts → FilterBar-BQWpTi2b.d.mts} +4 -4
  45. package/dist/FilterBar.d.mts +1 -1
  46. package/dist/FilterPickerMenu.d.mts +2 -2
  47. package/dist/FilterPill.d.mts +1 -1
  48. package/dist/FilterPopover.d.mts +1 -1
  49. package/dist/FloatValueControl.d.mts +1 -1
  50. package/dist/{FormField-cSG93kuo.mjs → FormField-D0y1uRV5.mjs} +9 -9
  51. package/dist/{FormField-cSG93kuo.mjs.map → FormField-D0y1uRV5.mjs.map} +1 -1
  52. package/dist/HighlightedText.d.mts +1 -1
  53. package/dist/{HoverCopyableField-D-BubaRd.mjs → HoverCopyableField-DkOosH8z.mjs} +2 -2
  54. package/dist/{HoverCopyableField-D-BubaRd.mjs.map → HoverCopyableField-DkOosH8z.mjs.map} +1 -1
  55. package/dist/ImageThumbnail.d.mts +43 -0
  56. package/dist/IndexTableLayout.d.mts +3 -3
  57. package/dist/InlineCombobox.d.mts +1 -1
  58. package/dist/InlineInput.d.mts +1 -1
  59. package/dist/IntegerValueControl.d.mts +1 -1
  60. package/dist/{Link-Cf__H8gD.mjs → Link-BSuXrvTu.mjs} +1 -1
  61. package/dist/{Link-Cf__H8gD.mjs.map → Link-BSuXrvTu.mjs.map} +1 -1
  62. package/dist/{ListBox-C80J7DCG.mjs → ListBox-BLoJVezh.mjs} +2 -2
  63. package/dist/{ListBox-C80J7DCG.mjs.map → ListBox-BLoJVezh.mjs.map} +1 -1
  64. package/dist/{ListBox-Bj5c626-.d.mts → ListBox-CXFI-0nG.d.mts} +2 -2
  65. package/dist/ListBox.d.mts +1 -1
  66. package/dist/{ListBoxField-DsJRabgE.d.mts → ListBoxField-BCOrph2k.d.mts} +4 -4
  67. package/dist/ListBoxField.d.mts +1 -1
  68. package/dist/{LogicRow-CcpD-Kk3.d.mts → LogicRow-8oYRkwz5.d.mts} +2 -2
  69. package/dist/LogicRow.d.mts +1 -1
  70. package/dist/{Menu-C0FU9WEF.d.mts → Menu-DHnQZ5yN.d.mts} +2 -2
  71. package/dist/Menu.d.mts +1 -1
  72. package/dist/{Modal-kjUptzg1.mjs → Modal-IJdbLORi.mjs} +1 -1
  73. package/dist/{Modal-kjUptzg1.mjs.map → Modal-IJdbLORi.mjs.map} +1 -1
  74. package/dist/{Pagination-DtK-jbEY.mjs → Pagination-C5ANr9M0.mjs} +2 -2
  75. package/dist/{Pagination-DtK-jbEY.mjs.map → Pagination-C5ANr9M0.mjs.map} +1 -1
  76. package/dist/{Pii-DueIK287.mjs → Pii-G16_b7Yk.mjs} +1 -1
  77. package/dist/{Pii-DueIK287.mjs.map → Pii-G16_b7Yk.mjs.map} +1 -1
  78. package/dist/{PillShell-CXROf8gO.d.mts → PillShell-DqglbFHe.d.mts} +2 -2
  79. package/dist/PillShell.d.mts +1 -1
  80. package/dist/{Popover-BcII7usF.mjs → Popover-532iYFek.mjs} +1 -1
  81. package/dist/{Popover-BcII7usF.mjs.map → Popover-532iYFek.mjs.map} +1 -1
  82. package/dist/{Radio-DDT_l8rd.mjs → Radio-BstZg3DI.mjs} +1 -1
  83. package/dist/{Radio-DDT_l8rd.mjs.map → Radio-BstZg3DI.mjs.map} +1 -1
  84. package/dist/{RadioGroup-CmD8NFCL.mjs → RadioGroup-erKerwSt.mjs} +2 -2
  85. package/dist/{RadioGroup-CmD8NFCL.mjs.map → RadioGroup-erKerwSt.mjs.map} +1 -1
  86. package/dist/{RangeCalendar-cZSYjuOZ.d.mts → RangeCalendar-CK5ArbIQ.d.mts} +2 -2
  87. package/dist/RangeCalendar.d.mts +1 -1
  88. package/dist/RangeCalendarPresets.d.mts +1 -1
  89. package/dist/RuleEditorFields.d.mts +2 -2
  90. package/dist/RulesPill.d.mts +1 -1
  91. package/dist/{SegmentedControl-CcKPfxBR.mjs → SegmentedControl-DQjIO6iX.mjs} +1 -1
  92. package/dist/{SegmentedControl-CcKPfxBR.mjs.map → SegmentedControl-DQjIO6iX.mjs.map} +1 -1
  93. package/dist/{Select-sBEBrNgo.d.mts → Select-CSbamhJO.d.mts} +2 -2
  94. package/dist/{Select-BJ9ACmRD.mjs → Select-hnXfPywt.mjs} +1 -1
  95. package/dist/{Select-BJ9ACmRD.mjs.map → Select-hnXfPywt.mjs.map} +1 -1
  96. package/dist/Select.d.mts +1 -1
  97. package/dist/SelectionCard.d.mts +51 -0
  98. package/dist/SelectionCardGroup.d.mts +37 -0
  99. package/dist/SmartTextInput.d.mts +22 -5
  100. package/dist/SmartTextInputField.d.mts +2 -2
  101. package/dist/SmartTextInputFunctionDialog.d.mts +2 -2
  102. package/dist/SmartTextInputListbox.d.mts +2 -2
  103. package/dist/SmartTextInputPopover.d.mts +1 -1
  104. package/dist/{Spinner-uVZyOh1T.mjs → Spinner-AnIk8d9M.mjs} +1 -1
  105. package/dist/{Spinner-uVZyOh1T.mjs.map → Spinner-AnIk8d9M.mjs.map} +1 -1
  106. package/dist/StringValueControl.d.mts +1 -1
  107. package/dist/{Table--o4ZLRbb.mjs → Table-CmuWqmVN.mjs} +2 -2
  108. package/dist/{Table--o4ZLRbb.mjs.map → Table-CmuWqmVN.mjs.map} +1 -1
  109. package/dist/{TableBox-3qunC9Nl.mjs → TableBox-BRJunn09.mjs} +4 -4
  110. package/dist/{TableBox-3qunC9Nl.mjs.map → TableBox-BRJunn09.mjs.map} +1 -1
  111. package/dist/TableBox.d.mts +1 -1
  112. package/dist/{TableBoxCell-BRECnVTs.d.mts → TableBoxCell-Cag3ZYRs.d.mts} +4 -4
  113. package/dist/TableBoxCell.d.mts +1 -1
  114. package/dist/{TableBoxField-Lm_tn73M.d.mts → TableBoxField-C3Cg6Lv5.d.mts} +5 -5
  115. package/dist/TableBoxField.d.mts +1 -1
  116. package/dist/{TableBoxRow-BS8Dc-zw.d.mts → TableBoxRow-y5ay5lMt.d.mts} +2 -2
  117. package/dist/TableBoxRow.d.mts +1 -1
  118. package/dist/{TableBoxRowActions-oLMb8XcM.d.mts → TableBoxRowActions-CDdVjAPQ.d.mts} +2 -2
  119. package/dist/TableBoxRowActions.d.mts +1 -1
  120. package/dist/{TableControls-DxSFqRwJ.mjs → TableControls-C5fNNdmT.mjs} +1359 -1308
  121. package/dist/TableControls-C5fNNdmT.mjs.map +1 -0
  122. package/dist/{TableControls-UfObf-ZN.d.mts → TableControls-DCtTiWk6.d.mts} +10 -5
  123. package/dist/TableControls.d.mts +2 -2
  124. package/dist/{TableControlsContext-Bhtb0cMs.d.mts → TableControlsContext-CzJCMrSe.d.mts} +2 -2
  125. package/dist/TableControlsContext.d.mts +1 -1
  126. package/dist/{TableViewConfigurationDropdown-BDXe2Q1Y.d.mts → TableViewConfigurationDropdown-BvOlDkJy.d.mts} +2 -2
  127. package/dist/TableViewConfigurationDropdown.d.mts +1 -1
  128. package/dist/{TextArea-CFf0Lspu.mjs → TextArea-CaIEBz8d.mjs} +1 -1
  129. package/dist/{TextArea-CFf0Lspu.mjs.map → TextArea-CaIEBz8d.mjs.map} +1 -1
  130. package/dist/{Toggle-BLdemkh_.mjs → Toggle-BsqSNYok.mjs} +1 -1
  131. package/dist/{Toggle-BLdemkh_.mjs.map → Toggle-BsqSNYok.mjs.map} +1 -1
  132. package/dist/{chainTypes-BbFZn8a9.d.mts → chainTypes-Cjp0zZ4E.d.mts} +3 -3
  133. package/dist/{clientFilterUtils-c5xzvoBj.d.mts → clientFilterUtils-BqDn44aM.d.mts} +2 -2
  134. package/dist/clientFilterUtils.d.mts +1 -1
  135. package/dist/{codec-DIp7dhcN.d.mts → codec-KAvQRjTW.d.mts} +2 -2
  136. package/dist/codec.d.mts +1 -1
  137. package/dist/components/Combobox/index.d.mts +2 -2
  138. package/dist/components/Combobox/index.mjs +1 -1
  139. package/dist/components/ConfigFormGroup/index.mjs +1 -1
  140. package/dist/components/DataTable/TableControls/index.d.mts +21 -21
  141. package/dist/components/DataTable/TableControls/index.mjs +1 -1
  142. package/dist/components/DataTable/index.d.mts +5 -5
  143. package/dist/components/DataTable/index.mjs +1 -1
  144. package/dist/components/DataTable/types.d.mts +1 -1
  145. package/dist/components/DatePicker/index.d.mts +5 -5
  146. package/dist/components/DatePicker/index.mjs +1 -1
  147. package/dist/components/DropdownMenu/index.d.mts +1 -1
  148. package/dist/components/FormField/index.d.mts +4 -4
  149. package/dist/components/FormField/index.mjs +1 -1
  150. package/dist/components/HoverCopyableField/index.mjs +1 -1
  151. package/dist/components/ImageTile/index.mjs +82 -52
  152. package/dist/components/ImageTile/index.mjs.map +1 -1
  153. package/dist/components/Link/index.mjs +1 -1
  154. package/dist/components/ListBox/index.d.mts +3 -3
  155. package/dist/components/ListBox/index.mjs +1 -1
  156. package/dist/components/Menu/index.d.mts +1 -1
  157. package/dist/components/Modal/index.mjs +1 -1
  158. package/dist/components/Pagination/index.mjs +1 -1
  159. package/dist/components/Pii/index.mjs +1 -1
  160. package/dist/components/Popover/index.mjs +1 -1
  161. package/dist/components/Radio/index.mjs +1 -1
  162. package/dist/components/RadioGroup/index.mjs +1 -1
  163. package/dist/components/SegmentedControl/index.mjs +1 -1
  164. package/dist/components/Select/index.d.mts +1 -1
  165. package/dist/components/Select/index.mjs +1 -1
  166. package/dist/components/SelectionCard/index.d.mts +3 -0
  167. package/dist/components/SelectionCard/index.mjs +264 -0
  168. package/dist/components/SelectionCard/index.mjs.map +1 -0
  169. package/dist/components/SmartTextInput/index.d.mts +2 -2
  170. package/dist/components/SmartTextInput/index.mjs +12 -3
  171. package/dist/components/SmartTextInput/index.mjs.map +1 -1
  172. package/dist/components/Spinner/index.mjs +1 -1
  173. package/dist/components/Table/index.mjs +1 -1
  174. package/dist/components/TableBox/index.d.mts +1 -1
  175. package/dist/components/TableBox/index.mjs +1 -1
  176. package/dist/components/TextArea/index.mjs +1 -1
  177. package/dist/components/Toggle/index.mjs +1 -1
  178. package/dist/components/VersionHistory/index.mjs +3 -3
  179. package/dist/compound-expression.d.mts +1 -1
  180. package/dist/{date-range-rule-value-D01nn054.d.mts → date-range-rule-value-Dk6pZHnD.d.mts} +2 -2
  181. package/dist/date-range-rule-value.d.mts +1 -1
  182. package/dist/{date-rule-intent-B4hQ_APa.d.mts → date-rule-intent-C5C8HB1v.d.mts} +3 -3
  183. package/dist/date-rule-intent.d.mts +1 -1
  184. package/dist/display.d.mts +1 -1
  185. package/dist/fieldPickerOptions.d.mts +2 -2
  186. package/dist/{filter-size-context-Dj7Idc8j.d.mts → filter-size-context-5xQ3ycnC.d.mts} +2 -2
  187. package/dist/filter-size-context.d.mts +1 -1
  188. package/dist/{filters-DDrsgRYy.d.mts → filters-jG9Y3UKb.d.mts} +2 -2
  189. package/dist/filters.d.mts +1 -1
  190. package/dist/{helpers-D8_2I4ZX.d.mts → helpers-DCIto6aY.d.mts} +2 -2
  191. package/dist/helpers.d.mts +1 -1
  192. package/dist/{index-DsjvDLF8.d.mts → index-C3uFHTo0.d.mts} +3 -3
  193. package/dist/{index-DkXly4xw.d.mts → index-DTAcGzW4.d.mts} +2 -2
  194. package/dist/index3.d.mts +3 -3
  195. package/dist/index4.d.mts +1 -1
  196. package/dist/index5.d.mts +14 -14
  197. package/dist/{inputFields-DyOZHse2.d.mts → inputFields-mUsms2tO.d.mts} +4 -4
  198. package/dist/inputFields.d.mts +1 -1
  199. package/dist/{jsonSchema-COo0XQLA.d.mts → jsonSchema-DmOA0Lnu.d.mts} +2 -2
  200. package/dist/lib/PDSProvider.d.mts +8 -0
  201. package/dist/lib/PDSProvider.mjs +15 -11
  202. package/dist/lib/PDSProvider.mjs.map +1 -1
  203. package/dist/lib/chainTypes.d.mts +1 -1
  204. package/dist/lib/colorMode.d.mts +30 -0
  205. package/dist/lib/colorMode.mjs +71 -0
  206. package/dist/lib/colorMode.mjs.map +1 -0
  207. package/dist/lib/customFunctionSchema.d.mts +1 -1
  208. package/dist/lib/date-filter/index.d.mts +2 -2
  209. package/dist/lib/jsonSchema.d.mts +1 -1
  210. package/dist/lib/jsonSchema.mjs.map +1 -1
  211. package/dist/lib/jsonSchemaType.d.mts +1 -1
  212. package/dist/lib/methodChain.d.mts +1 -1
  213. package/dist/lib/objectSchemaTypes.d.mts +1 -1
  214. package/dist/lib/platformObject.d.mts +1 -1
  215. package/dist/lib/schemaOptions.d.mts +2 -2
  216. package/dist/lib/schemaOptions.mjs +20 -2
  217. package/dist/lib/schemaOptions.mjs.map +1 -1
  218. package/dist/lib/useChain.d.mts +1 -1
  219. package/dist/{listbox-options-D-DyLUdi.d.mts → listbox-options-DCmqWWgy.d.mts} +3 -3
  220. package/dist/listbox-options.d.mts +1 -1
  221. package/dist/{methodChain-BAUFBbR9.d.mts → methodChain-DaDvXsHF.d.mts} +2 -2
  222. package/dist/{operators-DjQXVNVI.d.mts → operators-Seez5p9U.d.mts} +2 -2
  223. package/dist/operators.d.mts +1 -1
  224. package/dist/patterns/IndexTableLayout/index.mjs +1 -1
  225. package/dist/patterns/LayoutLoadingShimmer/index.mjs +1 -1
  226. package/dist/patterns/WizardModal/index.mjs +1 -1
  227. package/dist/relative-date-label.d.mts +1 -1
  228. package/dist/{schemaOptions-D536qhSd.d.mts → schemaOptions-BQgLOgtD.d.mts} +8 -3
  229. package/dist/{types-DHTFd-5t.d.mts → types-39QvBZuZ.d.mts} +1 -1
  230. package/dist/{types-Dx8PgO8a.d.mts → types-CrRWZ0Ia.d.mts} +4 -4
  231. package/dist/{types-DFU6M0OL.d.mts → types-DWU0Dmy9.d.mts} +3 -3
  232. package/dist/{types-CjQzH0MM.d.mts → types-DxJyE_U8.d.mts} +2 -2
  233. package/dist/{types-BMpEizJP.d.mts → types-EodOhsjw.d.mts} +2 -2
  234. package/dist/{types-BhHdS6QP.d.mts → types-SOkNtcBq.d.mts} +2 -2
  235. package/dist/types3.d.mts +1 -1
  236. package/dist/types4.d.mts +2 -2
  237. package/dist/types5.d.mts +2 -2
  238. package/dist/types6.d.mts +1 -1
  239. package/dist/types7.d.mts +1 -1
  240. package/dist/{url-state-D1muieS7.d.mts → url-state-CwaRVyXR.d.mts} +2 -2
  241. package/dist/url-state.d.mts +1 -1
  242. package/dist/{useChain-D5LvupTW.d.mts → useChain-DXkKKe-J.d.mts} +4 -4
  243. package/dist/useChainAtCursor.d.mts +4 -4
  244. package/dist/useCodeChainFunctionPanel.d.mts +1 -1
  245. package/dist/useColumnConfiguration.d.mts +2 -2
  246. package/dist/useComboboxFooterAction.d.mts +1 -1
  247. package/dist/{useFilterStateV2-MH3zbQFr.d.mts → useFilterStateV2-Bzo27PR-.d.mts} +3 -3
  248. package/dist/useFilterStateV2.d.mts +1 -1
  249. package/dist/{useTableColumns-rLskKJhd.d.mts → useTableColumns-DSXRInPb.d.mts} +2 -2
  250. package/dist/useTableColumns.d.mts +1 -1
  251. package/dist/useTruncation.d.mts +1 -1
  252. package/dist/utils.d.mts +1 -1
  253. package/dist/utils4.d.mts +2 -2
  254. package/dist/{validation-Csi2SJxm.d.mts → validation-D6NPueNk.d.mts} +3 -3
  255. package/dist/validation.d.mts +1 -1
  256. package/dist/validation2.d.mts +3 -3
  257. package/dist/{value-control-resolver-BoBFvQ_0.d.mts → value-control-resolver-C-UrXVlB.d.mts} +2 -2
  258. package/dist/value-control-resolver.d.mts +1 -1
  259. package/llms.txt +9 -1
  260. package/package.json +5 -1
  261. package/skills/ui-authoring/COMPONENTS.md +2 -1
  262. package/skills/ui-authoring/PATTERNS.md +5 -1
  263. package/skills/ui-authoring/TOKENS.md +10 -0
  264. package/dist/DataTable-95ZxiPlV.mjs.map +0 -1
  265. package/dist/TableControls-DxSFqRwJ.mjs.map +0 -1
  266. /package/dist/{index-BYd3Btr1.d.mts → index-DztUdPP_.d.mts} +0 -0
  267. /package/dist/{index-IJ_PXwxI2.d.mts → index-uMNhmxEh.d.mts} +0 -0
@@ -23,15 +23,15 @@ import { r as InputGroup } from "./Input-Dap055xf.mjs";
23
23
  import Input_default from "./components/Input/index.mjs";
24
24
  import { c as MenuList, s as MenuItem } from "./Menu-Dw6Rcpk6.mjs";
25
25
  import { n as DropdownMenu_default } from "./DropdownMenu-CedsmO5h.mjs";
26
- import { t as Combobox } from "./Combobox-CcFJo68g.mjs";
27
- import { n as Select, r as SelectHeader, t as Option } from "./Select-BJ9ACmRD.mjs";
28
- import { s as Popover_default } from "./Popover-BcII7usF.mjs";
29
- import { i as Modal_default } from "./Modal-kjUptzg1.mjs";
30
- import { r as DateRangePicker, t as DatePicker } from "./DatePicker-D0cvVbzq.mjs";
31
- import { t as Toggle } from "./Toggle-BLdemkh_.mjs";
26
+ import { n as Select, r as SelectHeader, t as Option } from "./Select-hnXfPywt.mjs";
27
+ import { s as Popover_default } from "./Popover-532iYFek.mjs";
28
+ import { i as Modal_default } from "./Modal-IJdbLORi.mjs";
29
+ import { t as Combobox } from "./Combobox-BXHCfVpE.mjs";
30
+ import { r as DateRangePicker, t as DatePicker } from "./DatePicker-CkNQjfCN.mjs";
31
+ import { t as ListBox } from "./ListBox-BLoJVezh.mjs";
32
32
  import { i as selectedPresetFor, n as parseDateFilterValue, r as resolveDateFilterValue, t as formatDateFilterValue } from "./date-filter-D0ys8s0D.mjs";
33
- import { t as ListBox } from "./ListBox-C80J7DCG.mjs";
34
- import { m as FormField, o as ComboboxField } from "./FormField-cSG93kuo.mjs";
33
+ import { m as FormField, o as ComboboxField } from "./FormField-D0y1uRV5.mjs";
34
+ import { t as Toggle } from "./Toggle-BsqSNYok.mjs";
35
35
  import { BorderRadius, Color, Spacing, Typography } from "@persona/design-tokens";
36
36
  import { Fragment, createContext, createElement, forwardRef, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
37
37
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -41,14 +41,14 @@ import { CaretDownIcon as CaretDownIcon$1, CircleInfoIcon as CircleInfoIcon$1, L
41
41
  import { debounce, isEqual, noop, partition, startCase, union, uniqueId } from "lodash-es";
42
42
  import { useId } from "react-aria";
43
43
  import { mergeRefs } from "@react-aria/utils";
44
- import Fuse from "fuse.js";
45
44
  import { DragIcon as DragIcon$1 } from "@persona/icons/24px";
46
45
  import * as PopoverPrimitive from "@radix-ui/react-popover";
47
46
  import { DndContext, PointerSensor, closestCenter, pointerWithin, useSensor, useSensors } from "@dnd-kit/core";
48
47
  import { SortableContext, horizontalListSortingStrategy, useSortable } from "@dnd-kit/sortable";
48
+ import Fuse from "fuse.js";
49
49
  import { parseAbsolute, parseDate, toCalendarDate, toZoned } from "@internationalized/date";
50
- import { DragDropContext, Draggable, Droppable } from "@hello-pangea/dnd";
51
50
  import { v4 } from "uuid";
51
+ import { DragDropContext, Draggable, Droppable } from "@hello-pangea/dnd";
52
52
  //#region components/DataTable/FormControlLabel/FormControlLabel.ts
53
53
  const FormControlLabel = styled(Label$1).attrs(() => ({
54
54
  size: "md",
@@ -696,13 +696,36 @@ const ControlBar = ({ presetViews, currentView, onSelectView, views, hiddenViewI
696
696
  const allViews = views ?? [];
697
697
  return [allViews.filter((view) => !hiddenIds.has(view.id)), allViews.filter((view) => hiddenIds.has(view.id))];
698
698
  }, [views, hiddenViewIds]);
699
+ const shownViewsKey = shownViews.map((view) => view.id).join("\n");
700
+ const [selectedSlot, setSelectedSlot] = useState(null);
701
+ const selectedSlotIndex = selectedSlot != null && selectedSlot.viewId === currentView && selectedSlot.shownViewsKey === shownViewsKey ? selectedSlot.index : null;
702
+ const stripViews = useMemo(() => {
703
+ const selectedView = shownViews.find((view) => view.id === currentView);
704
+ if (selectedSlotIndex == null || !selectedView) return shownViews;
705
+ const otherViews = shownViews.filter((view) => view !== selectedView);
706
+ return [
707
+ ...otherViews.slice(0, selectedSlotIndex),
708
+ selectedView,
709
+ ...otherViews.slice(selectedSlotIndex)
710
+ ];
711
+ }, [
712
+ shownViews,
713
+ currentView,
714
+ selectedSlotIndex
715
+ ]);
699
716
  const onResize = useCallback(() => {
700
717
  if (!tabListRef.current) return;
701
718
  const containerRect = tabListRef.current.getBoundingClientRect();
702
719
  const tabListContainer = tabListRef.current;
703
720
  const firstOverflowIndex = Array.from(tabListContainer.querySelectorAll("[data-tab-view]")).findIndex((el) => !rectContainsRect(containerRect, el.getBoundingClientRect()));
704
- const nextOverflowViews = firstOverflowIndex === -1 ? [] : shownViews.slice(firstOverflowIndex);
721
+ const nextOverflowViews = firstOverflowIndex === -1 ? [] : stripViews.slice(firstOverflowIndex);
705
722
  setOverflowViews((prev) => prev.length === nextOverflowViews.length && prev.every((view, index) => view === nextOverflowViews[index]) ? prev : nextOverflowViews);
723
+ const selectedIndex = stripViews.findIndex((view) => view.id === currentView);
724
+ if (onReorderViews && currentView && firstOverflowIndex > 0 && selectedIndex >= firstOverflowIndex) setSelectedSlot({
725
+ viewId: currentView,
726
+ shownViewsKey,
727
+ index: firstOverflowIndex - 1
728
+ });
706
729
  const firstRowElements = Array.from(tabListContainer.children).filter((el) => el instanceof HTMLElement && el !== tabOverflowRef.current && rectContainsRect(containerRect, el.getBoundingClientRect()));
707
730
  const anchor = firstRowElements[firstRowElements.length - 1];
708
731
  setOverflowLeft(anchor ? anchor.offsetLeft + anchor.offsetWidth + OVERFLOW_GAP : null);
@@ -710,7 +733,12 @@ const ControlBar = ({ presetViews, currentView, onSelectView, views, hiddenViewI
710
733
  setIsAddViewOverflowing(addViewButton instanceof HTMLElement && !firstRowElements.includes(addViewButton));
711
734
  const tabOverflowWidth = tabOverflowRef.current?.getBoundingClientRect().width;
712
735
  if (tabOverflowWidth) setTabOverflowWidth((prev) => prev != null && prev > tabOverflowWidth && prev - tabOverflowWidth < OVERFLOW_WIDTH_SHRINK_THRESHOLD ? prev : tabOverflowWidth);
713
- }, [shownViews]);
736
+ }, [
737
+ currentView,
738
+ onReorderViews,
739
+ shownViewsKey,
740
+ stripViews
741
+ ]);
714
742
  useLayoutEffect(() => {
715
743
  const tabList = tabListRef.current;
716
744
  if (!showViews || !tabList) return;
@@ -735,7 +763,7 @@ const ControlBar = ({ presetViews, currentView, onSelectView, views, hiddenViewI
735
763
  }, [
736
764
  onResize,
737
765
  showViews,
738
- shownViews
766
+ stripViews
739
767
  ]);
740
768
  const isCurrentViewInOverflow = useMemo(() => !!currentView && [...overflowViews, ...hiddenViews].some((view) => view.id === currentView), [
741
769
  currentView,
@@ -781,13 +809,22 @@ const ControlBar = ({ presetViews, currentView, onSelectView, views, hiddenViewI
781
809
  y: 0
782
810
  };
783
811
  }, []);
812
+ const orderedViewIds = useMemo(() => {
813
+ let shownIndex = 0;
814
+ const hiddenIds = new Set(hiddenViews.map((view) => view.id));
815
+ return (views ?? []).map((view) => hiddenIds.has(view.id) ? view.id : stripViews[shownIndex++].id);
816
+ }, [
817
+ views,
818
+ hiddenViews,
819
+ stripViews
820
+ ]);
784
821
  const moveView = (viewId, toIndex) => {
785
822
  if (!views || !onReorderViews) return;
786
- const viewIds = views.map((view) => view.id);
823
+ const viewIds = orderedViewIds;
787
824
  const reordered = moveItemByIndex(viewIds, viewIds.indexOf(viewId), toIndex);
788
825
  if (reordered !== viewIds) onReorderViews(reordered);
789
826
  };
790
- const indexOfView = (viewId) => views?.findIndex((view) => view.id === viewId) ?? -1;
827
+ const indexOfView = (viewId) => orderedViewIds.indexOf(viewId);
791
828
  const openViewFromMenu = (viewId) => {
792
829
  if (hiddenViews.some((view) => view.id === viewId)) onViewHiddenChange?.(viewId, false);
793
830
  moveView(viewId, 0);
@@ -798,7 +835,7 @@ const ControlBar = ({ presetViews, currentView, onSelectView, views, hiddenViewI
798
835
  };
799
836
  const hideView = (viewId) => {
800
837
  const overflowIds = new Set(overflowViews.map((view) => view.id));
801
- const visibleViews = shownViews.filter((view) => !overflowIds.has(view.id));
838
+ const visibleViews = stripViews.filter((view) => !overflowIds.has(view.id));
802
839
  const index = visibleViews.findIndex((view) => view.id === viewId);
803
840
  const isCurrent = viewId === currentView;
804
841
  const focusTarget = isCurrent || index === -1 ? void 0 : visibleViews[index + 1] ?? visibleViews[index - 1];
@@ -807,8 +844,10 @@ const ControlBar = ({ presetViews, currentView, onSelectView, views, hiddenViewI
807
844
  document.getElementById(`${TAB_ID_PREFIX}${focusTarget ? focusTarget.id : "default"}`)?.focus({ preventScroll: true });
808
845
  };
809
846
  const moveViewWithKeyboard = (viewId, direction) => {
810
- const neighbor = shownViews[shownViews.findIndex((view) => view.id === viewId) + direction];
847
+ const neighbor = stripViews[stripViews.findIndex((view) => view.id === viewId) + direction];
811
848
  if (!neighbor) return;
849
+ const swapIds = [viewId, neighbor.id];
850
+ if (currentView != null && swapIds.includes(currentView) && swapIds.some((id) => overflowViews.some((view) => view.id === id))) return;
812
851
  moveView(viewId, indexOfView(neighbor.id));
813
852
  };
814
853
  const handleAddView = async () => {
@@ -996,10 +1035,10 @@ const ControlBar = ({ presetViews, currentView, onSelectView, views, hiddenViewI
996
1035
  }, view.id);
997
1036
  }),
998
1037
  onReorderViews ? /* @__PURE__ */ jsx(SortableContext, {
999
- items: shownViews.map((view) => view.id),
1038
+ items: stripViews.map((view) => view.id),
1000
1039
  strategy: horizontalListSortingStrategy,
1001
- children: shownViews.map((view) => /* @__PURE__ */ jsx(SortableSavedViewTab, { ...getSavedViewTabProps(view) }, view.id))
1002
- }) : shownViews.map((view) => /* @__PURE__ */ jsx(SavedViewTabSlot, { ...getSavedViewTabProps(view) }, view.id)),
1040
+ children: stripViews.map((view) => /* @__PURE__ */ jsx(SortableSavedViewTab, { ...getSavedViewTabProps(view) }, view.id))
1041
+ }) : stripViews.map((view) => /* @__PURE__ */ jsx(SavedViewTabSlot, { ...getSavedViewTabProps(view) }, view.id)),
1003
1042
  canEditViews && onAddView && /* @__PURE__ */ jsx(AddViewButton, {
1004
1043
  id: ADD_VIEW_BUTTON_ID,
1005
1044
  size: "md",
@@ -1174,7 +1213,7 @@ const FilterCombobox = styled.div`
1174
1213
  `}
1175
1214
  `;
1176
1215
  const InputFilter = ({ config, compact, onActiveChange }) => {
1177
- const [value, setValue] = useState(config.value);
1216
+ const [value, setValue] = useState(config.value ?? "");
1178
1217
  const onChangeRef = useRef(config.onChange);
1179
1218
  onChangeRef.current = config.onChange;
1180
1219
  const onActiveChangeRef = useRef(onActiveChange);
@@ -1183,13 +1222,13 @@ const InputFilter = ({ config, compact, onActiveChange }) => {
1183
1222
  onActiveChangeRef.current(nextValue.length > 0);
1184
1223
  onChangeRef.current(nextValue);
1185
1224
  }, 300), []);
1186
- useEffect(() => setValue(config.value), [config.value]);
1225
+ useEffect(() => setValue(config.value ?? ""), [config.value]);
1187
1226
  useEffect(() => () => debouncedOnChange.cancel(), [debouncedOnChange]);
1188
1227
  return /* @__PURE__ */ jsx(Input_default, {
1189
1228
  "aria-label": config.name,
1190
1229
  placeholder: config.placeholder,
1191
1230
  size: compact ? "sm" : void 0,
1192
- value,
1231
+ value: value ?? "",
1193
1232
  onChange: ({ target: { value: nextValue } }) => {
1194
1233
  setValue(nextValue);
1195
1234
  debouncedOnChange(nextValue);
@@ -1201,7 +1240,7 @@ const SelectFilter = ({ config, compact, onActiveChange }) => {
1201
1240
  return /* @__PURE__ */ jsx(Select, {
1202
1241
  "aria-label": config.name,
1203
1242
  placeholder: config.placeholder ?? strings.selectFilterOption,
1204
- value: config.value,
1243
+ value: config.value ?? null,
1205
1244
  width: "auto",
1206
1245
  minWidth: 150,
1207
1246
  size: compact ? "sm" : void 0,
@@ -1382,7 +1421,7 @@ const synchronizeActiveFilters = (filters, activeFilters, filterResolver) => {
1382
1421
  ...activeFromConfig,
1383
1422
  ...retainedDefaultVisible,
1384
1423
  ...configuring
1385
- ].sort((first, second) => first.id - second.id);
1424
+ ].filter((active, index, all) => all.findIndex((other) => other.id === active.id) === index).sort((first, second) => first.id - second.id);
1386
1425
  return next.length === 0 ? [createUnsetFilter(activeFilters)] : next;
1387
1426
  };
1388
1427
  const Filter = ({ config, compact, hideFilterSelection, filterResolver }) => {
@@ -1452,7 +1491,7 @@ const FilterSelection = ({ value, disabled, compact, filterResolver }) => {
1452
1491
  width: "auto",
1453
1492
  minWidth: 120,
1454
1493
  placeholder: strings.selectFilterType,
1455
- value,
1494
+ value: value ?? null,
1456
1495
  size: compact ? "sm" : void 0,
1457
1496
  disabled,
1458
1497
  onValueChange: replaceFilter,
@@ -1507,598 +1546,155 @@ const Filters = ({ compact, filterResolver, ...props }) => {
1507
1546
  });
1508
1547
  };
1509
1548
  //#endregion
1510
- //#region components/DataTable/dnd/StyledIconDrag.tsx
1511
- const StyledIconDrag = styled(DragIcon$1)`
1512
- display: ${({ $shouldHide = false }) => $shouldHide ? "none" : "inline"};
1513
-
1514
- ${({ $horizontal = false }) => $horizontal ? css`
1515
- transform: rotate(90deg);
1516
- ` : ""}
1517
-
1518
- ${({ $active = false, $hover = false }) => $active ? css`
1519
- cursor: grabbing;
1520
- opacity: 1;
1521
- ` : $hover ? css`
1522
- cursor: grab;
1523
- opacity: 1;
1524
- ` : css`
1525
- cursor: normal;
1526
- opacity: 0.4;
1527
- `}
1528
-
1529
- transition: opacity 0.2s ease-in-out;
1530
-
1531
- path {
1532
- stroke: ${({ theme }) => semanticColor(theme, "FgPrimary", Color.CoreSlate1000)};
1533
- stroke-width: 1px;
1534
- }
1535
- `;
1536
- //#endregion
1537
- //#region components/DataTable/dnd/utils.ts
1549
+ //#region components/DataTable/TableControls/FiltersV2/operators.ts
1538
1550
  /**
1539
- * reorder returns a reordered version of the inputed list by swapping the element
1540
- * at startIndex with the one at endIndex
1551
+ * Operator vocabulary per field type. The UI uses this to populate the
1552
+ * operator dropdown in the Filter popover and to gate which operators are
1553
+ * reachable for a given field. `is_empty` / `is_not_empty` apply broadly;
1554
+ * everything else is type-specific.
1555
+ *
1556
+ * The first entry for each type is the default operator selected when a
1557
+ * pill is created from the picker.
1541
1558
  */
1542
- function reorder(list, startIndex, endIndex) {
1543
- const result = Array.from(list);
1544
- const [removed] = result.splice(startIndex, 1);
1545
- result.splice(endIndex, 0, removed);
1546
- return result;
1559
+ const OPERATORS_BY_TYPE = {
1560
+ string: [
1561
+ "is",
1562
+ "is_not",
1563
+ "contains",
1564
+ "does_not_contain",
1565
+ "is_empty",
1566
+ "is_not_empty"
1567
+ ],
1568
+ email: [
1569
+ "is",
1570
+ "is_not",
1571
+ "contains",
1572
+ "does_not_contain",
1573
+ "is_empty",
1574
+ "is_not_empty"
1575
+ ],
1576
+ choices: [
1577
+ "is",
1578
+ "is_not",
1579
+ "is_empty",
1580
+ "is_not_empty"
1581
+ ],
1582
+ multi_choices: [
1583
+ "is",
1584
+ "is_not",
1585
+ "is_empty",
1586
+ "is_not_empty"
1587
+ ],
1588
+ boolean: ["is"],
1589
+ integer: [
1590
+ "is",
1591
+ "is_not",
1592
+ "is_empty",
1593
+ "is_not_empty"
1594
+ ],
1595
+ float: [
1596
+ "is",
1597
+ "is_not",
1598
+ "is_empty",
1599
+ "is_not_empty"
1600
+ ],
1601
+ date: [
1602
+ "is",
1603
+ "before",
1604
+ "after",
1605
+ "between",
1606
+ "is_empty",
1607
+ "is_not_empty"
1608
+ ],
1609
+ array: [
1610
+ "contains",
1611
+ "does_not_contain",
1612
+ "is_empty",
1613
+ "is_not_empty"
1614
+ ],
1615
+ tags_conditions: ["is"],
1616
+ signals: ["is"],
1617
+ date_range: ["is"]
1618
+ };
1619
+ /**
1620
+ * Numeric comparison operators. Together with `is` (is equal to) they give
1621
+ * `integer` and `float` fields greater-than / less-than / equal-to filtering.
1622
+ * Inclusive bounds are deliberately not offered: "at least 5" is
1623
+ * `gt 5` OR `is 5`, built with the Rules editor.
1624
+ *
1625
+ * They are opt-in rather than part of the default numeric vocabulary above:
1626
+ * a host whose backend serializes them lists them in the field's
1627
+ * `allowedOperators` (e.g. `['is', ...NUMERIC_COMPARISON_OPERATORS]`).
1628
+ * Adding them to the default vocabulary would surface operators in every
1629
+ * existing numeric field whose host can't serialize them.
1630
+ */
1631
+ const NUMERIC_COMPARISON_OPERATORS = ["gt", "lt"];
1632
+ const NUMERIC_COMPARISON_OPERATOR_SET = new Set(NUMERIC_COMPARISON_OPERATORS);
1633
+ const NUMERIC_FIELD_TYPES = new Set(["integer", "float"]);
1634
+ function isNumericComparisonOperator(operator) {
1635
+ return NUMERIC_COMPARISON_OPERATOR_SET.has(operator);
1547
1636
  }
1548
1637
  /**
1549
- * Translates a drag result into indices into the full list. dnd reports positions among the
1550
- * rendered rows, which diverge from the full list as soon as the view is filtered.
1638
+ * Whether a comparison value is complete for a numeric field: a finite
1639
+ * number, and a safe integer when the field is an `integer`. Strings are
1640
+ * rejected — the numeric value controls always emit numbers, so a string
1641
+ * here is a corrupted or hand-crafted rule the backend can't compare.
1642
+ */
1643
+ function isCompleteNumericComparisonValue(value, fieldType) {
1644
+ if (typeof value !== "number" || !Number.isFinite(value)) return false;
1645
+ return fieldType === "integer" ? Number.isSafeInteger(value) : true;
1646
+ }
1647
+ /**
1648
+ * Carry `value` into a numeric comparison. Equality rules on numeric fields
1649
+ * tolerate strings (v1 URLs hydrate `'5'`), but a comparison is only complete
1650
+ * with a number, so a numeric string is converted — truncated on `integer`
1651
+ * fields, matching what `IntegerValueControl` emits — and anything else
1652
+ * becomes an empty draft rather than a committed rule validation rejects.
1653
+ */
1654
+ function numericComparisonValue(value, fieldType) {
1655
+ const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
1656
+ if (!Number.isFinite(parsed)) return "";
1657
+ const normalized = fieldType === "integer" ? Math.trunc(parsed) : parsed;
1658
+ return isCompleteNumericComparisonValue(normalized, fieldType) ? normalized : "";
1659
+ }
1660
+ /**
1661
+ * Whether `(fieldType, operator)` is a built-in numeric comparison — the
1662
+ * case validators and labels specialize. Comparisons on non-numeric types
1663
+ * (only reachable through `condition`) are not.
1664
+ */
1665
+ function isNumericComparison(operator, fieldType) {
1666
+ return fieldType != null && NUMERIC_FIELD_TYPES.has(fieldType) && isNumericComparisonOperator(operator);
1667
+ }
1668
+ /** Operators that have no value control (the operator itself is the predicate). */
1669
+ const VALUELESS_OPERATORS = new Set(["is_empty", "is_not_empty"]);
1670
+ /**
1671
+ * Operators whose value is a {@link DateRangeFilterValue} rather than the
1672
+ * scalar its field type would otherwise carry. The value control, the draft
1673
+ * seed, and every validator branch on this rather than on field type alone —
1674
+ * a `date` field carries an ISO day string for `is` / `before` / `after` and
1675
+ * a `{ startDate, endDate }` pair for `between`.
1676
+ */
1677
+ const RANGE_OPERATORS = new Set(["between"]);
1678
+ const TIME_COMPONENT$1 = /T\d{2}:\d{2}/;
1679
+ function getOperatorsForType(type) {
1680
+ return OPERATORS_BY_TYPE[type] ?? [];
1681
+ }
1682
+ /**
1683
+ * Effective operator vocabulary for a concrete field: its `allowedOperators`
1684
+ * allow-list, or the field type's full vocabulary when the allow-list is
1685
+ * absent or empty. An empty allow-list means "unconstrained" rather than
1686
+ * "no operators" — the latter would leave the field selectable in the picker
1687
+ * but incapable of producing a rule that passes validation.
1551
1688
  *
1552
- * Returns null when either end cannot be resolved, in which case the drag should be ignored
1553
- * rather than applied at the wrong position.
1689
+ * Prefer this over {@link getOperatorsForType} anywhere a real field is in
1690
+ * hand — the popover's operator dropdown, `FieldRegistry`, validation — so a
1691
+ * field can never surface an operator the host application can't serialize.
1554
1692
  */
1555
- function resolveDragIndices(listKeys, renderedKeys, draggableId, destinationIndex) {
1556
- const destinationKey = renderedKeys[destinationIndex];
1557
- if (destinationKey == null) return null;
1558
- const from = listKeys.indexOf(draggableId);
1559
- const to = listKeys.indexOf(destinationKey);
1560
- return from === -1 || to === -1 ? null : {
1561
- from,
1562
- to
1563
- };
1564
- }
1565
- //#endregion
1566
- //#region components/DataTable/TableControls/TableViewConfigurationDropdown.tsx
1567
- const SHOWN_DROPPABLE_ID = "cases-view__configure-shown-columns";
1568
- const startsCapitalized = (label) => {
1569
- const first = label.charAt(0);
1570
- return first !== "" && first === first.toUpperCase() && first !== first.toLowerCase();
1571
- };
1572
- const byLabel = (a, b) => {
1573
- const aCapitalized = startsCapitalized(a.label);
1574
- if (aCapitalized !== startsCapitalized(b.label)) return aCapitalized ? -1 : 1;
1575
- return a.label.localeCompare(b.label, void 0, { numeric: true });
1576
- };
1577
- const DndSection = styled("div")`
1578
- max-height: 400px;
1579
-
1580
- overflow: scroll;
1581
- ::-webkit-scrollbar {
1582
- display: none;
1583
- }
1584
- `;
1585
- const DndItemWrapper = styled("div")`
1586
- background: white;
1587
- ${semanticOnly((tokens) => `background: ${tokens.SurfacePrimary};`)}
1588
- width: 100%;
1589
- border-bottom: 1px solid ${({ theme }) => semanticColor(theme, "BorderSubtle", theme.color.border.secondary)};
1590
- padding: ${Spacing[2]} ${Spacing[2]};
1591
-
1592
- display: flex;
1593
- flex-direction: row;
1594
- align-items: center;
1595
- justify-content: left;
1596
- `;
1597
- const SuffixContainer = styled("div")`
1598
- padding-left: ${Spacing[2]};
1599
- margin-left: auto;
1600
- align-self: center;
1601
- `;
1602
- const SearchContainer = styled.div`
1603
- display: flex;
1604
- `;
1605
- const StyledSearchInput = styled(SearchInput)`
1606
- width: 100%;
1607
- `;
1608
- const PopoverCard$1 = styled(Card_default)`
1609
- width: 300px;
1610
- padding: ${Spacing[4]};
1611
- `;
1612
- const TriggerIconButton = forwardRef(function TriggerIconButton({ disabled, ...elemProps }, ref) {
1613
- const strings = usePDSStrings();
1614
- return /* @__PURE__ */ jsx(Button, {
1615
- disabled,
1616
- width: "icon",
1617
- variant: "basic",
1618
- size: "md",
1619
- ...elemProps,
1620
- ref,
1621
- "data-tour-target": "table-properties-button",
1622
- "aria-label": strings.tableProperties,
1623
- children: /* @__PURE__ */ jsx(SettingsIcon, {})
1624
- });
1625
- });
1626
- const TooltipButtonWrapper = styled("div")`
1627
- display: inline-block;
1628
- `;
1629
- const TableViewConfigurationDropdown = ({ disabled = false, disabledTooltipMessage, size = "md", variant = "basic", ...props }) => {
1630
- const strings = usePDSStrings();
1631
- return disabled ? /* @__PURE__ */ jsx(Tooltip, {
1632
- trigger: /* @__PURE__ */ jsx(TooltipButtonWrapper, { children: /* @__PURE__ */ jsx(TriggerIconButton, {
1633
- disabled: true,
1634
- size,
1635
- variant
1636
- }) }),
1637
- children: disabledTooltipMessage ?? strings.configureViewPermissionDenied
1638
- }) : /* @__PURE__ */ jsxs(Popover_default, { children: [/* @__PURE__ */ jsx(Popover_default.Trigger, {
1639
- asChild: true,
1640
- children: /* @__PURE__ */ jsx(TriggerIconButton, {
1641
- disabled: false,
1642
- size,
1643
- variant
1644
- })
1645
- }), /* @__PURE__ */ jsx(Popover_default.Content, {
1646
- style: { zIndex: Z_INDICES.dropdown },
1647
- sideOffset: 8,
1648
- side: "bottom",
1649
- align: "end",
1650
- children: /* @__PURE__ */ jsx(PopoverCard$1, {
1651
- elevated: true,
1652
- children: /* @__PURE__ */ jsx(TableViewConfiguration, { ...props })
1653
- })
1654
- })] });
1655
- };
1656
- const TableViewConfiguration = ({ columnConfiguration, attributeOptions, updateColumnConfiguration, getColumnDisabled, sortByRelevance = false }) => {
1657
- const strings = usePDSStrings();
1658
- const [searchTerm, setSearchTerm] = useState("");
1659
- const fuse = useMemo(() => {
1660
- return new Fuse(attributeOptions, sortByRelevance ? {
1661
- shouldSort: true,
1662
- threshold: .3,
1663
- ignoreLocation: true,
1664
- includeScore: true,
1665
- minMatchCharLength: 1,
1666
- keys: [{
1667
- name: "label",
1668
- weight: 1
1669
- }]
1670
- } : {
1671
- shouldSort: false,
1672
- threshold: .4,
1673
- location: 0,
1674
- distance: 100,
1675
- minMatchCharLength: 1,
1676
- keys: [{
1677
- name: "label",
1678
- weight: 1
1679
- }]
1680
- });
1681
- }, [attributeOptions, sortByRelevance]);
1682
- const allOptions = useMemo(() => union(attributeOptions, columnConfiguration), [attributeOptions, columnConfiguration]);
1683
- const filteredSearchOptions = useMemo(() => {
1684
- if (searchTerm === "") return allOptions;
1685
- const results = fuse.search(searchTerm);
1686
- if (!sortByRelevance) return results.map(({ item }) => item);
1687
- return [...results].sort((a, b) => (a.score ?? 0) - (b.score ?? 0) || byLabel(a.item, b.item)).map(({ item }) => item);
1688
- }, [
1689
- allOptions,
1690
- fuse,
1691
- searchTerm,
1692
- sortByRelevance
1693
- ]);
1694
- const hiddenAttributes = useMemo(() => {
1695
- const columnConfigurationKeys = new Set(columnConfiguration.map((c) => c.key));
1696
- const hidden = filteredSearchOptions.filter((attribute) => !columnConfigurationKeys.has(attribute.key));
1697
- if (!sortByRelevance || searchTerm !== "") return hidden;
1698
- const [systemColumns, customFields] = partition(hidden, (a) => !a.isCustomField);
1699
- return [...systemColumns, ...[...customFields].sort(byLabel)];
1700
- }, [
1701
- columnConfiguration,
1702
- filteredSearchOptions,
1703
- sortByRelevance,
1704
- searchTerm
1705
- ]);
1706
- const filteredShownAttributes = useMemo(() => {
1707
- return columnConfiguration.filter((column) => filteredSearchOptions.map((o) => o.key).includes(column.key));
1708
- }, [columnConfiguration, filteredSearchOptions]);
1709
- const handleDragEnd = (result, _provided) => {
1710
- const { destination, source } = result;
1711
- if (!destination || destination.droppableId === source.droppableId && destination.index === source.index) return;
1712
- const indices = resolveDragIndices(columnConfiguration.map((c) => c.key), filteredShownAttributes.map((c) => c.key), result.draggableId, destination.index);
1713
- if (!indices) return;
1714
- updateColumnConfiguration(reorder(columnConfiguration, indices.from, indices.to));
1715
- };
1716
- const generateShownAttributesToggle = (column) => () => {
1717
- updateColumnConfiguration(columnConfiguration.filter((a) => a.key !== column.key));
1718
- };
1719
- const generateHiddenAttributesToggle = (column) => () => {
1720
- updateColumnConfiguration(columnConfiguration.concat({
1721
- key: column.key,
1722
- label: column.label
1723
- }));
1724
- };
1725
- const shownColumnIds = useMemo(() => {
1726
- return columnConfiguration.map((c) => c.key);
1727
- }, [columnConfiguration]);
1728
- const shownDndItemProps = filteredShownAttributes.map((column, index) => {
1729
- const isDisabledByDefault = columnConfiguration.length === 1;
1730
- const isDisabledByCustom = getColumnDisabled?.(column.key, shownColumnIds) ?? false;
1731
- const shouldDisableDragForColumn = isDisabledByCustom && index === filteredShownAttributes.length - 1;
1732
- return {
1733
- column,
1734
- index,
1735
- isDisabled: isDisabledByDefault || isDisabledByCustom,
1736
- isToggled: true,
1737
- shouldDisableDrag: shouldDisableDragForColumn,
1738
- handleToggle: generateShownAttributesToggle(column)
1739
- };
1740
- });
1741
- const hiddenDndItemProps = hiddenAttributes.map((column, index) => {
1742
- return {
1743
- column,
1744
- index,
1745
- isToggled: false,
1746
- shouldDisableDrag: true,
1747
- handleToggle: generateHiddenAttributesToggle(column)
1748
- };
1749
- });
1750
- return /* @__PURE__ */ jsxs("div", { children: [
1751
- /* @__PURE__ */ jsx(Label$1, {
1752
- size: "lg",
1753
- weight: "bold",
1754
- children: strings.configureColumns
1755
- }),
1756
- /* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
1757
- /* @__PURE__ */ jsx(SearchContainer, { children: /* @__PURE__ */ jsx(StyledSearchInput, {
1758
- title: null,
1759
- value: searchTerm,
1760
- handleInput: setSearchTerm,
1761
- size: "md",
1762
- placeholder: strings.searchColumns,
1763
- dataTest: "checklist-item-panel-item-table__search"
1764
- }) }),
1765
- /* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
1766
- shownDndItemProps.length > 0 || hiddenDndItemProps.length > 0 ? /* @__PURE__ */ jsxs(DndSection, { children: [
1767
- shownDndItemProps.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Paragraph, { children: strings.shownColumns }), /* @__PURE__ */ jsx(DragDropContext, {
1768
- onDragEnd: handleDragEnd,
1769
- children: /* @__PURE__ */ jsx(Droppable, {
1770
- droppableId: SHOWN_DROPPABLE_ID,
1771
- renderClone: (provided, _snapshot, rubric) => {
1772
- return /* @__PURE__ */ jsx("div", {
1773
- ...provided.draggableProps,
1774
- ...provided.dragHandleProps,
1775
- ref: provided.innerRef,
1776
- children: /* @__PURE__ */ jsx(AttributeDnd, { ...shownDndItemProps[rubric.source.index] })
1777
- });
1778
- },
1779
- children: (provided) => {
1780
- return /* @__PURE__ */ jsxs("div", {
1781
- ref: provided.innerRef,
1782
- ...provided.droppableProps,
1783
- children: [shownDndItemProps.map((props, idx) => {
1784
- return /* @__PURE__ */ jsx(Draggable, {
1785
- draggableId: props.column.key,
1786
- index: idx,
1787
- isDragDisabled: props.shouldDisableDrag,
1788
- children: (provided) => {
1789
- return /* @__PURE__ */ jsx("div", {
1790
- ref: provided.innerRef,
1791
- ...provided.draggableProps,
1792
- ...provided.dragHandleProps,
1793
- children: /* @__PURE__ */ jsx(AttributeDnd, { ...props })
1794
- });
1795
- }
1796
- }, `${props.column.key}`);
1797
- }), provided.placeholder]
1798
- });
1799
- }
1800
- })
1801
- })] }),
1802
- /* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
1803
- hiddenDndItemProps.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Paragraph, { children: strings.hiddenColumns }), hiddenDndItemProps.map((props) => /* @__PURE__ */ jsx(AttributeDnd, { ...props }, props.column.key))] })
1804
- ] }) : /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Paragraph, { children: strings.noMatchingColumns }) })
1805
- ] });
1806
- };
1807
- const OverflowText = styled("div")`
1808
- max-width: 160px;
1809
- flex-wrap: wrap;
1810
- display: flex;
1811
- `;
1812
- const AttributeDnd = ({ column, index, isDisabled = false, isToggled, shouldDisableDrag, handleToggle }) => {
1813
- const strings = usePDSStrings();
1814
- return /* @__PURE__ */ jsxs(DndItemWrapper, { children: [
1815
- /* @__PURE__ */ jsx(StyledIconDrag, {
1816
- $active: true,
1817
- $shouldHide: shouldDisableDrag
1818
- }),
1819
- /* @__PURE__ */ jsx(OverflowText, { children: /* @__PURE__ */ jsx(Paragraph, {
1820
- color: "primary",
1821
- size: "sm",
1822
- children: column.label
1823
- }) }),
1824
- /* @__PURE__ */ jsx(SuffixContainer, { children: /* @__PURE__ */ jsx(Toggle, {
1825
- value: isToggled,
1826
- disabled: isDisabled,
1827
- onChange: handleToggle,
1828
- "aria-label": isToggled ? strings.hideColumn(column.label) : strings.showColumn(column.label)
1829
- }) })
1830
- ] }, `${column.key}-${index}`);
1831
- };
1832
- //#endregion
1833
- //#region components/DataTable/TableControls/TableControls.tsx
1834
- const EMPTY_FILTERS = [];
1835
- const resolveFilterArea = (filterPanel, configuredFilterPanel) => filterPanel ?? configuredFilterPanel;
1836
- const hasNewFilterId = (currentIds, previousIds) => {
1837
- const previous = new Set(previousIds.split("\0").filter(Boolean));
1838
- return currentIds.split("\0").some((id) => id !== "" && !previous.has(id));
1839
- };
1840
- const TableControls = ({ filters = EMPTY_FILTERS, filterResolver, filterPanel, searchProps, defaultShowFilters, showFilters: controlledShowFilters, setShowFilters: controlledSetShowFilters, className, tableActions, propertiesButton, isLoading, isFilterPanelLoading, showViews, canEditViews, onSelectView, views, hiddenViewIds, currentView, onAddView, onViewNameChange, onReorderViews, onViewHiddenChange, onDuplicateView, onDeleteView, onSaveView, onSetDefaultView, onUnsetDefaultView, isViewDirty, selectedRows, bulkActions, onClearSelection, title, presetViews, compact, columnConfigurationProps, showFiltersAlways = false, onError, "data-tour-target": dataTourTarget }) => {
1841
- const filtersId = useId();
1842
- const enabledFilters = useMemo(() => filters.filter((filter) => filter.enabled !== false), [filters]);
1843
- const [activeFilters, setActiveFilters] = useState(() => getActiveFilters(enabledFilters, [], true, filterResolver));
1844
- const resolvedFilterPanel = resolveFilterArea(filterPanel, enabledFilters.length > 0 ? /* @__PURE__ */ jsx(TableControlsProvider, {
1845
- filters: enabledFilters,
1846
- activeFilters,
1847
- setActiveFilters,
1848
- children: /* @__PURE__ */ jsx(Filters, {
1849
- id: filtersId,
1850
- compact,
1851
- filterResolver
1852
- })
1853
- }) : void 0);
1854
- const [internalShowFilters, internalSetShowFilters] = useState(defaultShowFilters ?? (filterPanel == null && activeFilters.length > 0));
1855
- useEffect(() => {
1856
- setActiveFilters((current) => synchronizeActiveFilters(enabledFilters, current, filterResolver));
1857
- }, [enabledFilters, filterResolver]);
1858
- const activeFilterIds = enabledFilters.filter((filter) => isFilterActive(filter, filterResolver)).map((filter) => filter.id).sort().join("\0");
1859
- const configuredFilterIds = activeFilters.flatMap((filter) => filter.config ? [filter.config.id] : []).sort().join("\0");
1860
- const previousActiveFilterIds = useRef(activeFilterIds);
1861
- const previousConfiguredFilterIds = useRef(configuredFilterIds);
1862
- useEffect(() => {
1863
- const becameActive = hasNewFilterId(activeFilterIds, previousActiveFilterIds.current);
1864
- const newlyConfigured = hasNewFilterId(configuredFilterIds, previousConfiguredFilterIds.current);
1865
- previousActiveFilterIds.current = activeFilterIds;
1866
- previousConfiguredFilterIds.current = configuredFilterIds;
1867
- if (defaultShowFilters == null && filterPanel == null && (becameActive || newlyConfigured)) internalSetShowFilters(true);
1868
- }, [
1869
- activeFilterIds,
1870
- configuredFilterIds,
1871
- defaultShowFilters,
1872
- filterPanel
1873
- ]);
1874
- const showFilters = controlledShowFilters != null ? controlledShowFilters : internalShowFilters;
1875
- const setShowFilters = controlledSetShowFilters != null ? controlledSetShowFilters : internalSetShowFilters;
1876
- const enhancedPropertiesButton = useMemo(() => {
1877
- if (!columnConfigurationProps && !propertiesButton) return;
1878
- const elements = [];
1879
- if (propertiesButton) elements.push(propertiesButton);
1880
- if (columnConfigurationProps) elements.push(/* @__PURE__ */ jsx(TableViewConfigurationDropdown, {
1881
- disabled: false,
1882
- ...columnConfigurationProps
1883
- }, "column-config"));
1884
- if (elements.length === 1) return elements[0];
1885
- return /* @__PURE__ */ jsx(StyledEnhancedPropertiesButton, { children: elements });
1886
- }, [columnConfigurationProps, propertiesButton]);
1887
- return /* @__PURE__ */ jsx(Container, {
1888
- className,
1889
- children: /* @__PURE__ */ jsx(ControlBar, {
1890
- showViews: compact ? false : showViews,
1891
- views: compact ? void 0 : views,
1892
- hiddenViewIds: compact ? void 0 : hiddenViewIds,
1893
- onSelectView: compact ? void 0 : onSelectView,
1894
- currentView: compact ? void 0 : currentView,
1895
- onAddView: compact ? void 0 : onAddView,
1896
- onDeleteView: compact ? void 0 : onDeleteView,
1897
- onDuplicateView: compact ? void 0 : onDuplicateView,
1898
- onViewNameChange: compact ? void 0 : onViewNameChange,
1899
- onReorderViews: compact ? void 0 : onReorderViews,
1900
- onViewHiddenChange: compact ? void 0 : onViewHiddenChange,
1901
- onSaveView: compact ? void 0 : onSaveView,
1902
- ...!compact && onSetDefaultView ? {
1903
- onSetDefaultView,
1904
- onUnsetDefaultView
1905
- } : {
1906
- onSetDefaultView: void 0,
1907
- onUnsetDefaultView: void 0
1908
- },
1909
- isViewDirty,
1910
- canEditViews: compact ? false : canEditViews,
1911
- showFilters: showFilters && (showFiltersAlways || !isFilterPanelLoading),
1912
- setShowFilters: (resolvedFilterPanel != null || isFilterPanelLoading) && !showFiltersAlways ? setShowFilters : void 0,
1913
- filtersId,
1914
- searchProps,
1915
- propertiesButton: enhancedPropertiesButton,
1916
- tableActions,
1917
- isLoading,
1918
- isFilterPanelLoading,
1919
- selectedRows,
1920
- bulkActions,
1921
- onClearSelection,
1922
- title,
1923
- "data-tour-target": dataTourTarget,
1924
- presetViews: compact ? void 0 : presetViews,
1925
- compact,
1926
- onError,
1927
- filterPanel: resolvedFilterPanel
1928
- })
1929
- });
1930
- };
1931
- const Container = styled(Stack)`
1932
- margin-bottom: ${Spacing[2]};
1933
- `;
1934
- const StyledEnhancedPropertiesButton = styled.div`
1935
- display: flex;
1936
- gap: ${Spacing[1]};
1937
- `;
1938
- //#endregion
1939
- //#region components/DataTable/TableControls/FiltersV2/date-rule-intent.ts
1940
- function dateRuleIntent(change, writesEnabled) {
1941
- if (change.source === "clear") return { kind: "clear" };
1942
- if (change.source === "calendar" || !writesEnabled) return change.range == null ? { kind: "clear" } : {
1943
- kind: "absolute",
1944
- range: change.range
1945
- };
1946
- return {
1947
- kind: "relative",
1948
- preset: change.preset,
1949
- range: change.range
1950
- };
1951
- }
1952
- //#endregion
1953
- //#region components/DataTable/TableControls/FiltersV2/operators.ts
1954
- /**
1955
- * Operator vocabulary per field type. The UI uses this to populate the
1956
- * operator dropdown in the Filter popover and to gate which operators are
1957
- * reachable for a given field. `is_empty` / `is_not_empty` apply broadly;
1958
- * everything else is type-specific.
1959
- *
1960
- * The first entry for each type is the default operator selected when a
1961
- * pill is created from the picker.
1962
- */
1963
- const OPERATORS_BY_TYPE = {
1964
- string: [
1965
- "is",
1966
- "is_not",
1967
- "contains",
1968
- "does_not_contain",
1969
- "is_empty",
1970
- "is_not_empty"
1971
- ],
1972
- email: [
1973
- "is",
1974
- "is_not",
1975
- "contains",
1976
- "does_not_contain",
1977
- "is_empty",
1978
- "is_not_empty"
1979
- ],
1980
- choices: [
1981
- "is",
1982
- "is_not",
1983
- "is_empty",
1984
- "is_not_empty"
1985
- ],
1986
- multi_choices: [
1987
- "is",
1988
- "is_not",
1989
- "is_empty",
1990
- "is_not_empty"
1991
- ],
1992
- boolean: ["is"],
1993
- integer: [
1994
- "is",
1995
- "is_not",
1996
- "is_empty",
1997
- "is_not_empty"
1998
- ],
1999
- float: [
2000
- "is",
2001
- "is_not",
2002
- "is_empty",
2003
- "is_not_empty"
2004
- ],
2005
- date: [
2006
- "is",
2007
- "before",
2008
- "after",
2009
- "between",
2010
- "is_empty",
2011
- "is_not_empty"
2012
- ],
2013
- array: [
2014
- "contains",
2015
- "does_not_contain",
2016
- "is_empty",
2017
- "is_not_empty"
2018
- ],
2019
- tags_conditions: ["is"],
2020
- signals: ["is"],
2021
- date_range: ["is"]
2022
- };
2023
- /**
2024
- * Numeric comparison operators. Together with `is` (is equal to) they give
2025
- * `integer` and `float` fields greater-than / less-than / equal-to filtering.
2026
- * Inclusive bounds are deliberately not offered: "at least 5" is
2027
- * `gt 5` OR `is 5`, built with the Rules editor.
2028
- *
2029
- * They are opt-in rather than part of the default numeric vocabulary above:
2030
- * a host whose backend serializes them lists them in the field's
2031
- * `allowedOperators` (e.g. `['is', ...NUMERIC_COMPARISON_OPERATORS]`).
2032
- * Adding them to the default vocabulary would surface operators in every
2033
- * existing numeric field whose host can't serialize them.
2034
- */
2035
- const NUMERIC_COMPARISON_OPERATORS = ["gt", "lt"];
2036
- const NUMERIC_COMPARISON_OPERATOR_SET = new Set(NUMERIC_COMPARISON_OPERATORS);
2037
- const NUMERIC_FIELD_TYPES = new Set(["integer", "float"]);
2038
- function isNumericComparisonOperator(operator) {
2039
- return NUMERIC_COMPARISON_OPERATOR_SET.has(operator);
2040
- }
2041
- /**
2042
- * Whether a comparison value is complete for a numeric field: a finite
2043
- * number, and a safe integer when the field is an `integer`. Strings are
2044
- * rejected — the numeric value controls always emit numbers, so a string
2045
- * here is a corrupted or hand-crafted rule the backend can't compare.
2046
- */
2047
- function isCompleteNumericComparisonValue(value, fieldType) {
2048
- if (typeof value !== "number" || !Number.isFinite(value)) return false;
2049
- return fieldType === "integer" ? Number.isSafeInteger(value) : true;
2050
- }
2051
- /**
2052
- * Carry `value` into a numeric comparison. Equality rules on numeric fields
2053
- * tolerate strings (v1 URLs hydrate `'5'`), but a comparison is only complete
2054
- * with a number, so a numeric string is converted — truncated on `integer`
2055
- * fields, matching what `IntegerValueControl` emits — and anything else
2056
- * becomes an empty draft rather than a committed rule validation rejects.
2057
- */
2058
- function numericComparisonValue(value, fieldType) {
2059
- const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
2060
- if (!Number.isFinite(parsed)) return "";
2061
- const normalized = fieldType === "integer" ? Math.trunc(parsed) : parsed;
2062
- return isCompleteNumericComparisonValue(normalized, fieldType) ? normalized : "";
2063
- }
2064
- /**
2065
- * Whether `(fieldType, operator)` is a built-in numeric comparison — the
2066
- * case validators and labels specialize. Comparisons on non-numeric types
2067
- * (only reachable through `condition`) are not.
2068
- */
2069
- function isNumericComparison(operator, fieldType) {
2070
- return fieldType != null && NUMERIC_FIELD_TYPES.has(fieldType) && isNumericComparisonOperator(operator);
2071
- }
2072
- /** Operators that have no value control (the operator itself is the predicate). */
2073
- const VALUELESS_OPERATORS = new Set(["is_empty", "is_not_empty"]);
2074
- /**
2075
- * Operators whose value is a {@link DateRangeFilterValue} rather than the
2076
- * scalar its field type would otherwise carry. The value control, the draft
2077
- * seed, and every validator branch on this rather than on field type alone —
2078
- * a `date` field carries an ISO day string for `is` / `before` / `after` and
2079
- * a `{ startDate, endDate }` pair for `between`.
2080
- */
2081
- const RANGE_OPERATORS = new Set(["between"]);
2082
- const TIME_COMPONENT$1 = /T\d{2}:\d{2}/;
2083
- function getOperatorsForType(type) {
2084
- return OPERATORS_BY_TYPE[type] ?? [];
2085
- }
2086
- /**
2087
- * Effective operator vocabulary for a concrete field: its `allowedOperators`
2088
- * allow-list, or the field type's full vocabulary when the allow-list is
2089
- * absent or empty. An empty allow-list means "unconstrained" rather than
2090
- * "no operators" — the latter would leave the field selectable in the picker
2091
- * but incapable of producing a rule that passes validation.
2092
- *
2093
- * Prefer this over {@link getOperatorsForType} anywhere a real field is in
2094
- * hand — the popover's operator dropdown, `FieldRegistry`, validation — so a
2095
- * field can never surface an operator the host application can't serialize.
2096
- */
2097
- function getOperatorsForField(field) {
2098
- if (field.condition != null) return field.condition.operators.map(({ operator }) => operator);
2099
- const { allowedOperators } = field;
2100
- if (allowedOperators == null || allowedOperators.length === 0) return getOperatorsForType(field.type);
2101
- return allowedOperators;
1693
+ function getOperatorsForField(field) {
1694
+ if (field.condition != null) return field.condition.operators.map(({ operator }) => operator);
1695
+ const { allowedOperators } = field;
1696
+ if (allowedOperators == null || allowedOperators.length === 0) return getOperatorsForType(field.type);
1697
+ return allowedOperators;
2102
1698
  }
2103
1699
  function getDefaultOperatorForType(type) {
2104
1700
  const list = OPERATORS_BY_TYPE[type];
@@ -2291,25 +1887,6 @@ function reconcileValueForOperator(field, nextOperator, previousOperator, value)
2291
1887
  return value ?? emptyValueForRule(field, nextOperator);
2292
1888
  }
2293
1889
  //#endregion
2294
- //#region components/DataTable/TableControls/FiltersV2/date-range-rule-value.ts
2295
- /**
2296
- * The absolute bounds a rule should be queried with, or `null` when it does not
2297
- * carry a date range.
2298
- *
2299
- * Relative metadata wins over the stored snapshot: the snapshot is a picture of
2300
- * the window when it was saved, and the point of a relative rule is that the
2301
- * window moves. The operator gate is part of this rather than the caller's job —
2302
- * metadata only ever describes a range, so resolving it onto a scalar rule
2303
- * would put a `{ startDate, endDate }` object where the wire expects a day.
2304
- */
2305
- function resolveDateRangeRuleValue(rule, fieldType) {
2306
- if (!carriesDateRange(rule.operator, fieldType)) return null;
2307
- const relative = parseDateFilterValue(rule.relativeDate);
2308
- if (relative?.kind === "relative") return resolveDateFilterValue(relative);
2309
- const parsed = parseDateFilterValue(rule.value);
2310
- return parsed ? resolveDateFilterValue(parsed) : null;
2311
- }
2312
- //#endregion
2313
1890
  //#region components/DataTable/TableControls/FiltersV2/format-date-value.ts
2314
1891
  const TIME_COMPONENT = /T\d{2}:\d{2}/;
2315
1892
  /**
@@ -2738,7 +2315,7 @@ const FILTER_SIZE_TOKENS = {
2738
2315
  const PopoverContent = styled(PopoverPrimitive.Content)`
2739
2316
  z-index: ${Z_INDICES.modalDropdown};
2740
2317
  `;
2741
- const PopoverCard = styled(Card_default)`
2318
+ const PopoverCard$1 = styled(Card_default)`
2742
2319
  width: ${({ $wide }) => $wide ? "520px" : "384px"};
2743
2320
  max-width: calc(100vw - ${Spacing[4]});
2744
2321
  margin-bottom: 0;
@@ -2972,7 +2549,7 @@ const AdvancedFilterRulesPopover = ({ registry, compound, onChange, trigger, ope
2972
2549
  event.preventDefault();
2973
2550
  handleOpenChange(false);
2974
2551
  },
2975
- children: /* @__PURE__ */ jsxs(PopoverCard, {
2552
+ children: /* @__PURE__ */ jsxs(PopoverCard$1, {
2976
2553
  elevated: true,
2977
2554
  $wide: wide,
2978
2555
  children: [/* @__PURE__ */ jsx(Header, { children: /* @__PURE__ */ jsx(Paragraph, {
@@ -3264,6 +2841,20 @@ function ChoicesValueControl({ field, operator, value, onChange, onCommit, ariaL
3264
2841
  });
3265
2842
  }
3266
2843
  //#endregion
2844
+ //#region components/DataTable/TableControls/FiltersV2/date-rule-intent.ts
2845
+ function dateRuleIntent(change, writesEnabled) {
2846
+ if (change.source === "clear") return { kind: "clear" };
2847
+ if (change.source === "calendar" || !writesEnabled) return change.range == null ? { kind: "clear" } : {
2848
+ kind: "absolute",
2849
+ range: change.range
2850
+ };
2851
+ return {
2852
+ kind: "relative",
2853
+ preset: change.preset,
2854
+ range: change.range
2855
+ };
2856
+ }
2857
+ //#endregion
3267
2858
  //#region components/DataTable/TableControls/FiltersV2/value-controls/DateRangeValueControl/DateRangeValueControl.tsx
3268
2859
  const CALENDAR_DATE = /^(\d{4}-\d{2}-\d{2})/;
3269
2860
  const DATE_ONLY$1 = /^\d{4}-\d{2}-\d{2}$/;
@@ -4500,722 +4091,1182 @@ function isFilterRuleValue(value) {
4500
4091
  return false;
4501
4092
  }
4502
4093
  /**
4503
- * Strip rules referencing unregistered `fieldId`s from a decoded state.
4504
- * Pairs with `isFilterStateV2` for defense-in-depth: even after the
4505
- * shape guard passes, a rule with a `fieldId` that doesn't exist in the
4506
- * current resource's registry would silently render nothing in the UI
4507
- * but still reach the adapter and ship to the backend. Pruning closes
4508
- * that gap.
4509
- *
4510
- * Empty AND-groups created by pruning are also dropped, since the
4511
- * adapter prunes them on serialize anyway and the editor UI hides
4512
- * empty groups too.
4513
- *
4514
- * Skips its own work and returns the input unchanged when `registry`
4515
- * is `undefined` — useful for callers that hydrate before the registry
4516
- * is built.
4517
- *
4518
- * For valueless operators (`is_empty`, etc.) the value contract is
4519
- * `null`. Non-valueless operators keep in-progress draft values (an empty
4520
- * string / empty array) so a pill the user is mid-editing survives the
4521
- * report/hydrate round-trip and stays visible — top-level and compound
4522
- * rules are treated alike here, and the serialize boundary
4523
- * (`hasCompleteValue`) is what drops incomplete rules from the request.
4094
+ * Strip rules referencing unregistered `fieldId`s from a decoded state.
4095
+ * Pairs with `isFilterStateV2` for defense-in-depth: even after the
4096
+ * shape guard passes, a rule with a `fieldId` that doesn't exist in the
4097
+ * current resource's registry would silently render nothing in the UI
4098
+ * but still reach the adapter and ship to the backend. Pruning closes
4099
+ * that gap.
4100
+ *
4101
+ * Empty AND-groups created by pruning are also dropped, since the
4102
+ * adapter prunes them on serialize anyway and the editor UI hides
4103
+ * empty groups too.
4104
+ *
4105
+ * Skips its own work and returns the input unchanged when `registry`
4106
+ * is `undefined` — useful for callers that hydrate before the registry
4107
+ * is built.
4108
+ *
4109
+ * For valueless operators (`is_empty`, etc.) the value contract is
4110
+ * `null`. Non-valueless operators keep in-progress draft values (an empty
4111
+ * string / empty array) so a pill the user is mid-editing survives the
4112
+ * report/hydrate round-trip and stays visible — top-level and compound
4113
+ * rules are treated alike here, and the serialize boundary
4114
+ * (`hasCompleteValue`) is what drops incomplete rules from the request.
4115
+ */
4116
+ function pruneToRegistry(state, registry) {
4117
+ if (!registry) return state;
4118
+ const coerced = coerceBooleanRules(state, registry);
4119
+ return {
4120
+ topLevelRules: coerced.topLevelRules.filter((rule) => isRuleAllowed(rule, registry, { allowDraftValues: true })),
4121
+ compound: { groups: coerced.compound.groups.map((group) => ({ rules: group.rules.filter((rule) => isRuleAllowed(rule, registry, { allowDraftValues: true })) })).filter((group) => group.rules.length > 0) }
4122
+ };
4123
+ }
4124
+ /**
4125
+ * Normalizes boolean-field rules into the in-memory contract the new UI
4126
+ * and adapter expect — `is` / `is_not` + a real boolean `value`. Two
4127
+ * legacy shapes are coerced, both only for rules whose registered field
4128
+ * type is `boolean`:
4129
+ *
4130
+ * - Pre–value-control rules using the `is_true` / `is_false` operators
4131
+ * (from saved views) become `is` + boolean `value` so they survive
4132
+ * operator-vocabulary pruning and render in the new UI.
4133
+ * - v1-URL bookmarks hydrate query params as strings, so
4134
+ * `filter[custom][active]=true` arrives as `{ operator: 'is', value:
4135
+ * 'true' }`. The string is coerced back to a boolean so the adapter
4136
+ * emits the legacy `is_true` / `is_false` wire operator instead of
4137
+ * shipping the raw string (which would break old boolean bookmarks).
4138
+ * Non-`'true'`/`'false'` strings are left as-is and dropped later by
4139
+ * `isRuleAllowed`.
4140
+ */
4141
+ function coerceBooleanRule(rule, registry) {
4142
+ const field = registry.get(rule.fieldId);
4143
+ if (!field || field.condition != null || field.type !== "boolean") return rule;
4144
+ if (rule.operator === "is_true") return {
4145
+ ...rule,
4146
+ operator: "is",
4147
+ value: true
4148
+ };
4149
+ if (rule.operator === "is_false") return {
4150
+ ...rule,
4151
+ operator: "is",
4152
+ value: false
4153
+ };
4154
+ if (rule.operator === "is" || rule.operator === "is_not") {
4155
+ if (rule.value === "true") return {
4156
+ ...rule,
4157
+ value: true
4158
+ };
4159
+ if (rule.value === "false") return {
4160
+ ...rule,
4161
+ value: false
4162
+ };
4163
+ }
4164
+ return rule;
4165
+ }
4166
+ function coerceBooleanRules(state, registry) {
4167
+ return {
4168
+ topLevelRules: state.topLevelRules.map((r) => coerceBooleanRule(r, registry)),
4169
+ compound: { groups: state.compound.groups.map((g) => ({ rules: g.rules.map((r) => coerceBooleanRule(r, registry)) })) }
4170
+ };
4171
+ }
4172
+ function isDraftRuleValue(rule, registry) {
4173
+ const field = registry.get(rule.fieldId);
4174
+ if (!field) return false;
4175
+ const config = getConditionOperator(field, rule.operator);
4176
+ if (config != null) switch (config.valueType) {
4177
+ case "none":
4178
+ case "boolean": return false;
4179
+ case "string_array": return Array.isArray(rule.value) && rule.value.length === 0;
4180
+ default: return rule.value === "" || rule.value == null;
4181
+ }
4182
+ if (isValuelessOperator(rule.operator)) return false;
4183
+ if (isRangeOperator(rule.operator)) return !isCompleteDateRange(rule.value);
4184
+ switch (field.type) {
4185
+ case "multi_choices":
4186
+ case "tags_conditions":
4187
+ case "signals": return Array.isArray(rule.value) && rule.value.length === 0;
4188
+ case "date": return rule.value === "" || rule.value == null;
4189
+ case "date_range": return false;
4190
+ case "boolean": return false;
4191
+ default: return rule.value === "" || rule.value == null;
4192
+ }
4193
+ }
4194
+ function isRuleAllowed(rule, registry, options = { allowDraftValues: false }) {
4195
+ const field = registry.get(rule.fieldId);
4196
+ if (field == null) return false;
4197
+ if (!registry.operatorsFor(rule.fieldId).includes(rule.operator)) return false;
4198
+ if (field.condition != null) return hasCompleteConditionValue(rule, field) || options.allowDraftValues && isDraftRuleValue(rule, registry);
4199
+ if (isValuelessOperator(rule.operator)) return rule.value == null;
4200
+ if (options.allowDraftValues && isDraftRuleValue(rule, registry)) return true;
4201
+ if (isRangeOperator(rule.operator)) return isCompleteDateRange(rule.value);
4202
+ if (isNumericComparison(rule.operator, field.type)) return isCompleteNumericComparisonValue(rule.value, field.type);
4203
+ if (registry.get(rule.fieldId)?.type === "boolean") return typeof rule.value === "boolean";
4204
+ if (registry.get(rule.fieldId)?.type === "date") return typeof rule.value === "string" && rule.value.length > 0;
4205
+ if (registry.get(rule.fieldId)?.type === "multi_choices") {
4206
+ if (Array.isArray(rule.value)) return rule.value.length > 0;
4207
+ return typeof rule.value === "string" && rule.value.length > 0;
4208
+ }
4209
+ if (registry.get(rule.fieldId)?.type === "date_range") return isCompleteDateRange(rule.value);
4210
+ const conditionGroupType = registry.get(rule.fieldId)?.type;
4211
+ if (conditionGroupType === "tags_conditions" || conditionGroupType === "signals") return Array.isArray(rule.value) && rule.value.length > 0;
4212
+ return rule.value != null && typeof rule.value !== "boolean";
4213
+ }
4214
+ /**
4215
+ * Whether `rule` carries a complete value for its field type — the
4216
+ * serialize-boundary predicate `pruneEmptyRulesForSerialize` uses to drop
4217
+ * half-edited rules from the request (a value-requiring operator like
4218
+ * `is_not` with no value yet makes the backend reject the whole request).
4219
+ *
4220
+ * Deliberately stricter than `isRuleAllowed`'s hydration policy, which keeps
4221
+ * an empty string so an in-progress pill survives the state round-trip and
4222
+ * stays visible for the user to finish. Checks value completeness only — it
4223
+ * does not gate operator vocabulary or registry membership.
4224
+ */
4225
+ function hasCompleteValue(rule, registry) {
4226
+ const field = registry.get(rule.fieldId);
4227
+ if (field?.condition != null) return hasCompleteConditionValue(rule, field);
4228
+ if (isValuelessOperator(rule.operator)) return rule.value == null;
4229
+ const { value } = rule;
4230
+ if (value == null) return false;
4231
+ if (isRangeOperator(rule.operator)) return isCompleteDateRange(value);
4232
+ if (field != null && isNumericComparison(rule.operator, field.type)) return isCompleteNumericComparisonValue(value, field.type);
4233
+ switch (registry.get(rule.fieldId)?.type) {
4234
+ case "boolean": return typeof value === "boolean";
4235
+ case "date": return typeof value === "string" && value.length > 0;
4236
+ case "multi_choices": return Array.isArray(value) ? value.length > 0 : typeof value === "string" && value.length > 0;
4237
+ case "tags_conditions":
4238
+ case "signals": return Array.isArray(value) && value.length > 0;
4239
+ case "date_range": return isCompleteDateRange(value);
4240
+ default:
4241
+ if (typeof value === "string") return value.length > 0;
4242
+ if (Array.isArray(value)) return value.length > 0;
4243
+ return typeof value !== "boolean";
4244
+ }
4245
+ }
4246
+ //#endregion
4247
+ //#region components/DataTable/TableControls/FiltersV2/useFilterStateV2.ts
4248
+ const nextRuleId = () => `top-rule-${v4()}`;
4249
+ /**
4250
+ * Owns `FilterStateV2` for a filter surface and exposes a small mutator API.
4251
+ *
4252
+ * When `advancedRulesEnabled` is false, `compound.groups` is forced to
4253
+ * `[]`.
4254
+ *
4255
+ * Putting that invariant here (rather than in the components) keeps
4256
+ * `TableControls/FiltersV2` callers from accidentally leaking compound state when
4257
+ * the owning view has opted out.
4258
+ *
4259
+ * The mutator surface is deliberately small: top-level rules get
4260
+ * upsert/remove, the compound expression gets whole-value writes. That
4261
+ * matches how the UI emits changes — simple pills mutate one rule at a
4262
+ * time; the rules popover always emits a full compound expression as
4263
+ * one `onChange`.
4264
+ *
4265
+ * A pill the user has picked but not yet valued is held by the bar rather
4266
+ * than committed. Which pills those are is recorded when the picker mints
4267
+ * them rather than inferred: they are never reported, so incoming state never
4268
+ * describes one and hydration has nothing to reconcile against them. They
4269
+ * stay in the same row as the committed pills, so giving one a value doesn't
4270
+ * move it.
4271
+ */
4272
+ function useFilterStateV2(options) {
4273
+ const { initialState = EMPTY_FILTER_STATE, advancedRulesEnabled = true, registry } = options;
4274
+ const [internal, setInternal] = useState(() => ({
4275
+ all: pruneToRegistry(initialState, registry),
4276
+ heldIds: /* @__PURE__ */ new Set()
4277
+ }));
4278
+ const { all, heldIds } = internal;
4279
+ const lastHydratedState = useRef(all);
4280
+ useEffect(() => {
4281
+ const nextHydratedState = pruneToRegistry(initialState, registry);
4282
+ const hydrate = !isEqual(nextHydratedState, lastHydratedState.current);
4283
+ if (hydrate) lastHydratedState.current = nextHydratedState;
4284
+ setInternal((prev) => {
4285
+ const pruned = pruneHeldRules(prev, registry);
4286
+ return hydrate ? withHeldRules(nextHydratedState, pruned) : pruned;
4287
+ });
4288
+ }, [initialState, registry]);
4289
+ const state = useMemo(() => enforceAdvancedRulesEnabled(all, advancedRulesEnabled), [all, advancedRulesEnabled]);
4290
+ return {
4291
+ state,
4292
+ committedState: useMemo(() => heldIds.size === 0 ? state : {
4293
+ ...state,
4294
+ topLevelRules: state.topLevelRules.filter((rule) => !heldIds.has(rule.id))
4295
+ }, [state, heldIds]),
4296
+ advancedRulesEnabled,
4297
+ setState: useCallback((next) => {
4298
+ setInternal({
4299
+ all: pruneToRegistry(next, registry),
4300
+ heldIds: /* @__PURE__ */ new Set()
4301
+ });
4302
+ }, [registry]),
4303
+ setCompound: useCallback((compound) => {
4304
+ setInternal((prev) => ({
4305
+ ...prev,
4306
+ all: {
4307
+ ...prev.all,
4308
+ compound
4309
+ }
4310
+ }));
4311
+ }, []),
4312
+ reset: useCallback(() => {
4313
+ setInternal({
4314
+ all: {
4315
+ topLevelRules: [],
4316
+ compound: { groups: [] }
4317
+ },
4318
+ heldIds: /* @__PURE__ */ new Set()
4319
+ });
4320
+ }, []),
4321
+ addTopLevelRule: useCallback((rule) => {
4322
+ const id = nextRuleId();
4323
+ const added = {
4324
+ ...rule,
4325
+ id
4326
+ };
4327
+ setInternal((prev) => ({
4328
+ all: {
4329
+ ...prev.all,
4330
+ topLevelRules: [...prev.all.topLevelRules, added]
4331
+ },
4332
+ heldIds: registry.get(rule.fieldId)?.condition != null || isHeldRule(added, registry) ? withId(prev.heldIds, id) : prev.heldIds
4333
+ }));
4334
+ return id;
4335
+ }, [registry]),
4336
+ updateTopLevelRule: useCallback((id, update) => {
4337
+ setInternal((prev) => {
4338
+ const idx = prev.all.topLevelRules.findIndex((r) => r.id === id);
4339
+ const currentRule = prev.all.topLevelRules[idx];
4340
+ if (currentRule == null) return prev;
4341
+ const rule = typeof update === "function" ? update(currentRule) : update;
4342
+ const topLevelRules = [...prev.all.topLevelRules];
4343
+ topLevelRules[idx] = {
4344
+ ...rule,
4345
+ id
4346
+ };
4347
+ const heldIds = isHeldRule(rule, registry) ? withId(prev.heldIds, id) : withoutId(prev.heldIds, id);
4348
+ return {
4349
+ all: {
4350
+ ...prev.all,
4351
+ topLevelRules
4352
+ },
4353
+ heldIds
4354
+ };
4355
+ });
4356
+ }, [registry]),
4357
+ removeTopLevelRule: useCallback((id) => {
4358
+ setInternal((prev) => {
4359
+ const topLevelRules = prev.all.topLevelRules.filter((r) => r.id !== id);
4360
+ if (topLevelRules.length === prev.all.topLevelRules.length) return prev;
4361
+ return {
4362
+ all: {
4363
+ ...prev.all,
4364
+ topLevelRules
4365
+ },
4366
+ heldIds: prev.heldIds.has(id) ? withoutId(prev.heldIds, id) : prev.heldIds
4367
+ };
4368
+ });
4369
+ }, [])
4370
+ };
4371
+ }
4372
+ /**
4373
+ * A pill the picker minted that has no value yet. `null` and not "empty":
4374
+ * `''` and `[]` are values a user can choose, and a filter cleared to one is
4375
+ * still a filter.
4524
4376
  */
4525
- function pruneToRegistry(state, registry) {
4526
- if (!registry) return state;
4527
- const coerced = coerceBooleanRules(state, registry);
4528
- return {
4529
- topLevelRules: coerced.topLevelRules.filter((rule) => isRuleAllowed(rule, registry, { allowDraftValues: true })),
4530
- compound: { groups: coerced.compound.groups.map((group) => ({ rules: group.rules.filter((rule) => isRuleAllowed(rule, registry, { allowDraftValues: true })) })).filter((group) => group.rules.length > 0) }
4531
- };
4377
+ function isHeldRule(rule, registry) {
4378
+ if (registry.get(rule.fieldId)?.condition != null) return !hasCompleteValue(rule, registry);
4379
+ return !isValuelessOperator(rule.operator) && rule.value == null;
4380
+ }
4381
+ function heldRulesOf(state) {
4382
+ return state.heldIds.size === 0 ? [] : state.all.topLevelRules.filter((rule) => state.heldIds.has(rule.id));
4532
4383
  }
4533
4384
  /**
4534
- * Normalizes boolean-field rules into the in-memory contract the new UI
4535
- * and adapter expect — `is` / `is_not` + a real boolean `value`. Two
4536
- * legacy shapes are coerced, both only for rules whose registered field
4537
- * type is `boolean`:
4538
- *
4539
- * - Pre–value-control rules using the `is_true` / `is_false` operators
4540
- * (from saved views) become `is` + boolean `value` so they survive
4541
- * operator-vocabulary pruning and render in the new UI.
4542
- * - v1-URL bookmarks hydrate query params as strings, so
4543
- * `filter[custom][active]=true` arrives as `{ operator: 'is', value:
4544
- * 'true' }`. The string is coerced back to a boolean so the adapter
4545
- * emits the legacy `is_true` / `is_false` wire operator instead of
4546
- * shipping the raw string (which would break old boolean bookmarks).
4547
- * Non-`'true'`/`'false'` strings are left as-is and dropped later by
4548
- * `isRuleAllowed`.
4385
+ * Lays incoming committed rules into the row, keeping the held pills where the
4386
+ * user left them: a held pill goes back after however many committed rules
4387
+ * preceded it before. An id the incoming state now uses is the parent's, so the
4388
+ * held pill of that id gives way rather than duplicating it.
4549
4389
  */
4550
- function coerceBooleanRule(rule, registry) {
4551
- const field = registry.get(rule.fieldId);
4552
- if (!field || field.condition != null || field.type !== "boolean") return rule;
4553
- if (rule.operator === "is_true") return {
4554
- ...rule,
4555
- operator: "is",
4556
- value: true
4390
+ function withHeldRules(committed, prev) {
4391
+ if (prev.heldIds.size === 0) return {
4392
+ all: committed,
4393
+ heldIds: prev.heldIds
4557
4394
  };
4558
- if (rule.operator === "is_false") return {
4559
- ...rule,
4560
- operator: "is",
4561
- value: false
4395
+ const incoming = committed.topLevelRules;
4396
+ const incomingIds = new Set(incoming.map((rule) => rule.id));
4397
+ const topLevelRules = [];
4398
+ const heldIds = /* @__PURE__ */ new Set();
4399
+ let taken = 0;
4400
+ let committedBefore = 0;
4401
+ prev.all.topLevelRules.forEach((rule) => {
4402
+ if (!prev.heldIds.has(rule.id)) {
4403
+ committedBefore += 1;
4404
+ return;
4405
+ }
4406
+ if (incomingIds.has(rule.id)) return;
4407
+ while (taken < committedBefore && taken < incoming.length) {
4408
+ topLevelRules.push(incoming[taken]);
4409
+ taken += 1;
4410
+ }
4411
+ topLevelRules.push(rule);
4412
+ heldIds.add(rule.id);
4413
+ });
4414
+ if (heldIds.size === 0) return {
4415
+ all: committed,
4416
+ heldIds
4562
4417
  };
4563
- if (rule.operator === "is" || rule.operator === "is_not") {
4564
- if (rule.value === "true") return {
4565
- ...rule,
4566
- value: true
4567
- };
4568
- if (rule.value === "false") return {
4569
- ...rule,
4570
- value: false
4571
- };
4418
+ while (taken < incoming.length) {
4419
+ topLevelRules.push(incoming[taken]);
4420
+ taken += 1;
4572
4421
  }
4573
- return rule;
4422
+ return {
4423
+ all: {
4424
+ ...committed,
4425
+ topLevelRules
4426
+ },
4427
+ heldIds
4428
+ };
4574
4429
  }
4575
- function coerceBooleanRules(state, registry) {
4430
+ /**
4431
+ * Drops held pills whose field or operator the registry no longer supports,
4432
+ * preserving the state's identity when there are none so an unchanged registry
4433
+ * can't loop the effect.
4434
+ */
4435
+ function pruneHeldRules(state, registry) {
4436
+ const droppedIds = new Set(heldRulesOf(state).filter((rule) => !registry.has(rule.fieldId) || !registry.operatorsFor(rule.fieldId).includes(rule.operator)).map((rule) => rule.id));
4437
+ if (droppedIds.size === 0) return state;
4438
+ const heldIds = new Set(state.heldIds);
4439
+ droppedIds.forEach((id) => heldIds.delete(id));
4576
4440
  return {
4577
- topLevelRules: state.topLevelRules.map((r) => coerceBooleanRule(r, registry)),
4578
- compound: { groups: state.compound.groups.map((g) => ({ rules: g.rules.map((r) => coerceBooleanRule(r, registry)) })) }
4441
+ all: {
4442
+ ...state.all,
4443
+ topLevelRules: state.all.topLevelRules.filter((rule) => !droppedIds.has(rule.id))
4444
+ },
4445
+ heldIds
4579
4446
  };
4580
4447
  }
4581
- function isDraftRuleValue(rule, registry) {
4582
- const field = registry.get(rule.fieldId);
4583
- if (!field) return false;
4584
- const config = getConditionOperator(field, rule.operator);
4585
- if (config != null) switch (config.valueType) {
4586
- case "none":
4587
- case "boolean": return false;
4588
- case "string_array": return Array.isArray(rule.value) && rule.value.length === 0;
4589
- default: return rule.value === "" || rule.value == null;
4590
- }
4591
- if (isValuelessOperator(rule.operator)) return false;
4592
- if (isRangeOperator(rule.operator)) return !isCompleteDateRange(rule.value);
4593
- switch (field.type) {
4594
- case "multi_choices":
4595
- case "tags_conditions":
4596
- case "signals": return Array.isArray(rule.value) && rule.value.length === 0;
4597
- case "date": return rule.value === "" || rule.value == null;
4598
- case "date_range": return false;
4599
- case "boolean": return false;
4600
- default: return rule.value === "" || rule.value == null;
4601
- }
4448
+ function withId(ids, id) {
4449
+ return ids.has(id) ? ids : new Set(ids).add(id);
4602
4450
  }
4603
- function isRuleAllowed(rule, registry, options = { allowDraftValues: false }) {
4604
- const field = registry.get(rule.fieldId);
4605
- if (field == null) return false;
4606
- if (!registry.operatorsFor(rule.fieldId).includes(rule.operator)) return false;
4607
- if (field.condition != null) return hasCompleteConditionValue(rule, field) || options.allowDraftValues && isDraftRuleValue(rule, registry);
4608
- if (isValuelessOperator(rule.operator)) return rule.value == null;
4609
- if (options.allowDraftValues && isDraftRuleValue(rule, registry)) return true;
4610
- if (isRangeOperator(rule.operator)) return isCompleteDateRange(rule.value);
4611
- if (isNumericComparison(rule.operator, field.type)) return isCompleteNumericComparisonValue(rule.value, field.type);
4612
- if (registry.get(rule.fieldId)?.type === "boolean") return typeof rule.value === "boolean";
4613
- if (registry.get(rule.fieldId)?.type === "date") return typeof rule.value === "string" && rule.value.length > 0;
4614
- if (registry.get(rule.fieldId)?.type === "multi_choices") {
4615
- if (Array.isArray(rule.value)) return rule.value.length > 0;
4616
- return typeof rule.value === "string" && rule.value.length > 0;
4617
- }
4618
- if (registry.get(rule.fieldId)?.type === "date_range") return isCompleteDateRange(rule.value);
4619
- const conditionGroupType = registry.get(rule.fieldId)?.type;
4620
- if (conditionGroupType === "tags_conditions" || conditionGroupType === "signals") return Array.isArray(rule.value) && rule.value.length > 0;
4621
- return rule.value != null && typeof rule.value !== "boolean";
4451
+ function withoutId(ids, id) {
4452
+ if (!ids.has(id)) return ids;
4453
+ const next = new Set(ids);
4454
+ next.delete(id);
4455
+ return next;
4456
+ }
4457
+ function enforceAdvancedRulesEnabled(state, advancedRulesEnabled) {
4458
+ if (advancedRulesEnabled) return state;
4459
+ if (state.compound.groups.length === 0) return state;
4460
+ return {
4461
+ ...state,
4462
+ compound: { groups: [] }
4463
+ };
4622
4464
  }
4465
+ //#endregion
4466
+ //#region components/DataTable/TableControls/FiltersV2/FilterBar/FilterBar.tsx
4467
+ const Bar = styled.div`
4468
+ display: flex;
4469
+ align-items: center;
4470
+ gap: ${Spacing[2]};
4471
+ flex-wrap: nowrap;
4472
+ min-width: 0;
4473
+ padding: ${Spacing[2]} 0;
4474
+ `;
4475
+ const SCROLL_FADE_WIDTH = 48;
4476
+ const SCROLL_SETTLE_FALLBACK_MS = 400;
4477
+ const POPOVER_CLOSE_BUFFER_PX = 8;
4478
+ const PILL_FOCUS_RING_SPACE = 3;
4479
+ /**
4480
+ * Builds the `mask-image` gradient that fades the scroll track's edges.
4481
+ *
4482
+ * A `mask-image` (rather than a same-colored overlay painted on top) fades
4483
+ * the track's *entire rendered output* — pill backgrounds, borders, and any
4484
+ * focus ring that bleeds into the padding above — because masking
4485
+ * composites against the real pixels the element paints, instead of
4486
+ * relying on an opaque sibling layer to visually cover them. An overlay
4487
+ * approach only hides whatever happens to sit below it in paint order;
4488
+ * a mask fades everything, including outlines.
4489
+ */
4490
+ const buildEdgeMaskImage = (showLeftFade, showRightFade) => {
4491
+ return `linear-gradient(to right, ${showLeftFade ? `transparent 0, black ${SCROLL_FADE_WIDTH}px` : "black 0"}, ${showRightFade ? `black calc(100% - ${SCROLL_FADE_WIDTH}px), transparent 100%` : "black 100%"})`;
4492
+ };
4493
+ const PillsScrollRegion = styled.div`
4494
+ display: flex;
4495
+ align-items: center;
4496
+ gap: ${Spacing[1]};
4497
+ flex: 0 1 auto;
4498
+ min-width: 0;
4499
+ width: max-content;
4500
+ max-width: 100%;
4501
+ `;
4502
+ const PillsScrollTrack = styled.div`
4503
+ display: flex;
4504
+ align-items: center;
4505
+ flex: 1 1 auto;
4506
+ min-width: 0;
4507
+ flex-wrap: nowrap;
4508
+ gap: ${Spacing[1]};
4509
+ overflow-x: auto;
4510
+ overflow-y: hidden;
4511
+ -webkit-overflow-scrolling: touch;
4512
+ scrollbar-width: none;
4513
+ padding: ${PILL_FOCUS_RING_SPACE}px;
4514
+ margin: -${PILL_FOCUS_RING_SPACE}px;
4515
+ -webkit-mask-image: ${({ $showLeftFade, $showRightFade }) => buildEdgeMaskImage($showLeftFade, $showRightFade)};
4516
+ mask-image: ${({ $showLeftFade, $showRightFade }) => buildEdgeMaskImage($showLeftFade, $showRightFade)};
4517
+ transition:
4518
+ -webkit-mask-image ${"200ms"} ease-out,
4519
+ mask-image ${"200ms"} ease-out;
4520
+
4521
+ & > * {
4522
+ flex: 0 0 auto;
4523
+ }
4524
+
4525
+ &::-webkit-scrollbar {
4526
+ display: none;
4527
+ }
4528
+ `;
4529
+ const Divider = styled.div`
4530
+ width: 1px;
4531
+ height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].controlHeight};
4532
+ background: ${({ theme }) => semanticColor(theme, "BorderSubtle", Color.BorderDivider)};
4533
+ flex: 0 0 auto;
4534
+ margin: 0 ${({ $flush }) => $flush ? 0 : Spacing[1]};
4535
+ `;
4536
+ const PickerSlot = styled.div`
4537
+ flex: 0 0 auto;
4538
+ `;
4539
+ const EmptyTrailingGroup = styled.div`
4540
+ display: flex;
4541
+ align-items: center;
4542
+ gap: ${Spacing[1]};
4543
+ flex: 0 0 auto;
4544
+ `;
4623
4545
  /**
4624
- * Whether `rule` carries a complete value for its field type — the
4625
- * serialize-boundary predicate `pruneEmptyRulesForSerialize` uses to drop
4626
- * half-edited rules from the request (a value-requiring operator like
4627
- * `is_not` with no value yet makes the backend reject the whole request).
4628
- *
4629
- * Deliberately stricter than `isRuleAllowed`'s hydration policy, which keeps
4630
- * an empty string so an in-progress pill survives the state round-trip and
4631
- * stays visible for the user to finish. Checks value completeness only — it
4632
- * does not gate operator vocabulary or registry membership.
4546
+ * Returns the previous array whenever its contents still match, so the field
4547
+ * ids can be derived on every render without giving the picker a new array to
4548
+ * rebuild its whole option tree from. Editing a pill's value rewrites the
4549
+ * rules while leaving the fields in use unchanged.
4633
4550
  */
4634
- function hasCompleteValue(rule, registry) {
4635
- const field = registry.get(rule.fieldId);
4636
- if (field?.condition != null) return hasCompleteConditionValue(rule, field);
4637
- if (isValuelessOperator(rule.operator)) return rule.value == null;
4638
- const { value } = rule;
4639
- if (value == null) return false;
4640
- if (isRangeOperator(rule.operator)) return isCompleteDateRange(value);
4641
- if (field != null && isNumericComparison(rule.operator, field.type)) return isCompleteNumericComparisonValue(value, field.type);
4642
- switch (registry.get(rule.fieldId)?.type) {
4643
- case "boolean": return typeof value === "boolean";
4644
- case "date": return typeof value === "string" && value.length > 0;
4645
- case "multi_choices": return Array.isArray(value) ? value.length > 0 : typeof value === "string" && value.length > 0;
4646
- case "tags_conditions":
4647
- case "signals": return Array.isArray(value) && value.length > 0;
4648
- case "date_range": return isCompleteDateRange(value);
4649
- default:
4650
- if (typeof value === "string") return value.length > 0;
4651
- if (Array.isArray(value)) return value.length > 0;
4652
- return typeof value !== "boolean";
4653
- }
4551
+ function useStableFieldIds(fieldIds) {
4552
+ const stable = useRef(fieldIds);
4553
+ const previous = stable.current;
4554
+ if (previous == null || fieldIds == null || previous.length !== fieldIds.length || fieldIds.some((id, index) => previous[index] !== id)) stable.current = fieldIds;
4555
+ return stable.current;
4556
+ }
4557
+ function FilterBar({ registry, initialState, onChange, advancedRulesEnabled = true, relativeDatesEnabled = false, dateTimesEnabled = false, singleUseFields = false, size = "md", valueControlResolver }) {
4558
+ const strings = usePDSStrings();
4559
+ const filterState = useFilterStateV2({
4560
+ registry,
4561
+ initialState,
4562
+ advancedRulesEnabled
4563
+ });
4564
+ const [editingRuleId, setEditingRuleId] = useState(null);
4565
+ const [rulesPopoverOpen, setRulesPopoverOpen] = useState(false);
4566
+ const pillNodesRef = useRef(/* @__PURE__ */ new Map());
4567
+ const onChangeRef = useRef(onChange);
4568
+ useEffect(() => {
4569
+ onChangeRef.current = onChange;
4570
+ }, [onChange]);
4571
+ const { committedState } = filterState;
4572
+ const committedRuleIds = useMemo(() => new Set(committedState.topLevelRules.map((rule) => rule.id)), [committedState.topLevelRules]);
4573
+ const lastReportedState = useRef(committedState);
4574
+ useEffect(() => {
4575
+ if (isEqual(lastReportedState.current, committedState)) return;
4576
+ lastReportedState.current = committedState;
4577
+ onChangeRef.current?.(committedState);
4578
+ }, [committedState]);
4579
+ const pendingEditRuleIdRef = useRef(null);
4580
+ const openedViaScrollIntoViewRef = useRef(false);
4581
+ const { addTopLevelRule } = filterState;
4582
+ const handlePickField = useCallback((field) => {
4583
+ const operator = registry.defaultOperatorFor(field.id);
4584
+ pendingEditRuleIdRef.current = addTopLevelRule({
4585
+ fieldId: field.id,
4586
+ operator,
4587
+ value: field.condition != null ? emptyValueForRule(field, operator) : null
4588
+ });
4589
+ }, [addTopLevelRule, registry]);
4590
+ const hasPills = filterState.state.topLevelRules.length > 0;
4591
+ const excludeFieldIds = useStableFieldIds(singleUseFields ? filterState.state.topLevelRules.map((rule) => rule.fieldId) : void 0);
4592
+ const pillsScrollRef = useRef(null);
4593
+ const { hasHorizontalOverflow, hasMoreScrollLeft, hasMoreScrollRight } = useScrollOverflow(pillsScrollRef, { enabled: hasPills });
4594
+ const ruleCount = filterState.state.topLevelRules.length;
4595
+ const previousRuleCountRef = useRef(ruleCount);
4596
+ useEffect(() => {
4597
+ const grew = ruleCount > previousRuleCountRef.current;
4598
+ previousRuleCountRef.current = ruleCount;
4599
+ if (!grew || pendingEditRuleIdRef.current == null) return;
4600
+ const openPendingRule = () => {
4601
+ openedViaScrollIntoViewRef.current = true;
4602
+ setEditingRuleId(pendingEditRuleIdRef.current);
4603
+ pendingEditRuleIdRef.current = null;
4604
+ };
4605
+ const track = pillsScrollRef.current;
4606
+ if (!track || !track.scrollTo) {
4607
+ openPendingRule();
4608
+ return;
4609
+ }
4610
+ const maxScrollLeft = Math.max(track.scrollWidth - track.clientWidth, 0);
4611
+ if (Math.abs(track.scrollLeft - maxScrollLeft) <= 1) {
4612
+ openPendingRule();
4613
+ return;
4614
+ }
4615
+ let settled = false;
4616
+ const finish = () => {
4617
+ if (settled) return;
4618
+ settled = true;
4619
+ track.removeEventListener("scrollend", finish);
4620
+ window.clearTimeout(fallbackTimer);
4621
+ openPendingRule();
4622
+ };
4623
+ track.addEventListener("scrollend", finish, { once: true });
4624
+ const fallbackTimer = window.setTimeout(finish, SCROLL_SETTLE_FALLBACK_MS);
4625
+ track.scrollTo({
4626
+ left: track.scrollWidth,
4627
+ behavior: "smooth"
4628
+ });
4629
+ return () => {
4630
+ track.removeEventListener("scrollend", finish);
4631
+ window.clearTimeout(fallbackTimer);
4632
+ };
4633
+ }, [ruleCount]);
4634
+ useEffect(() => {
4635
+ if (editingRuleId == null) return;
4636
+ const root = pillsScrollRef.current;
4637
+ const target = pillNodesRef.current.get(editingRuleId);
4638
+ if (!root || !target || typeof IntersectionObserver === "undefined") return;
4639
+ let skipInitialReport = openedViaScrollIntoViewRef.current;
4640
+ openedViaScrollIntoViewRef.current = false;
4641
+ const observer = new IntersectionObserver(([entry]) => {
4642
+ if (skipInitialReport) {
4643
+ skipInitialReport = false;
4644
+ return;
4645
+ }
4646
+ if (entry && entry.intersectionRatio < 1) setEditingRuleId(null);
4647
+ }, {
4648
+ root,
4649
+ threshold: 1,
4650
+ rootMargin: `0px ${POPOVER_CLOSE_BUFFER_PX}px 0px ${POPOVER_CLOSE_BUFFER_PX}px`
4651
+ });
4652
+ observer.observe(target);
4653
+ return () => observer.disconnect();
4654
+ }, [editingRuleId]);
4655
+ useLayoutEffect(() => {
4656
+ if (editingRuleId == null) return;
4657
+ pillNodesRef.current.get(editingRuleId)?.scrollIntoView?.({
4658
+ block: "nearest",
4659
+ inline: "nearest"
4660
+ });
4661
+ }, [editingRuleId, useMemo(() => editingRuleId == null ? null : filterState.state.topLevelRules.find((rule) => rule.id === editingRuleId)?.value ?? null, [editingRuleId, filterState.state.topLevelRules])]);
4662
+ const visibleCustomFieldParents = useMemo(() => deriveVisibleCustomFieldParents(registry, filterState.state.topLevelRules), [registry, filterState.state.topLevelRules]);
4663
+ return /* @__PURE__ */ jsx(FilterBarContext.Provider, {
4664
+ value: { size },
4665
+ children: /* @__PURE__ */ jsxs(Bar, {
4666
+ role: "toolbar",
4667
+ "aria-label": strings.filterBar,
4668
+ children: [filterState.advancedRulesEnabled ? /* @__PURE__ */ jsx(AdvancedFilterRulesPopover, {
4669
+ registry,
4670
+ dateTimesEnabled,
4671
+ compound: filterState.state.compound,
4672
+ open: rulesPopoverOpen,
4673
+ onOpenChange: setRulesPopoverOpen,
4674
+ onChange: (compound) => {
4675
+ filterState.setCompound(compound);
4676
+ },
4677
+ renderValueControl: (field, operator, value, onChange, relativeDate) => resolveValueControl({
4678
+ field,
4679
+ operator,
4680
+ value,
4681
+ relativeDate,
4682
+ onChange,
4683
+ layout: "inline",
4684
+ relativeDatesEnabled,
4685
+ dateTimesEnabled
4686
+ }, valueControlResolver),
4687
+ trigger: /* @__PURE__ */ jsx(RulesPill, {
4688
+ compound: filterState.state.compound,
4689
+ enabled: true,
4690
+ isOpen: rulesPopoverOpen
4691
+ })
4692
+ }) : null, hasPills ? /* @__PURE__ */ jsxs(Fragment$1, { children: [filterState.advancedRulesEnabled ? /* @__PURE__ */ jsx(Divider, {
4693
+ "aria-hidden": "true",
4694
+ $size: size
4695
+ }) : null, /* @__PURE__ */ jsxs(PillsScrollRegion, {
4696
+ "data-test": "filter-bar-pills-scroll-region",
4697
+ children: [/* @__PURE__ */ jsx(PillsScrollTrack, {
4698
+ ref: pillsScrollRef,
4699
+ "data-test": "filter-bar-pills-scroll-track",
4700
+ $showLeftFade: hasMoreScrollLeft,
4701
+ $showRightFade: hasHorizontalOverflow && hasMoreScrollRight,
4702
+ children: filterState.state.topLevelRules.map((rule) => {
4703
+ const field = registry.get(rule.fieldId);
4704
+ if (!field) return null;
4705
+ const optionLabels = registry.optionLabelsFor(field.id);
4706
+ const isOpen = editingRuleId === rule.id;
4707
+ return /* @__PURE__ */ jsx(FilterPopover, {
4708
+ field,
4709
+ operator: rule.operator,
4710
+ wide: isMinutePrecisionDate(field, rule.operator, dateTimesEnabled) && carriesDateRange(rule.operator, field.type),
4711
+ onChangeOperator: (op) => {
4712
+ const { relativeDate: _staleRelativeDate, ...rest } = rule;
4713
+ filterState.updateTopLevelRule(rule.id, {
4714
+ ...rest,
4715
+ operator: op,
4716
+ value: reconcileValueForOperator(field, op, rule.operator, rule.value)
4717
+ });
4718
+ },
4719
+ valueControlSlot: resolveValueControl({
4720
+ field,
4721
+ operator: rule.operator,
4722
+ value: rule.value,
4723
+ relativeDate: rule.relativeDate,
4724
+ onChange: (value, relativeDate) => {
4725
+ const { relativeDate: _staleRelativeDate, ...rest } = rule;
4726
+ filterState.updateTopLevelRule(rule.id, {
4727
+ ...rest,
4728
+ value,
4729
+ ...relativeDate ? { relativeDate } : {}
4730
+ });
4731
+ },
4732
+ onCommit: () => setEditingRuleId(null),
4733
+ relativeDatesEnabled,
4734
+ dateTimesEnabled
4735
+ }, valueControlResolver),
4736
+ onClose: () => setEditingRuleId(null),
4737
+ onApply: field.condition != null && !committedRuleIds.has(rule.id) && hasCompleteValue(rule, registry) ? () => filterState.updateTopLevelRule(rule.id, (currentRule) => currentRule) : void 0,
4738
+ open: isOpen,
4739
+ onOpenChange: (open) => setEditingRuleId(open ? rule.id : null),
4740
+ trigger: /* @__PURE__ */ jsx(FilterPill, {
4741
+ ref: (node) => {
4742
+ if (node) pillNodesRef.current.set(rule.id, node);
4743
+ else pillNodesRef.current.delete(rule.id);
4744
+ },
4745
+ field,
4746
+ rule: field.condition != null && !committedRuleIds.has(rule.id) ? null : rule,
4747
+ variant: isOpen ? "active" : "default",
4748
+ optionLabels,
4749
+ onDismiss: () => filterState.removeTopLevelRule(rule.id)
4750
+ })
4751
+ }, rule.id);
4752
+ })
4753
+ }), /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
4754
+ registry,
4755
+ onPickField: handlePickField,
4756
+ compact: true,
4757
+ visibleCustomFieldParents,
4758
+ excludeFieldIds
4759
+ }) })]
4760
+ })] }) : filterState.advancedRulesEnabled ? /* @__PURE__ */ jsxs(EmptyTrailingGroup, { children: [/* @__PURE__ */ jsx(Divider, {
4761
+ "aria-hidden": "true",
4762
+ $size: size,
4763
+ $flush: true
4764
+ }), /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
4765
+ registry,
4766
+ onPickField: handlePickField,
4767
+ visibleCustomFieldParents,
4768
+ excludeFieldIds
4769
+ }) })] }) : /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
4770
+ registry,
4771
+ onPickField: handlePickField,
4772
+ visibleCustomFieldParents,
4773
+ excludeFieldIds
4774
+ }) })]
4775
+ })
4776
+ });
4654
4777
  }
4655
- //#endregion
4656
- //#region components/DataTable/TableControls/FiltersV2/useFilterStateV2.ts
4657
- const nextRuleId = () => `top-rule-${v4()}`;
4658
4778
  /**
4659
- * Owns `FilterStateV2` for a filter surface and exposes a small mutator API.
4660
- *
4661
- * When `advancedRulesEnabled` is false, `compound.groups` is forced to
4662
- * `[]`.
4663
- *
4664
- * Putting that invariant here (rather than in the components) keeps
4665
- * `TableControls/FiltersV2` callers from accidentally leaking compound state when
4666
- * the owning view has opted out.
4779
+ * The parent labels the picker's Fields submenu should keep visible,
4780
+ * derived from active `is` pills on scoping selector fields
4781
+ * ({@link FieldRegistry.narrowingSelectorFields}). Each selected option
4782
+ * value is a backend record-type/template id, resolved to its parent
4783
+ * label via {@link FieldRegistry.parentRecordLabelForId}.
4667
4784
  *
4668
- * The mutator surface is deliberately small: top-level rules get
4669
- * upsert/remove, the compound expression gets whole-value writes. That
4670
- * matches how the UI emits changes — simple pills mutate one rule at a
4671
- * time; the rules popover always emits a full compound expression as
4672
- * one `onChange`.
4785
+ * Returns `undefined` only when no `is` selector pill is active (show
4786
+ * everything). An active pill always returns an array, possibly empty:
4787
+ * selecting a scope with no custom fields narrows to *nothing*, not to
4788
+ * everything — conflating the two would leak other scopes' fields in.
4673
4789
  *
4674
- * A pill the user has picked but not yet valued is held by the bar rather
4675
- * than committed. Which pills those are is recorded when the picker mints
4676
- * them rather than inferred: they are never reported, so incoming state never
4677
- * describes one and hydration has nothing to reconcile against them. They
4678
- * stay in the same row as the committed pills, so giving one a value doesn't
4679
- * move it.
4680
- */
4681
- function useFilterStateV2(options) {
4682
- const { initialState = EMPTY_FILTER_STATE, advancedRulesEnabled = true, registry } = options;
4683
- const [internal, setInternal] = useState(() => ({
4684
- all: pruneToRegistry(initialState, registry),
4685
- heldIds: /* @__PURE__ */ new Set()
4686
- }));
4687
- const { all, heldIds } = internal;
4688
- const lastHydratedState = useRef(all);
4689
- useEffect(() => {
4690
- const nextHydratedState = pruneToRegistry(initialState, registry);
4691
- const hydrate = !isEqual(nextHydratedState, lastHydratedState.current);
4692
- if (hydrate) lastHydratedState.current = nextHydratedState;
4693
- setInternal((prev) => {
4694
- const pruned = pruneHeldRules(prev, registry);
4695
- return hydrate ? withHeldRules(nextHydratedState, pruned) : pruned;
4696
- });
4697
- }, [initialState, registry]);
4698
- const state = useMemo(() => enforceAdvancedRulesEnabled(all, advancedRulesEnabled), [all, advancedRulesEnabled]);
4699
- return {
4700
- state,
4701
- committedState: useMemo(() => heldIds.size === 0 ? state : {
4702
- ...state,
4703
- topLevelRules: state.topLevelRules.filter((rule) => !heldIds.has(rule.id))
4704
- }, [state, heldIds]),
4705
- advancedRulesEnabled,
4706
- setState: useCallback((next) => {
4707
- setInternal({
4708
- all: pruneToRegistry(next, registry),
4709
- heldIds: /* @__PURE__ */ new Set()
4710
- });
4711
- }, [registry]),
4712
- setCompound: useCallback((compound) => {
4713
- setInternal((prev) => ({
4714
- ...prev,
4715
- all: {
4716
- ...prev.all,
4717
- compound
4718
- }
4719
- }));
4720
- }, []),
4721
- reset: useCallback(() => {
4722
- setInternal({
4723
- all: {
4724
- topLevelRules: [],
4725
- compound: { groups: [] }
4726
- },
4727
- heldIds: /* @__PURE__ */ new Set()
4728
- });
4729
- }, []),
4730
- addTopLevelRule: useCallback((rule) => {
4731
- const id = nextRuleId();
4732
- const added = {
4733
- ...rule,
4734
- id
4735
- };
4736
- setInternal((prev) => ({
4737
- all: {
4738
- ...prev.all,
4739
- topLevelRules: [...prev.all.topLevelRules, added]
4740
- },
4741
- heldIds: registry.get(rule.fieldId)?.condition != null || isHeldRule(added, registry) ? withId(prev.heldIds, id) : prev.heldIds
4742
- }));
4743
- return id;
4744
- }, [registry]),
4745
- updateTopLevelRule: useCallback((id, update) => {
4746
- setInternal((prev) => {
4747
- const idx = prev.all.topLevelRules.findIndex((r) => r.id === id);
4748
- const currentRule = prev.all.topLevelRules[idx];
4749
- if (currentRule == null) return prev;
4750
- const rule = typeof update === "function" ? update(currentRule) : update;
4751
- const topLevelRules = [...prev.all.topLevelRules];
4752
- topLevelRules[idx] = {
4753
- ...rule,
4754
- id
4755
- };
4756
- const heldIds = isHeldRule(rule, registry) ? withId(prev.heldIds, id) : withoutId(prev.heldIds, id);
4757
- return {
4758
- all: {
4759
- ...prev.all,
4760
- topLevelRules
4761
- },
4762
- heldIds
4763
- };
4764
- });
4765
- }, [registry]),
4766
- removeTopLevelRule: useCallback((id) => {
4767
- setInternal((prev) => {
4768
- const topLevelRules = prev.all.topLevelRules.filter((r) => r.id !== id);
4769
- if (topLevelRules.length === prev.all.topLevelRules.length) return prev;
4770
- return {
4771
- all: {
4772
- ...prev.all,
4773
- topLevelRules
4774
- },
4775
- heldIds: prev.heldIds.has(id) ? withoutId(prev.heldIds, id) : prev.heldIds
4776
- };
4777
- });
4778
- }, [])
4779
- };
4780
- }
4781
- /**
4782
- * A pill the picker minted that has no value yet. `null` and not "empty":
4783
- * `''` and `[]` are values a user can choose, and a filter cleared to one is
4784
- * still a filter.
4785
- */
4786
- function isHeldRule(rule, registry) {
4787
- if (registry.get(rule.fieldId)?.condition != null) return !hasCompleteValue(rule, registry);
4788
- return !isValuelessOperator(rule.operator) && rule.value == null;
4789
- }
4790
- function heldRulesOf(state) {
4791
- return state.heldIds.size === 0 ? [] : state.all.topLevelRules.filter((rule) => state.heldIds.has(rule.id));
4792
- }
4793
- /**
4794
- * Lays incoming committed rules into the row, keeping the held pills where the
4795
- * user left them: a held pill goes back after however many committed rules
4796
- * preceded it before. An id the incoming state now uses is the parent's, so the
4797
- * held pill of that id gives way rather than duplicating it.
4790
+ * Only `is` narrows; multiple pills union their parents.
4798
4791
  */
4799
- function withHeldRules(committed, prev) {
4800
- if (prev.heldIds.size === 0) return {
4801
- all: committed,
4802
- heldIds: prev.heldIds
4803
- };
4804
- const incoming = committed.topLevelRules;
4805
- const incomingIds = new Set(incoming.map((rule) => rule.id));
4806
- const topLevelRules = [];
4807
- const heldIds = /* @__PURE__ */ new Set();
4808
- let taken = 0;
4809
- let committedBefore = 0;
4810
- prev.all.topLevelRules.forEach((rule) => {
4811
- if (!prev.heldIds.has(rule.id)) {
4812
- committedBefore += 1;
4813
- return;
4814
- }
4815
- if (incomingIds.has(rule.id)) return;
4816
- while (taken < committedBefore && taken < incoming.length) {
4817
- topLevelRules.push(incoming[taken]);
4818
- taken += 1;
4792
+ function deriveVisibleCustomFieldParents(registry, topLevelRules) {
4793
+ const selectorFields = registry.narrowingSelectorFields();
4794
+ if (selectorFields.length === 0 || topLevelRules.length === 0) return;
4795
+ const selectorIds = new Set(selectorFields.map((f) => f.id));
4796
+ const allowedParents = /* @__PURE__ */ new Set();
4797
+ let hasActiveSelectorRule = false;
4798
+ for (const rule of topLevelRules) {
4799
+ if (!selectorIds.has(rule.fieldId)) continue;
4800
+ if (rule.operator !== "is") continue;
4801
+ hasActiveSelectorRule = true;
4802
+ const values = Array.isArray(rule.value) ? rule.value : [rule.value];
4803
+ for (const value of values) {
4804
+ if (typeof value !== "string") continue;
4805
+ const parentLabel = registry.parentRecordLabelForId(value);
4806
+ if (parentLabel != null) allowedParents.add(parentLabel);
4819
4807
  }
4820
- topLevelRules.push(rule);
4821
- heldIds.add(rule.id);
4822
- });
4823
- if (heldIds.size === 0) return {
4824
- all: committed,
4825
- heldIds
4826
- };
4827
- while (taken < incoming.length) {
4828
- topLevelRules.push(incoming[taken]);
4829
- taken += 1;
4830
4808
  }
4831
- return {
4832
- all: {
4833
- ...committed,
4834
- topLevelRules
4835
- },
4836
- heldIds
4837
- };
4809
+ return hasActiveSelectorRule ? Array.from(allowedParents) : void 0;
4838
4810
  }
4811
+ //#endregion
4812
+ //#region components/DataTable/dnd/StyledIconDrag.tsx
4813
+ const StyledIconDrag = styled(DragIcon$1)`
4814
+ display: ${({ $shouldHide = false }) => $shouldHide ? "none" : "inline"};
4815
+
4816
+ ${({ $horizontal = false }) => $horizontal ? css`
4817
+ transform: rotate(90deg);
4818
+ ` : ""}
4819
+
4820
+ ${({ $active = false, $hover = false }) => $active ? css`
4821
+ cursor: grabbing;
4822
+ opacity: 1;
4823
+ ` : $hover ? css`
4824
+ cursor: grab;
4825
+ opacity: 1;
4826
+ ` : css`
4827
+ cursor: normal;
4828
+ opacity: 0.4;
4829
+ `}
4830
+
4831
+ transition: opacity 0.2s ease-in-out;
4832
+
4833
+ path {
4834
+ stroke: ${({ theme }) => semanticColor(theme, "FgPrimary", Color.CoreSlate1000)};
4835
+ stroke-width: 1px;
4836
+ }
4837
+ `;
4838
+ //#endregion
4839
+ //#region components/DataTable/dnd/utils.ts
4839
4840
  /**
4840
- * Drops held pills whose field or operator the registry no longer supports,
4841
- * preserving the state's identity when there are none so an unchanged registry
4842
- * can't loop the effect.
4841
+ * reorder returns a reordered version of the inputed list by swapping the element
4842
+ * at startIndex with the one at endIndex
4843
4843
  */
4844
- function pruneHeldRules(state, registry) {
4845
- const droppedIds = new Set(heldRulesOf(state).filter((rule) => !registry.has(rule.fieldId) || !registry.operatorsFor(rule.fieldId).includes(rule.operator)).map((rule) => rule.id));
4846
- if (droppedIds.size === 0) return state;
4847
- const heldIds = new Set(state.heldIds);
4848
- droppedIds.forEach((id) => heldIds.delete(id));
4849
- return {
4850
- all: {
4851
- ...state.all,
4852
- topLevelRules: state.all.topLevelRules.filter((rule) => !droppedIds.has(rule.id))
4853
- },
4854
- heldIds
4855
- };
4856
- }
4857
- function withId(ids, id) {
4858
- return ids.has(id) ? ids : new Set(ids).add(id);
4859
- }
4860
- function withoutId(ids, id) {
4861
- if (!ids.has(id)) return ids;
4862
- const next = new Set(ids);
4863
- next.delete(id);
4864
- return next;
4844
+ function reorder(list, startIndex, endIndex) {
4845
+ const result = Array.from(list);
4846
+ const [removed] = result.splice(startIndex, 1);
4847
+ result.splice(endIndex, 0, removed);
4848
+ return result;
4865
4849
  }
4866
- function enforceAdvancedRulesEnabled(state, advancedRulesEnabled) {
4867
- if (advancedRulesEnabled) return state;
4868
- if (state.compound.groups.length === 0) return state;
4869
- return {
4870
- ...state,
4871
- compound: { groups: [] }
4850
+ /**
4851
+ * Translates a drag result into indices into the full list. dnd reports positions among the
4852
+ * rendered rows, which diverge from the full list as soon as the view is filtered.
4853
+ *
4854
+ * Returns null when either end cannot be resolved, in which case the drag should be ignored
4855
+ * rather than applied at the wrong position.
4856
+ */
4857
+ function resolveDragIndices(listKeys, renderedKeys, draggableId, destinationIndex) {
4858
+ const destinationKey = renderedKeys[destinationIndex];
4859
+ if (destinationKey == null) return null;
4860
+ const from = listKeys.indexOf(draggableId);
4861
+ const to = listKeys.indexOf(destinationKey);
4862
+ return from === -1 || to === -1 ? null : {
4863
+ from,
4864
+ to
4872
4865
  };
4873
4866
  }
4874
4867
  //#endregion
4875
- //#region components/DataTable/TableControls/FiltersV2/FilterBar/FilterBar.tsx
4876
- const Bar = styled.div`
4868
+ //#region components/DataTable/TableControls/TableViewConfigurationDropdown.tsx
4869
+ const SHOWN_DROPPABLE_ID = "cases-view__configure-shown-columns";
4870
+ const startsCapitalized = (label) => {
4871
+ const first = label.charAt(0);
4872
+ return first !== "" && first === first.toUpperCase() && first !== first.toLowerCase();
4873
+ };
4874
+ const byLabel = (a, b) => {
4875
+ const aCapitalized = startsCapitalized(a.label);
4876
+ if (aCapitalized !== startsCapitalized(b.label)) return aCapitalized ? -1 : 1;
4877
+ return a.label.localeCompare(b.label, void 0, { numeric: true });
4878
+ };
4879
+ const DndSection = styled("div")`
4880
+ max-height: 400px;
4881
+
4882
+ overflow: scroll;
4883
+ ::-webkit-scrollbar {
4884
+ display: none;
4885
+ }
4886
+ `;
4887
+ const DndItemWrapper = styled("div")`
4888
+ background: white;
4889
+ ${semanticOnly((tokens) => `background: ${tokens.SurfacePrimary};`)}
4890
+ width: 100%;
4891
+ border-bottom: 1px solid ${({ theme }) => semanticColor(theme, "BorderSubtle", theme.color.border.secondary)};
4892
+ padding: ${Spacing[2]} ${Spacing[2]};
4893
+
4877
4894
  display: flex;
4895
+ flex-direction: row;
4878
4896
  align-items: center;
4879
- gap: ${Spacing[2]};
4880
- flex-wrap: nowrap;
4881
- min-width: 0;
4882
- padding: ${Spacing[2]} 0;
4897
+ justify-content: left;
4883
4898
  `;
4884
- const SCROLL_FADE_WIDTH = 48;
4885
- const SCROLL_SETTLE_FALLBACK_MS = 400;
4886
- const POPOVER_CLOSE_BUFFER_PX = 8;
4887
- const PILL_FOCUS_RING_SPACE = 3;
4888
- /**
4889
- * Builds the `mask-image` gradient that fades the scroll track's edges.
4890
- *
4891
- * A `mask-image` (rather than a same-colored overlay painted on top) fades
4892
- * the track's *entire rendered output* — pill backgrounds, borders, and any
4893
- * focus ring that bleeds into the padding above — because masking
4894
- * composites against the real pixels the element paints, instead of
4895
- * relying on an opaque sibling layer to visually cover them. An overlay
4896
- * approach only hides whatever happens to sit below it in paint order;
4897
- * a mask fades everything, including outlines.
4898
- */
4899
- const buildEdgeMaskImage = (showLeftFade, showRightFade) => {
4900
- return `linear-gradient(to right, ${showLeftFade ? `transparent 0, black ${SCROLL_FADE_WIDTH}px` : "black 0"}, ${showRightFade ? `black calc(100% - ${SCROLL_FADE_WIDTH}px), transparent 100%` : "black 100%"})`;
4899
+ const SuffixContainer = styled("div")`
4900
+ padding-left: ${Spacing[2]};
4901
+ margin-left: auto;
4902
+ align-self: center;
4903
+ `;
4904
+ const SearchContainer = styled.div`
4905
+ display: flex;
4906
+ `;
4907
+ const StyledSearchInput = styled(SearchInput)`
4908
+ width: 100%;
4909
+ `;
4910
+ const PopoverCard = styled(Card_default)`
4911
+ width: 300px;
4912
+ padding: ${Spacing[4]};
4913
+ `;
4914
+ const TriggerIconButton = forwardRef(function TriggerIconButton({ disabled, ...elemProps }, ref) {
4915
+ const strings = usePDSStrings();
4916
+ return /* @__PURE__ */ jsx(Button, {
4917
+ disabled,
4918
+ width: "icon",
4919
+ variant: "basic",
4920
+ size: "md",
4921
+ ...elemProps,
4922
+ ref,
4923
+ "data-tour-target": "table-properties-button",
4924
+ "aria-label": strings.tableProperties,
4925
+ children: /* @__PURE__ */ jsx(SettingsIcon, {})
4926
+ });
4927
+ });
4928
+ const TooltipButtonWrapper = styled("div")`
4929
+ display: inline-block;
4930
+ `;
4931
+ const TableViewConfigurationDropdown = ({ disabled = false, disabledTooltipMessage, size = "md", variant = "basic", ...props }) => {
4932
+ const strings = usePDSStrings();
4933
+ return disabled ? /* @__PURE__ */ jsx(Tooltip, {
4934
+ trigger: /* @__PURE__ */ jsx(TooltipButtonWrapper, { children: /* @__PURE__ */ jsx(TriggerIconButton, {
4935
+ disabled: true,
4936
+ size,
4937
+ variant
4938
+ }) }),
4939
+ children: disabledTooltipMessage ?? strings.configureViewPermissionDenied
4940
+ }) : /* @__PURE__ */ jsxs(Popover_default, { children: [/* @__PURE__ */ jsx(Popover_default.Trigger, {
4941
+ asChild: true,
4942
+ children: /* @__PURE__ */ jsx(TriggerIconButton, {
4943
+ disabled: false,
4944
+ size,
4945
+ variant
4946
+ })
4947
+ }), /* @__PURE__ */ jsx(Popover_default.Content, {
4948
+ style: { zIndex: Z_INDICES.dropdown },
4949
+ sideOffset: 8,
4950
+ side: "bottom",
4951
+ align: "end",
4952
+ children: /* @__PURE__ */ jsx(PopoverCard, {
4953
+ elevated: true,
4954
+ children: /* @__PURE__ */ jsx(TableViewConfiguration, { ...props })
4955
+ })
4956
+ })] });
4957
+ };
4958
+ const TableViewConfiguration = ({ columnConfiguration, attributeOptions, updateColumnConfiguration, getColumnDisabled, sortByRelevance = false }) => {
4959
+ const strings = usePDSStrings();
4960
+ const [searchTerm, setSearchTerm] = useState("");
4961
+ const fuse = useMemo(() => {
4962
+ return new Fuse(attributeOptions, sortByRelevance ? {
4963
+ shouldSort: true,
4964
+ threshold: .3,
4965
+ ignoreLocation: true,
4966
+ includeScore: true,
4967
+ minMatchCharLength: 1,
4968
+ keys: [{
4969
+ name: "label",
4970
+ weight: 1
4971
+ }]
4972
+ } : {
4973
+ shouldSort: false,
4974
+ threshold: .4,
4975
+ location: 0,
4976
+ distance: 100,
4977
+ minMatchCharLength: 1,
4978
+ keys: [{
4979
+ name: "label",
4980
+ weight: 1
4981
+ }]
4982
+ });
4983
+ }, [attributeOptions, sortByRelevance]);
4984
+ const allOptions = useMemo(() => union(attributeOptions, columnConfiguration), [attributeOptions, columnConfiguration]);
4985
+ const filteredSearchOptions = useMemo(() => {
4986
+ if (searchTerm === "") return allOptions;
4987
+ const results = fuse.search(searchTerm);
4988
+ if (!sortByRelevance) return results.map(({ item }) => item);
4989
+ return [...results].sort((a, b) => (a.score ?? 0) - (b.score ?? 0) || byLabel(a.item, b.item)).map(({ item }) => item);
4990
+ }, [
4991
+ allOptions,
4992
+ fuse,
4993
+ searchTerm,
4994
+ sortByRelevance
4995
+ ]);
4996
+ const hiddenAttributes = useMemo(() => {
4997
+ const columnConfigurationKeys = new Set(columnConfiguration.map((c) => c.key));
4998
+ const hidden = filteredSearchOptions.filter((attribute) => !columnConfigurationKeys.has(attribute.key));
4999
+ if (!sortByRelevance || searchTerm !== "") return hidden;
5000
+ const [systemColumns, customFields] = partition(hidden, (a) => !a.isCustomField);
5001
+ return [...systemColumns, ...[...customFields].sort(byLabel)];
5002
+ }, [
5003
+ columnConfiguration,
5004
+ filteredSearchOptions,
5005
+ sortByRelevance,
5006
+ searchTerm
5007
+ ]);
5008
+ const filteredShownAttributes = useMemo(() => {
5009
+ return columnConfiguration.filter((column) => filteredSearchOptions.map((o) => o.key).includes(column.key));
5010
+ }, [columnConfiguration, filteredSearchOptions]);
5011
+ const handleDragEnd = (result, _provided) => {
5012
+ const { destination, source } = result;
5013
+ if (!destination || destination.droppableId === source.droppableId && destination.index === source.index) return;
5014
+ const indices = resolveDragIndices(columnConfiguration.map((c) => c.key), filteredShownAttributes.map((c) => c.key), result.draggableId, destination.index);
5015
+ if (!indices) return;
5016
+ updateColumnConfiguration(reorder(columnConfiguration, indices.from, indices.to));
5017
+ };
5018
+ const generateShownAttributesToggle = (column) => () => {
5019
+ updateColumnConfiguration(columnConfiguration.filter((a) => a.key !== column.key));
5020
+ };
5021
+ const generateHiddenAttributesToggle = (column) => () => {
5022
+ updateColumnConfiguration(columnConfiguration.concat({
5023
+ key: column.key,
5024
+ label: column.label
5025
+ }));
5026
+ };
5027
+ const shownColumnIds = useMemo(() => {
5028
+ return columnConfiguration.map((c) => c.key);
5029
+ }, [columnConfiguration]);
5030
+ const shownDndItemProps = filteredShownAttributes.map((column, index) => {
5031
+ const isDisabledByDefault = columnConfiguration.length === 1;
5032
+ const isDisabledByCustom = getColumnDisabled?.(column.key, shownColumnIds) ?? false;
5033
+ const shouldDisableDragForColumn = isDisabledByCustom && index === filteredShownAttributes.length - 1;
5034
+ return {
5035
+ column,
5036
+ index,
5037
+ isDisabled: isDisabledByDefault || isDisabledByCustom,
5038
+ isToggled: true,
5039
+ shouldDisableDrag: shouldDisableDragForColumn,
5040
+ handleToggle: generateShownAttributesToggle(column)
5041
+ };
5042
+ });
5043
+ const hiddenDndItemProps = hiddenAttributes.map((column, index) => {
5044
+ return {
5045
+ column,
5046
+ index,
5047
+ isToggled: false,
5048
+ shouldDisableDrag: true,
5049
+ handleToggle: generateHiddenAttributesToggle(column)
5050
+ };
5051
+ });
5052
+ return /* @__PURE__ */ jsxs("div", { children: [
5053
+ /* @__PURE__ */ jsx(Label$1, {
5054
+ size: "lg",
5055
+ weight: "bold",
5056
+ children: strings.configureColumns
5057
+ }),
5058
+ /* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
5059
+ /* @__PURE__ */ jsx(SearchContainer, { children: /* @__PURE__ */ jsx(StyledSearchInput, {
5060
+ title: null,
5061
+ value: searchTerm,
5062
+ handleInput: setSearchTerm,
5063
+ size: "md",
5064
+ placeholder: strings.searchColumns,
5065
+ dataTest: "checklist-item-panel-item-table__search"
5066
+ }) }),
5067
+ /* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
5068
+ shownDndItemProps.length > 0 || hiddenDndItemProps.length > 0 ? /* @__PURE__ */ jsxs(DndSection, { children: [
5069
+ shownDndItemProps.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Paragraph, { children: strings.shownColumns }), /* @__PURE__ */ jsx(DragDropContext, {
5070
+ onDragEnd: handleDragEnd,
5071
+ children: /* @__PURE__ */ jsx(Droppable, {
5072
+ droppableId: SHOWN_DROPPABLE_ID,
5073
+ renderClone: (provided, _snapshot, rubric) => {
5074
+ return /* @__PURE__ */ jsx("div", {
5075
+ ...provided.draggableProps,
5076
+ ...provided.dragHandleProps,
5077
+ ref: provided.innerRef,
5078
+ children: /* @__PURE__ */ jsx(AttributeDnd, { ...shownDndItemProps[rubric.source.index] })
5079
+ });
5080
+ },
5081
+ children: (provided) => {
5082
+ return /* @__PURE__ */ jsxs("div", {
5083
+ ref: provided.innerRef,
5084
+ ...provided.droppableProps,
5085
+ children: [shownDndItemProps.map((props, idx) => {
5086
+ return /* @__PURE__ */ jsx(Draggable, {
5087
+ draggableId: props.column.key,
5088
+ index: idx,
5089
+ isDragDisabled: props.shouldDisableDrag,
5090
+ children: (provided) => {
5091
+ return /* @__PURE__ */ jsx("div", {
5092
+ ref: provided.innerRef,
5093
+ ...provided.draggableProps,
5094
+ ...provided.dragHandleProps,
5095
+ children: /* @__PURE__ */ jsx(AttributeDnd, { ...props })
5096
+ });
5097
+ }
5098
+ }, `${props.column.key}`);
5099
+ }), provided.placeholder]
5100
+ });
5101
+ }
5102
+ })
5103
+ })] }),
5104
+ /* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
5105
+ hiddenDndItemProps.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Paragraph, { children: strings.hiddenColumns }), hiddenDndItemProps.map((props) => /* @__PURE__ */ jsx(AttributeDnd, { ...props }, props.column.key))] })
5106
+ ] }) : /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Paragraph, { children: strings.noMatchingColumns }) })
5107
+ ] });
4901
5108
  };
4902
- const PillsScrollRegion = styled.div`
4903
- display: flex;
4904
- align-items: center;
4905
- gap: ${Spacing[1]};
4906
- flex: 0 1 auto;
4907
- min-width: 0;
4908
- width: max-content;
4909
- max-width: 100%;
4910
- `;
4911
- const PillsScrollTrack = styled.div`
4912
- display: flex;
4913
- align-items: center;
4914
- flex: 1 1 auto;
4915
- min-width: 0;
4916
- flex-wrap: nowrap;
4917
- gap: ${Spacing[1]};
4918
- overflow-x: auto;
4919
- overflow-y: hidden;
4920
- -webkit-overflow-scrolling: touch;
4921
- scrollbar-width: none;
4922
- padding: ${PILL_FOCUS_RING_SPACE}px;
4923
- margin: -${PILL_FOCUS_RING_SPACE}px;
4924
- -webkit-mask-image: ${({ $showLeftFade, $showRightFade }) => buildEdgeMaskImage($showLeftFade, $showRightFade)};
4925
- mask-image: ${({ $showLeftFade, $showRightFade }) => buildEdgeMaskImage($showLeftFade, $showRightFade)};
4926
- transition:
4927
- -webkit-mask-image ${"200ms"} ease-out,
4928
- mask-image ${"200ms"} ease-out;
4929
-
4930
- & > * {
4931
- flex: 0 0 auto;
4932
- }
4933
-
4934
- &::-webkit-scrollbar {
4935
- display: none;
4936
- }
4937
- `;
4938
- const Divider = styled.div`
4939
- width: 1px;
4940
- height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].controlHeight};
4941
- background: ${({ theme }) => semanticColor(theme, "BorderSubtle", Color.BorderDivider)};
4942
- flex: 0 0 auto;
4943
- margin: 0 ${({ $flush }) => $flush ? 0 : Spacing[1]};
4944
- `;
4945
- const PickerSlot = styled.div`
4946
- flex: 0 0 auto;
4947
- `;
4948
- const EmptyTrailingGroup = styled.div`
5109
+ const OverflowText = styled("div")`
5110
+ max-width: 160px;
5111
+ flex-wrap: wrap;
4949
5112
  display: flex;
4950
- align-items: center;
4951
- gap: ${Spacing[1]};
4952
- flex: 0 0 auto;
4953
5113
  `;
4954
- /**
4955
- * Returns the previous array whenever its contents still match, so the field
4956
- * ids can be derived on every render without giving the picker a new array to
4957
- * rebuild its whole option tree from. Editing a pill's value rewrites the
4958
- * rules while leaving the fields in use unchanged.
4959
- */
4960
- function useStableFieldIds(fieldIds) {
4961
- const stable = useRef(fieldIds);
4962
- const previous = stable.current;
4963
- if (previous == null || fieldIds == null || previous.length !== fieldIds.length || fieldIds.some((id, index) => previous[index] !== id)) stable.current = fieldIds;
4964
- return stable.current;
4965
- }
4966
- function FilterBar({ registry, initialState, onChange, advancedRulesEnabled = true, relativeDatesEnabled = false, dateTimesEnabled = false, singleUseFields = false, size = "md", valueControlResolver }) {
5114
+ const AttributeDnd = ({ column, index, isDisabled = false, isToggled, shouldDisableDrag, handleToggle }) => {
4967
5115
  const strings = usePDSStrings();
4968
- const filterState = useFilterStateV2({
4969
- registry,
4970
- initialState,
4971
- advancedRulesEnabled
4972
- });
4973
- const [editingRuleId, setEditingRuleId] = useState(null);
4974
- const [rulesPopoverOpen, setRulesPopoverOpen] = useState(false);
4975
- const pillNodesRef = useRef(/* @__PURE__ */ new Map());
4976
- const onChangeRef = useRef(onChange);
4977
- useEffect(() => {
4978
- onChangeRef.current = onChange;
4979
- }, [onChange]);
4980
- const { committedState } = filterState;
4981
- const committedRuleIds = useMemo(() => new Set(committedState.topLevelRules.map((rule) => rule.id)), [committedState.topLevelRules]);
4982
- const lastReportedState = useRef(committedState);
4983
- useEffect(() => {
4984
- if (isEqual(lastReportedState.current, committedState)) return;
4985
- lastReportedState.current = committedState;
4986
- onChangeRef.current?.(committedState);
4987
- }, [committedState]);
4988
- const pendingEditRuleIdRef = useRef(null);
4989
- const openedViaScrollIntoViewRef = useRef(false);
4990
- const { addTopLevelRule } = filterState;
4991
- const handlePickField = useCallback((field) => {
4992
- const operator = registry.defaultOperatorFor(field.id);
4993
- pendingEditRuleIdRef.current = addTopLevelRule({
4994
- fieldId: field.id,
4995
- operator,
4996
- value: field.condition != null ? emptyValueForRule(field, operator) : null
4997
- });
4998
- }, [addTopLevelRule, registry]);
4999
- const hasPills = filterState.state.topLevelRules.length > 0;
5000
- const excludeFieldIds = useStableFieldIds(singleUseFields ? filterState.state.topLevelRules.map((rule) => rule.fieldId) : void 0);
5001
- const pillsScrollRef = useRef(null);
5002
- const { hasHorizontalOverflow, hasMoreScrollLeft, hasMoreScrollRight } = useScrollOverflow(pillsScrollRef, { enabled: hasPills });
5003
- const ruleCount = filterState.state.topLevelRules.length;
5004
- const previousRuleCountRef = useRef(ruleCount);
5116
+ return /* @__PURE__ */ jsxs(DndItemWrapper, { children: [
5117
+ /* @__PURE__ */ jsx(StyledIconDrag, {
5118
+ $active: true,
5119
+ $shouldHide: shouldDisableDrag
5120
+ }),
5121
+ /* @__PURE__ */ jsx(OverflowText, { children: /* @__PURE__ */ jsx(Paragraph, {
5122
+ color: "primary",
5123
+ size: "sm",
5124
+ children: column.label
5125
+ }) }),
5126
+ /* @__PURE__ */ jsx(SuffixContainer, { children: /* @__PURE__ */ jsx(Toggle, {
5127
+ value: isToggled,
5128
+ disabled: isDisabled,
5129
+ onChange: handleToggle,
5130
+ "aria-label": isToggled ? strings.hideColumn(column.label) : strings.showColumn(column.label)
5131
+ }) })
5132
+ ] }, `${column.key}-${index}`);
5133
+ };
5134
+ //#endregion
5135
+ //#region components/DataTable/TableControls/TableControls.tsx
5136
+ const EMPTY_FILTERS = [];
5137
+ const resolveFilterArea = (filterPanel, filtersV2Panel, configuredFilterPanel) => filterPanel ?? filtersV2Panel ?? configuredFilterPanel;
5138
+ const hasNewFilterId = (currentIds, previousIds) => {
5139
+ const previous = new Set(previousIds.split("\0").filter(Boolean));
5140
+ return currentIds.split("\0").some((id) => id !== "" && !previous.has(id));
5141
+ };
5142
+ const TableControls = ({ filters = EMPTY_FILTERS, filterResolver, filtersV2, filterPanel, searchProps, defaultShowFilters, showFilters: controlledShowFilters, setShowFilters: controlledSetShowFilters, className, tableActions, propertiesButton, isLoading, isFilterPanelLoading, showViews, canEditViews, onSelectView, views, hiddenViewIds, currentView, onAddView, onViewNameChange, onReorderViews, onViewHiddenChange, onDuplicateView, onDeleteView, onSaveView, onSetDefaultView, onUnsetDefaultView, isViewDirty, selectedRows, bulkActions, onClearSelection, title, presetViews, compact, columnConfigurationProps, showFiltersAlways = false, onError, "data-tour-target": dataTourTarget }) => {
5143
+ const filtersId = useId();
5144
+ const enabledFilters = useMemo(() => filtersV2 != null ? EMPTY_FILTERS : filters.filter((filter) => filter.enabled !== false), [filters, filtersV2]);
5145
+ const [activeFilters, setActiveFilters] = useState(() => getActiveFilters(enabledFilters, [], true, filterResolver));
5146
+ const generatedFilterPanel = enabledFilters.length > 0 ? /* @__PURE__ */ jsx(TableControlsProvider, {
5147
+ filters: enabledFilters,
5148
+ activeFilters,
5149
+ setActiveFilters,
5150
+ children: /* @__PURE__ */ jsx(Filters, {
5151
+ id: filtersId,
5152
+ compact,
5153
+ filterResolver
5154
+ })
5155
+ }) : void 0;
5156
+ const usesFiltersV2 = filterPanel == null && filtersV2 != null;
5157
+ const resolvedFilterPanel = resolveFilterArea(filterPanel, filtersV2 != null ? /* @__PURE__ */ jsx("div", {
5158
+ id: filtersId,
5159
+ children: /* @__PURE__ */ jsx(FilterBar, {
5160
+ ...filtersV2,
5161
+ size: filtersV2.size ?? (compact ? "sm" : "md")
5162
+ })
5163
+ }) : void 0, generatedFilterPanel);
5164
+ const [internalShowFilters, internalSetShowFilters] = useState(defaultShowFilters ?? (filterPanel == null && (usesFiltersV2 || activeFilters.length > 0)));
5005
5165
  useEffect(() => {
5006
- const grew = ruleCount > previousRuleCountRef.current;
5007
- previousRuleCountRef.current = ruleCount;
5008
- if (!grew || pendingEditRuleIdRef.current == null) return;
5009
- const openPendingRule = () => {
5010
- openedViaScrollIntoViewRef.current = true;
5011
- setEditingRuleId(pendingEditRuleIdRef.current);
5012
- pendingEditRuleIdRef.current = null;
5013
- };
5014
- const track = pillsScrollRef.current;
5015
- if (!track || !track.scrollTo) {
5016
- openPendingRule();
5017
- return;
5018
- }
5019
- const maxScrollLeft = Math.max(track.scrollWidth - track.clientWidth, 0);
5020
- if (Math.abs(track.scrollLeft - maxScrollLeft) <= 1) {
5021
- openPendingRule();
5022
- return;
5023
- }
5024
- let settled = false;
5025
- const finish = () => {
5026
- if (settled) return;
5027
- settled = true;
5028
- track.removeEventListener("scrollend", finish);
5029
- window.clearTimeout(fallbackTimer);
5030
- openPendingRule();
5031
- };
5032
- track.addEventListener("scrollend", finish, { once: true });
5033
- const fallbackTimer = window.setTimeout(finish, SCROLL_SETTLE_FALLBACK_MS);
5034
- track.scrollTo({
5035
- left: track.scrollWidth,
5036
- behavior: "smooth"
5037
- });
5038
- return () => {
5039
- track.removeEventListener("scrollend", finish);
5040
- window.clearTimeout(fallbackTimer);
5041
- };
5042
- }, [ruleCount]);
5166
+ setActiveFilters((current) => synchronizeActiveFilters(enabledFilters, current, filterResolver));
5167
+ }, [enabledFilters, filterResolver]);
5168
+ const activeFilterIds = enabledFilters.filter((filter) => isFilterActive(filter, filterResolver)).map((filter) => filter.id).sort().join("\0");
5169
+ const configuredFilterIds = activeFilters.flatMap((filter) => filter.config ? [filter.config.id] : []).sort().join("\0");
5170
+ const previousActiveFilterIds = useRef(activeFilterIds);
5171
+ const previousConfiguredFilterIds = useRef(configuredFilterIds);
5172
+ const previouslyUsedFiltersV2 = useRef(usesFiltersV2);
5043
5173
  useEffect(() => {
5044
- if (editingRuleId == null) return;
5045
- const root = pillsScrollRef.current;
5046
- const target = pillNodesRef.current.get(editingRuleId);
5047
- if (!root || !target || typeof IntersectionObserver === "undefined") return;
5048
- let skipInitialReport = openedViaScrollIntoViewRef.current;
5049
- openedViaScrollIntoViewRef.current = false;
5050
- const observer = new IntersectionObserver(([entry]) => {
5051
- if (skipInitialReport) {
5052
- skipInitialReport = false;
5053
- return;
5054
- }
5055
- if (entry && entry.intersectionRatio < 1) setEditingRuleId(null);
5056
- }, {
5057
- root,
5058
- threshold: 1,
5059
- rootMargin: `0px ${POPOVER_CLOSE_BUFFER_PX}px 0px ${POPOVER_CLOSE_BUFFER_PX}px`
5060
- });
5061
- observer.observe(target);
5062
- return () => observer.disconnect();
5063
- }, [editingRuleId]);
5064
- useLayoutEffect(() => {
5065
- if (editingRuleId == null) return;
5066
- pillNodesRef.current.get(editingRuleId)?.scrollIntoView?.({
5067
- block: "nearest",
5068
- inline: "nearest"
5069
- });
5070
- }, [editingRuleId, useMemo(() => editingRuleId == null ? null : filterState.state.topLevelRules.find((rule) => rule.id === editingRuleId)?.value ?? null, [editingRuleId, filterState.state.topLevelRules])]);
5071
- const visibleCustomFieldParents = useMemo(() => deriveVisibleCustomFieldParents(registry, filterState.state.topLevelRules), [registry, filterState.state.topLevelRules]);
5072
- return /* @__PURE__ */ jsx(FilterBarContext.Provider, {
5073
- value: { size },
5074
- children: /* @__PURE__ */ jsxs(Bar, {
5075
- role: "toolbar",
5076
- "aria-label": strings.filterBar,
5077
- children: [filterState.advancedRulesEnabled ? /* @__PURE__ */ jsx(AdvancedFilterRulesPopover, {
5078
- registry,
5079
- dateTimesEnabled,
5080
- compound: filterState.state.compound,
5081
- open: rulesPopoverOpen,
5082
- onOpenChange: setRulesPopoverOpen,
5083
- onChange: (compound) => {
5084
- filterState.setCompound(compound);
5085
- },
5086
- renderValueControl: (field, operator, value, onChange, relativeDate) => resolveValueControl({
5087
- field,
5088
- operator,
5089
- value,
5090
- relativeDate,
5091
- onChange,
5092
- layout: "inline",
5093
- relativeDatesEnabled,
5094
- dateTimesEnabled
5095
- }, valueControlResolver),
5096
- trigger: /* @__PURE__ */ jsx(RulesPill, {
5097
- compound: filterState.state.compound,
5098
- enabled: true,
5099
- isOpen: rulesPopoverOpen
5100
- })
5101
- }) : null, hasPills ? /* @__PURE__ */ jsxs(Fragment$1, { children: [filterState.advancedRulesEnabled ? /* @__PURE__ */ jsx(Divider, {
5102
- "aria-hidden": "true",
5103
- $size: size
5104
- }) : null, /* @__PURE__ */ jsxs(PillsScrollRegion, {
5105
- "data-test": "filter-bar-pills-scroll-region",
5106
- children: [/* @__PURE__ */ jsx(PillsScrollTrack, {
5107
- ref: pillsScrollRef,
5108
- "data-test": "filter-bar-pills-scroll-track",
5109
- $showLeftFade: hasMoreScrollLeft,
5110
- $showRightFade: hasHorizontalOverflow && hasMoreScrollRight,
5111
- children: filterState.state.topLevelRules.map((rule) => {
5112
- const field = registry.get(rule.fieldId);
5113
- if (!field) return null;
5114
- const optionLabels = registry.optionLabelsFor(field.id);
5115
- const isOpen = editingRuleId === rule.id;
5116
- return /* @__PURE__ */ jsx(FilterPopover, {
5117
- field,
5118
- operator: rule.operator,
5119
- wide: isMinutePrecisionDate(field, rule.operator, dateTimesEnabled) && carriesDateRange(rule.operator, field.type),
5120
- onChangeOperator: (op) => {
5121
- const { relativeDate: _staleRelativeDate, ...rest } = rule;
5122
- filterState.updateTopLevelRule(rule.id, {
5123
- ...rest,
5124
- operator: op,
5125
- value: reconcileValueForOperator(field, op, rule.operator, rule.value)
5126
- });
5127
- },
5128
- valueControlSlot: resolveValueControl({
5129
- field,
5130
- operator: rule.operator,
5131
- value: rule.value,
5132
- relativeDate: rule.relativeDate,
5133
- onChange: (value, relativeDate) => {
5134
- const { relativeDate: _staleRelativeDate, ...rest } = rule;
5135
- filterState.updateTopLevelRule(rule.id, {
5136
- ...rest,
5137
- value,
5138
- ...relativeDate ? { relativeDate } : {}
5139
- });
5140
- },
5141
- onCommit: () => setEditingRuleId(null),
5142
- relativeDatesEnabled,
5143
- dateTimesEnabled
5144
- }, valueControlResolver),
5145
- onClose: () => setEditingRuleId(null),
5146
- onApply: field.condition != null && !committedRuleIds.has(rule.id) && hasCompleteValue(rule, registry) ? () => filterState.updateTopLevelRule(rule.id, (currentRule) => currentRule) : void 0,
5147
- open: isOpen,
5148
- onOpenChange: (open) => setEditingRuleId(open ? rule.id : null),
5149
- trigger: /* @__PURE__ */ jsx(FilterPill, {
5150
- ref: (node) => {
5151
- if (node) pillNodesRef.current.set(rule.id, node);
5152
- else pillNodesRef.current.delete(rule.id);
5153
- },
5154
- field,
5155
- rule: field.condition != null && !committedRuleIds.has(rule.id) ? null : rule,
5156
- variant: isOpen ? "active" : "default",
5157
- optionLabels,
5158
- onDismiss: () => filterState.removeTopLevelRule(rule.id)
5159
- })
5160
- }, rule.id);
5161
- })
5162
- }), /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
5163
- registry,
5164
- onPickField: handlePickField,
5165
- compact: true,
5166
- visibleCustomFieldParents,
5167
- excludeFieldIds
5168
- }) })]
5169
- })] }) : filterState.advancedRulesEnabled ? /* @__PURE__ */ jsxs(EmptyTrailingGroup, { children: [/* @__PURE__ */ jsx(Divider, {
5170
- "aria-hidden": "true",
5171
- $size: size,
5172
- $flush: true
5173
- }), /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
5174
- registry,
5175
- onPickField: handlePickField,
5176
- visibleCustomFieldParents,
5177
- excludeFieldIds
5178
- }) })] }) : /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
5179
- registry,
5180
- onPickField: handlePickField,
5181
- visibleCustomFieldParents,
5182
- excludeFieldIds
5183
- }) })]
5174
+ const becameActive = hasNewFilterId(activeFilterIds, previousActiveFilterIds.current);
5175
+ const newlyConfigured = hasNewFilterId(configuredFilterIds, previousConfiguredFilterIds.current);
5176
+ const enabledFiltersV2 = usesFiltersV2 && !previouslyUsedFiltersV2.current;
5177
+ previousActiveFilterIds.current = activeFilterIds;
5178
+ previousConfiguredFilterIds.current = configuredFilterIds;
5179
+ previouslyUsedFiltersV2.current = usesFiltersV2;
5180
+ if (defaultShowFilters == null && filterPanel == null && (enabledFiltersV2 || !usesFiltersV2 && (becameActive || newlyConfigured))) internalSetShowFilters(true);
5181
+ }, [
5182
+ activeFilterIds,
5183
+ configuredFilterIds,
5184
+ defaultShowFilters,
5185
+ filterPanel,
5186
+ usesFiltersV2
5187
+ ]);
5188
+ const showFilters = controlledShowFilters != null ? controlledShowFilters : internalShowFilters;
5189
+ const setShowFilters = controlledSetShowFilters != null ? controlledSetShowFilters : internalSetShowFilters;
5190
+ const enhancedPropertiesButton = useMemo(() => {
5191
+ if (!columnConfigurationProps && !propertiesButton) return;
5192
+ const elements = [];
5193
+ if (propertiesButton) elements.push(propertiesButton);
5194
+ if (columnConfigurationProps) elements.push(/* @__PURE__ */ jsx(TableViewConfigurationDropdown, {
5195
+ disabled: false,
5196
+ ...columnConfigurationProps
5197
+ }, "column-config"));
5198
+ if (elements.length === 1) return elements[0];
5199
+ return /* @__PURE__ */ jsx(StyledEnhancedPropertiesButton, { children: elements });
5200
+ }, [columnConfigurationProps, propertiesButton]);
5201
+ return /* @__PURE__ */ jsx(Container, {
5202
+ className,
5203
+ children: /* @__PURE__ */ jsx(ControlBar, {
5204
+ showViews: compact ? false : showViews,
5205
+ views: compact ? void 0 : views,
5206
+ hiddenViewIds: compact ? void 0 : hiddenViewIds,
5207
+ onSelectView: compact ? void 0 : onSelectView,
5208
+ currentView: compact ? void 0 : currentView,
5209
+ onAddView: compact ? void 0 : onAddView,
5210
+ onDeleteView: compact ? void 0 : onDeleteView,
5211
+ onDuplicateView: compact ? void 0 : onDuplicateView,
5212
+ onViewNameChange: compact ? void 0 : onViewNameChange,
5213
+ onReorderViews: compact ? void 0 : onReorderViews,
5214
+ onViewHiddenChange: compact ? void 0 : onViewHiddenChange,
5215
+ onSaveView: compact ? void 0 : onSaveView,
5216
+ ...!compact && onSetDefaultView ? {
5217
+ onSetDefaultView,
5218
+ onUnsetDefaultView
5219
+ } : {
5220
+ onSetDefaultView: void 0,
5221
+ onUnsetDefaultView: void 0
5222
+ },
5223
+ isViewDirty,
5224
+ canEditViews: compact ? false : canEditViews,
5225
+ showFilters: showFilters && (showFiltersAlways || !isFilterPanelLoading),
5226
+ setShowFilters: (resolvedFilterPanel != null || isFilterPanelLoading) && !showFiltersAlways ? setShowFilters : void 0,
5227
+ filtersId,
5228
+ searchProps,
5229
+ propertiesButton: enhancedPropertiesButton,
5230
+ tableActions,
5231
+ isLoading,
5232
+ isFilterPanelLoading,
5233
+ selectedRows,
5234
+ bulkActions,
5235
+ onClearSelection,
5236
+ title,
5237
+ "data-tour-target": dataTourTarget,
5238
+ presetViews: compact ? void 0 : presetViews,
5239
+ compact,
5240
+ onError,
5241
+ filterPanel: resolvedFilterPanel
5184
5242
  })
5185
5243
  });
5186
- }
5244
+ };
5245
+ const Container = styled(Stack)`
5246
+ margin-bottom: ${Spacing[2]};
5247
+ `;
5248
+ const StyledEnhancedPropertiesButton = styled.div`
5249
+ display: flex;
5250
+ gap: ${Spacing[1]};
5251
+ `;
5252
+ //#endregion
5253
+ //#region components/DataTable/TableControls/FiltersV2/date-range-rule-value.ts
5187
5254
  /**
5188
- * The parent labels the picker's Fields submenu should keep visible,
5189
- * derived from active `is` pills on scoping selector fields
5190
- * ({@link FieldRegistry.narrowingSelectorFields}). Each selected option
5191
- * value is a backend record-type/template id, resolved to its parent
5192
- * label via {@link FieldRegistry.parentRecordLabelForId}.
5193
- *
5194
- * Returns `undefined` only when no `is` selector pill is active (show
5195
- * everything). An active pill always returns an array, possibly empty:
5196
- * selecting a scope with no custom fields narrows to *nothing*, not to
5197
- * everything — conflating the two would leak other scopes' fields in.
5255
+ * The absolute bounds a rule should be queried with, or `null` when it does not
5256
+ * carry a date range.
5198
5257
  *
5199
- * Only `is` narrows; multiple pills union their parents.
5258
+ * Relative metadata wins over the stored snapshot: the snapshot is a picture of
5259
+ * the window when it was saved, and the point of a relative rule is that the
5260
+ * window moves. The operator gate is part of this rather than the caller's job —
5261
+ * metadata only ever describes a range, so resolving it onto a scalar rule
5262
+ * would put a `{ startDate, endDate }` object where the wire expects a day.
5200
5263
  */
5201
- function deriveVisibleCustomFieldParents(registry, topLevelRules) {
5202
- const selectorFields = registry.narrowingSelectorFields();
5203
- if (selectorFields.length === 0 || topLevelRules.length === 0) return;
5204
- const selectorIds = new Set(selectorFields.map((f) => f.id));
5205
- const allowedParents = /* @__PURE__ */ new Set();
5206
- let hasActiveSelectorRule = false;
5207
- for (const rule of topLevelRules) {
5208
- if (!selectorIds.has(rule.fieldId)) continue;
5209
- if (rule.operator !== "is") continue;
5210
- hasActiveSelectorRule = true;
5211
- const values = Array.isArray(rule.value) ? rule.value : [rule.value];
5212
- for (const value of values) {
5213
- if (typeof value !== "string") continue;
5214
- const parentLabel = registry.parentRecordLabelForId(value);
5215
- if (parentLabel != null) allowedParents.add(parentLabel);
5216
- }
5217
- }
5218
- return hasActiveSelectorRule ? Array.from(allowedParents) : void 0;
5264
+ function resolveDateRangeRuleValue(rule, fieldType) {
5265
+ if (!carriesDateRange(rule.operator, fieldType)) return null;
5266
+ const relative = parseDateFilterValue(rule.relativeDate);
5267
+ if (relative?.kind === "relative") return resolveDateFilterValue(relative);
5268
+ const parsed = parseDateFilterValue(rule.value);
5269
+ return parsed ? resolveDateFilterValue(parsed) : null;
5219
5270
  }
5220
5271
  //#endregion
5221
5272
  //#region components/DataTable/TableControls/FiltersV2/FilterBar/index.ts
@@ -5490,6 +5541,6 @@ function decodeBase64(input) {
5490
5541
  return decodeURIComponent(escape(atob(input)));
5491
5542
  }
5492
5543
  //#endregion
5493
- export { dateRuleIntent as A, useTableControls as B, getOperatorsForField as C, isRangeOperator as D, isNumericComparisonOperator as E, getActiveFilters as F, DataTableCompactBar as H, isFilterActive as I, resolveFilterTypeDefinition as L, TableViewConfiguration as M, TableViewConfigurationDropdown as N, isValuelessOperator as O, FiltersContainer as P, TableControlsContext as R, getDefaultOperatorForType as S, isCompleteDateRange as T, ControlBar as V, FILTER_SIZE_TOKENS as _, encodeFilterStateToUrlParam as a, NUMERIC_COMPARISON_OPERATORS as b, FilterBar_default as c, isFilterStateV2 as d, pruneToRegistry as f, resolveValueControl as g, PillShell as h, decodeFilterStateFromUrl as i, TableControls as j, reconcileValueForOperator as k, useFilterStateV2 as l, isEmptyState as m, applyFilterStateToSearchParams as n, FieldRegistry as o, EMPTY_FILTER_STATE as p, decodeFilterStateFromString as r, buildFieldRegistry as s, FILTER_STATE_URL_PARAM as t, hasCompleteValue as u, useFilterBarSize as v, getOperatorsForType as w, emptyValueForRule as x, resolveDateRangeRuleValue as y, TableControlsProvider as z };
5544
+ export { isNumericComparisonOperator as A, useTableControls as B, useFilterBarSize as C, getOperatorsForField as D, getDefaultOperatorForType as E, getActiveFilters as F, DataTableCompactBar as H, isFilterActive as I, resolveFilterTypeDefinition as L, isValuelessOperator as M, reconcileValueForOperator as N, getOperatorsForType as O, FiltersContainer as P, TableControlsContext as R, FILTER_SIZE_TOKENS as S, emptyValueForRule as T, ControlBar as V, EMPTY_FILTER_STATE as _, encodeFilterStateToUrlParam as a, resolveValueControl as b, FilterBar_default as c, TableViewConfiguration as d, TableViewConfigurationDropdown as f, pruneToRegistry as g, isFilterStateV2 as h, decodeFilterStateFromUrl as i, isRangeOperator as j, isCompleteDateRange as k, resolveDateRangeRuleValue as l, hasCompleteValue as m, applyFilterStateToSearchParams as n, FieldRegistry as o, useFilterStateV2 as p, decodeFilterStateFromString as r, buildFieldRegistry as s, FILTER_STATE_URL_PARAM as t, TableControls as u, isEmptyState as v, NUMERIC_COMPARISON_OPERATORS as w, dateRuleIntent as x, PillShell as y, TableControlsProvider as z };
5494
5545
 
5495
- //# sourceMappingURL=TableControls-DxSFqRwJ.mjs.map
5546
+ //# sourceMappingURL=TableControls-C5fNNdmT.mjs.map