@persona/design-system 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/CHANGELOG.md +13 -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-4dc7YXJY.d.mts} +2 -2
  9. package/dist/CheckboxGroupField.d.mts +1 -1
  10. package/dist/ChoicesValueControl.d.mts +1 -1
  11. package/dist/{Combobox-DH5ycYlG.d.mts → Combobox-Bm2eSg56.d.mts} +3 -3
  12. package/dist/Combobox.d.mts +2 -2
  13. package/dist/{ComboboxBase-BQQSKUBU.d.mts → ComboboxBase-B7ZcqU9p.d.mts} +2 -2
  14. package/dist/ComboboxBase.d.mts +1 -1
  15. package/dist/{ComboboxMultiSelect-Da3Gv1k5.d.mts → ComboboxMultiSelect-BkdRrH6K.d.mts} +2 -2
  16. package/dist/ComboboxMultiSelect.d.mts +1 -1
  17. package/dist/{ComboboxSingleSelect-BUDovONU.d.mts → ComboboxSingleSelect-DIBRZmvS.d.mts} +3 -3
  18. package/dist/ComboboxSingleSelect.d.mts +1 -1
  19. package/dist/{CompactBar-D12R7Nm2.d.mts → CompactBar-CfrrZ69k.d.mts} +1 -1
  20. package/dist/CompactBar.d.mts +1 -1
  21. package/dist/{DataTable-fZ7SNbIH.d.mts → DataTable--_f1Obhj.d.mts} +7 -5
  22. package/dist/{DataTable-95ZxiPlV.mjs → DataTable-BoAAKhVH.mjs} +3 -3
  23. package/dist/DataTable-BoAAKhVH.mjs.map +1 -0
  24. package/dist/DataTable.d.mts +1 -1
  25. package/dist/{DateField-DLz13Ksl.d.mts → DateField-CwSq77Ri.d.mts} +1 -1
  26. package/dist/DateField.d.mts +1 -1
  27. package/dist/{DatePicker-DYVWmbrb.d.mts → DatePicker-DRhY_0st.d.mts} +1 -1
  28. package/dist/DatePicker.d.mts +1 -1
  29. package/dist/{DateRangePicker-Be8TYtjz.d.mts → DateRangePicker-CLuBLF1q.d.mts} +2 -2
  30. package/dist/DateRangePicker.d.mts +1 -1
  31. package/dist/DateRangeValueControl.d.mts +2 -2
  32. package/dist/DateValueControl.d.mts +1 -1
  33. package/dist/DropdownButton.d.mts +1 -1
  34. package/dist/{DropdownMenu-DAa2kNrt.d.mts → DropdownMenu-Bie2cWjb.d.mts} +2 -2
  35. package/dist/DropdownMenu.d.mts +1 -1
  36. package/dist/{FieldRegistry-d5tGpSch.d.mts → FieldRegistry-B5E-GN91.d.mts} +2 -2
  37. package/dist/FieldRegistry.d.mts +1 -1
  38. package/dist/{FilterBar-Bh18VyOK.d.mts → FilterBar-BQWpTi2b.d.mts} +4 -4
  39. package/dist/FilterBar.d.mts +1 -1
  40. package/dist/FilterPickerMenu.d.mts +2 -2
  41. package/dist/FilterPill.d.mts +1 -1
  42. package/dist/FilterPopover.d.mts +1 -1
  43. package/dist/FloatValueControl.d.mts +1 -1
  44. package/dist/{FormField-cSG93kuo.mjs → FormField-C4B2qFgh.mjs} +6 -6
  45. package/dist/{FormField-cSG93kuo.mjs.map → FormField-C4B2qFgh.mjs.map} +1 -1
  46. package/dist/HighlightedText.d.mts +1 -1
  47. package/dist/IndexTableLayout.d.mts +3 -3
  48. package/dist/InlineCombobox.d.mts +1 -1
  49. package/dist/InlineInput.d.mts +1 -1
  50. package/dist/IntegerValueControl.d.mts +1 -1
  51. package/dist/{ListBox-Bj5c626-.d.mts → ListBox-DIk8suJM.d.mts} +2 -2
  52. package/dist/{ListBox-C80J7DCG.mjs → ListBox-eoyBHtnJ.mjs} +1 -1
  53. package/dist/{ListBox-C80J7DCG.mjs.map → ListBox-eoyBHtnJ.mjs.map} +1 -1
  54. package/dist/ListBox.d.mts +1 -1
  55. package/dist/{ListBoxField-DsJRabgE.d.mts → ListBoxField-DF8rqPsu.d.mts} +4 -4
  56. package/dist/ListBoxField.d.mts +1 -1
  57. package/dist/{LogicRow-CcpD-Kk3.d.mts → LogicRow-uxpe1vZ1.d.mts} +2 -2
  58. package/dist/LogicRow.d.mts +1 -1
  59. package/dist/MaskedInput.d.mts +1 -1
  60. package/dist/{Menu-C0FU9WEF.d.mts → Menu-CrThaUX2.d.mts} +2 -2
  61. package/dist/Menu.d.mts +1 -1
  62. package/dist/{PillShell-CXROf8gO.d.mts → PillShell-DqglbFHe.d.mts} +2 -2
  63. package/dist/PillShell.d.mts +1 -1
  64. package/dist/{Radio-DDT_l8rd.mjs → Radio-BstZg3DI.mjs} +1 -1
  65. package/dist/{Radio-DDT_l8rd.mjs.map → Radio-BstZg3DI.mjs.map} +1 -1
  66. package/dist/{RadioGroup-CmD8NFCL.mjs → RadioGroup-erKerwSt.mjs} +2 -2
  67. package/dist/{RadioGroup-CmD8NFCL.mjs.map → RadioGroup-erKerwSt.mjs.map} +1 -1
  68. package/dist/{RangeCalendar-cZSYjuOZ.d.mts → RangeCalendar-CK5ArbIQ.d.mts} +2 -2
  69. package/dist/RangeCalendar.d.mts +1 -1
  70. package/dist/RangeCalendarPresets.d.mts +1 -1
  71. package/dist/RuleEditorFields.d.mts +2 -2
  72. package/dist/RulesPill.d.mts +1 -1
  73. package/dist/{SegmentedControl-CcKPfxBR.mjs → SegmentedControl-DQjIO6iX.mjs} +1 -1
  74. package/dist/{SegmentedControl-CcKPfxBR.mjs.map → SegmentedControl-DQjIO6iX.mjs.map} +1 -1
  75. package/dist/{Select-sBEBrNgo.d.mts → Select-CsxK6A5m.d.mts} +2 -2
  76. package/dist/Select.d.mts +1 -1
  77. package/dist/SmartTextInput.d.mts +22 -5
  78. package/dist/SmartTextInputField.d.mts +2 -2
  79. package/dist/SmartTextInputFunctionDialog.d.mts +2 -2
  80. package/dist/SmartTextInputListbox.d.mts +2 -2
  81. package/dist/SmartTextInputPopover.d.mts +1 -1
  82. package/dist/StringValueControl.d.mts +1 -1
  83. package/dist/TableBox.d.mts +1 -1
  84. package/dist/{TableBoxCell-BRECnVTs.d.mts → TableBoxCell-okcOQ5Ec.d.mts} +4 -4
  85. package/dist/TableBoxCell.d.mts +1 -1
  86. package/dist/{TableBoxField-Lm_tn73M.d.mts → TableBoxField-DR-SJY9z.d.mts} +5 -5
  87. package/dist/TableBoxField.d.mts +1 -1
  88. package/dist/{TableBoxRow-BS8Dc-zw.d.mts → TableBoxRow-DiuAB_rV.d.mts} +2 -2
  89. package/dist/TableBoxRow.d.mts +1 -1
  90. package/dist/{TableBoxRowActions-oLMb8XcM.d.mts → TableBoxRowActions-Cm4KJsAD.d.mts} +2 -2
  91. package/dist/TableBoxRowActions.d.mts +1 -1
  92. package/dist/{TableControls-UfObf-ZN.d.mts → TableControls-B9bvsNUc.d.mts} +10 -5
  93. package/dist/{TableControls-DxSFqRwJ.mjs → TableControls-n16XKO9S.mjs} +1298 -1286
  94. package/dist/TableControls-n16XKO9S.mjs.map +1 -0
  95. package/dist/TableControls.d.mts +2 -2
  96. package/dist/{TableControlsContext-Bhtb0cMs.d.mts → TableControlsContext-CZThYoj5.d.mts} +2 -2
  97. package/dist/TableControlsContext.d.mts +1 -1
  98. package/dist/{TableViewConfigurationDropdown-BDXe2Q1Y.d.mts → TableViewConfigurationDropdown-CvN7tE98.d.mts} +2 -2
  99. package/dist/TableViewConfigurationDropdown.d.mts +1 -1
  100. package/dist/{TextArea-CFf0Lspu.mjs → TextArea-CaIEBz8d.mjs} +1 -1
  101. package/dist/{TextArea-CFf0Lspu.mjs.map → TextArea-CaIEBz8d.mjs.map} +1 -1
  102. package/dist/{Toggle-BLdemkh_.mjs → Toggle-BsqSNYok.mjs} +1 -1
  103. package/dist/{Toggle-BLdemkh_.mjs.map → Toggle-BsqSNYok.mjs.map} +1 -1
  104. package/dist/{chainTypes-BbFZn8a9.d.mts → chainTypes-Cjp0zZ4E.d.mts} +3 -3
  105. package/dist/{clientFilterUtils-c5xzvoBj.d.mts → clientFilterUtils-BXEpzorX.d.mts} +2 -2
  106. package/dist/clientFilterUtils.d.mts +1 -1
  107. package/dist/{codec-DIp7dhcN.d.mts → codec-KAvQRjTW.d.mts} +2 -2
  108. package/dist/codec.d.mts +1 -1
  109. package/dist/components/Combobox/index.d.mts +2 -2
  110. package/dist/components/DataTable/TableControls/index.d.mts +21 -21
  111. package/dist/components/DataTable/TableControls/index.mjs +1 -1
  112. package/dist/components/DataTable/index.d.mts +5 -5
  113. package/dist/components/DataTable/index.mjs +1 -1
  114. package/dist/components/DataTable/types.d.mts +1 -1
  115. package/dist/components/DatePicker/index.d.mts +5 -5
  116. package/dist/components/DropdownMenu/index.d.mts +1 -1
  117. package/dist/components/FormField/index.d.mts +4 -4
  118. package/dist/components/FormField/index.mjs +1 -1
  119. package/dist/components/ListBox/index.d.mts +3 -3
  120. package/dist/components/ListBox/index.mjs +1 -1
  121. package/dist/components/Menu/index.d.mts +1 -1
  122. package/dist/components/Radio/index.mjs +1 -1
  123. package/dist/components/RadioGroup/index.mjs +1 -1
  124. package/dist/components/SegmentedControl/index.mjs +1 -1
  125. package/dist/components/Select/index.d.mts +1 -1
  126. package/dist/components/SmartTextInput/index.d.mts +2 -2
  127. package/dist/components/SmartTextInput/index.mjs +12 -3
  128. package/dist/components/SmartTextInput/index.mjs.map +1 -1
  129. package/dist/components/TableBox/index.d.mts +1 -1
  130. package/dist/components/TextArea/index.mjs +1 -1
  131. package/dist/components/Toggle/index.mjs +1 -1
  132. package/dist/components/VersionHistory/index.mjs +1 -1
  133. package/dist/compound-expression.d.mts +1 -1
  134. package/dist/{date-range-rule-value-D01nn054.d.mts → date-range-rule-value-Dk6pZHnD.d.mts} +2 -2
  135. package/dist/date-range-rule-value.d.mts +1 -1
  136. package/dist/{date-rule-intent-B4hQ_APa.d.mts → date-rule-intent-C5C8HB1v.d.mts} +3 -3
  137. package/dist/date-rule-intent.d.mts +1 -1
  138. package/dist/display.d.mts +1 -1
  139. package/dist/fieldPickerOptions.d.mts +2 -2
  140. package/dist/{filter-size-context-Dj7Idc8j.d.mts → filter-size-context-5xQ3ycnC.d.mts} +2 -2
  141. package/dist/filter-size-context.d.mts +1 -1
  142. package/dist/{filters-DDrsgRYy.d.mts → filters-ogUUmLEf.d.mts} +2 -2
  143. package/dist/filters.d.mts +1 -1
  144. package/dist/{helpers-D8_2I4ZX.d.mts → helpers-Dqw5PEva.d.mts} +2 -2
  145. package/dist/helpers.d.mts +1 -1
  146. package/dist/{index-DsjvDLF8.d.mts → index-Ck939aXd.d.mts} +3 -3
  147. package/dist/{index-DkXly4xw.d.mts → index-DTAcGzW4.d.mts} +2 -2
  148. package/dist/index3.d.mts +3 -3
  149. package/dist/index4.d.mts +1 -1
  150. package/dist/index5.d.mts +14 -14
  151. package/dist/{inputFields-DyOZHse2.d.mts → inputFields-DVLg_2wz.d.mts} +4 -4
  152. package/dist/inputFields.d.mts +1 -1
  153. package/dist/{jsonSchema-COo0XQLA.d.mts → jsonSchema-DmOA0Lnu.d.mts} +2 -2
  154. package/dist/lib/PDSProvider.d.mts +8 -0
  155. package/dist/lib/PDSProvider.mjs +15 -11
  156. package/dist/lib/PDSProvider.mjs.map +1 -1
  157. package/dist/lib/chainTypes.d.mts +1 -1
  158. package/dist/lib/colorMode.d.mts +30 -0
  159. package/dist/lib/colorMode.mjs +71 -0
  160. package/dist/lib/colorMode.mjs.map +1 -0
  161. package/dist/lib/customFunctionSchema.d.mts +1 -1
  162. package/dist/lib/date-filter/index.d.mts +2 -2
  163. package/dist/lib/jsonSchema.d.mts +1 -1
  164. package/dist/lib/jsonSchema.mjs.map +1 -1
  165. package/dist/lib/jsonSchemaType.d.mts +1 -1
  166. package/dist/lib/methodChain.d.mts +1 -1
  167. package/dist/lib/objectSchemaTypes.d.mts +1 -1
  168. package/dist/lib/platformObject.d.mts +1 -1
  169. package/dist/lib/schemaOptions.d.mts +2 -2
  170. package/dist/lib/schemaOptions.mjs +20 -2
  171. package/dist/lib/schemaOptions.mjs.map +1 -1
  172. package/dist/lib/useChain.d.mts +1 -1
  173. package/dist/{listbox-options-D-DyLUdi.d.mts → listbox-options-DCmqWWgy.d.mts} +3 -3
  174. package/dist/listbox-options.d.mts +1 -1
  175. package/dist/{methodChain-BAUFBbR9.d.mts → methodChain-DaDvXsHF.d.mts} +2 -2
  176. package/dist/{operators-DjQXVNVI.d.mts → operators-Seez5p9U.d.mts} +2 -2
  177. package/dist/operators.d.mts +1 -1
  178. package/dist/patterns/IndexTableLayout/index.mjs +1 -1
  179. package/dist/relative-date-label.d.mts +1 -1
  180. package/dist/{schemaOptions-D536qhSd.d.mts → schemaOptions-BQgLOgtD.d.mts} +8 -3
  181. package/dist/{types-DHTFd-5t.d.mts → types-39QvBZuZ.d.mts} +1 -1
  182. package/dist/{types-CjQzH0MM.d.mts → types-BQP14Fng.d.mts} +2 -2
  183. package/dist/{types-Dx8PgO8a.d.mts → types-Bdi8Y7ll.d.mts} +3 -3
  184. package/dist/{types-DFU6M0OL.d.mts → types-DWU0Dmy9.d.mts} +3 -3
  185. package/dist/{types-BMpEizJP.d.mts → types-EodOhsjw.d.mts} +2 -2
  186. package/dist/{types-BhHdS6QP.d.mts → types-SOkNtcBq.d.mts} +2 -2
  187. package/dist/types3.d.mts +1 -1
  188. package/dist/types4.d.mts +2 -2
  189. package/dist/types5.d.mts +2 -2
  190. package/dist/types6.d.mts +1 -1
  191. package/dist/types7.d.mts +1 -1
  192. package/dist/{url-state-D1muieS7.d.mts → url-state-CwaRVyXR.d.mts} +2 -2
  193. package/dist/url-state.d.mts +1 -1
  194. package/dist/{useChain-D5LvupTW.d.mts → useChain-DXkKKe-J.d.mts} +4 -4
  195. package/dist/useChainAtCursor.d.mts +4 -4
  196. package/dist/useCodeChainFunctionPanel.d.mts +1 -1
  197. package/dist/useColumnConfiguration.d.mts +2 -2
  198. package/dist/useComboboxFooterAction.d.mts +1 -1
  199. package/dist/{useFilterStateV2-MH3zbQFr.d.mts → useFilterStateV2-Bzo27PR-.d.mts} +3 -3
  200. package/dist/useFilterStateV2.d.mts +1 -1
  201. package/dist/{useTableColumns-rLskKJhd.d.mts → useTableColumns-DdU0CLcU.d.mts} +2 -2
  202. package/dist/useTableColumns.d.mts +1 -1
  203. package/dist/useTruncation.d.mts +1 -1
  204. package/dist/utils.d.mts +1 -1
  205. package/dist/utils4.d.mts +2 -2
  206. package/dist/{validation-Csi2SJxm.d.mts → validation-D6NPueNk.d.mts} +3 -3
  207. package/dist/validation.d.mts +1 -1
  208. package/dist/validation2.d.mts +3 -3
  209. package/dist/{value-control-resolver-BoBFvQ_0.d.mts → value-control-resolver-C-UrXVlB.d.mts} +2 -2
  210. package/dist/value-control-resolver.d.mts +1 -1
  211. package/llms.txt +9 -1
  212. package/package.json +3 -1
  213. package/skills/ui-authoring/COMPONENTS.md +1 -1
  214. package/skills/ui-authoring/TOKENS.md +10 -0
  215. package/dist/DataTable-95ZxiPlV.mjs.map +0 -1
  216. package/dist/TableControls-DxSFqRwJ.mjs.map +0 -1
  217. /package/dist/{index-BYd3Btr1.d.mts → index-DztUdPP_.d.mts} +0 -0
  218. /package/dist/{index-IJ_PXwxI2.d.mts → index-uMNhmxEh.d.mts} +0 -0
@@ -28,10 +28,10 @@ import { n as Select, r as SelectHeader, t as Option } from "./Select-BJ9ACmRD.m
28
28
  import { s as Popover_default } from "./Popover-BcII7usF.mjs";
29
29
  import { i as Modal_default } from "./Modal-kjUptzg1.mjs";
30
30
  import { r as DateRangePicker, t as DatePicker } from "./DatePicker-D0cvVbzq.mjs";
31
- import { t as Toggle } from "./Toggle-BLdemkh_.mjs";
31
+ import { t as ListBox } from "./ListBox-eoyBHtnJ.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-C4B2qFgh.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";
@@ -47,8 +47,8 @@ import * as PopoverPrimitive from "@radix-ui/react-popover";
47
47
  import { DndContext, PointerSensor, closestCenter, pointerWithin, useSensor, useSensors } from "@dnd-kit/core";
48
48
  import { SortableContext, horizontalListSortingStrategy, useSortable } from "@dnd-kit/sortable";
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",
@@ -1507,598 +1507,155 @@ const Filters = ({ compact, filterResolver, ...props }) => {
1507
1507
  });
1508
1508
  };
1509
1509
  //#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
1510
+ //#region components/DataTable/TableControls/FiltersV2/operators.ts
1538
1511
  /**
1539
- * reorder returns a reordered version of the inputed list by swapping the element
1540
- * at startIndex with the one at endIndex
1512
+ * Operator vocabulary per field type. The UI uses this to populate the
1513
+ * operator dropdown in the Filter popover and to gate which operators are
1514
+ * reachable for a given field. `is_empty` / `is_not_empty` apply broadly;
1515
+ * everything else is type-specific.
1516
+ *
1517
+ * The first entry for each type is the default operator selected when a
1518
+ * pill is created from the picker.
1541
1519
  */
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;
1520
+ const OPERATORS_BY_TYPE = {
1521
+ string: [
1522
+ "is",
1523
+ "is_not",
1524
+ "contains",
1525
+ "does_not_contain",
1526
+ "is_empty",
1527
+ "is_not_empty"
1528
+ ],
1529
+ email: [
1530
+ "is",
1531
+ "is_not",
1532
+ "contains",
1533
+ "does_not_contain",
1534
+ "is_empty",
1535
+ "is_not_empty"
1536
+ ],
1537
+ choices: [
1538
+ "is",
1539
+ "is_not",
1540
+ "is_empty",
1541
+ "is_not_empty"
1542
+ ],
1543
+ multi_choices: [
1544
+ "is",
1545
+ "is_not",
1546
+ "is_empty",
1547
+ "is_not_empty"
1548
+ ],
1549
+ boolean: ["is"],
1550
+ integer: [
1551
+ "is",
1552
+ "is_not",
1553
+ "is_empty",
1554
+ "is_not_empty"
1555
+ ],
1556
+ float: [
1557
+ "is",
1558
+ "is_not",
1559
+ "is_empty",
1560
+ "is_not_empty"
1561
+ ],
1562
+ date: [
1563
+ "is",
1564
+ "before",
1565
+ "after",
1566
+ "between",
1567
+ "is_empty",
1568
+ "is_not_empty"
1569
+ ],
1570
+ array: [
1571
+ "contains",
1572
+ "does_not_contain",
1573
+ "is_empty",
1574
+ "is_not_empty"
1575
+ ],
1576
+ tags_conditions: ["is"],
1577
+ signals: ["is"],
1578
+ date_range: ["is"]
1579
+ };
1580
+ /**
1581
+ * Numeric comparison operators. Together with `is` (is equal to) they give
1582
+ * `integer` and `float` fields greater-than / less-than / equal-to filtering.
1583
+ * Inclusive bounds are deliberately not offered: "at least 5" is
1584
+ * `gt 5` OR `is 5`, built with the Rules editor.
1585
+ *
1586
+ * They are opt-in rather than part of the default numeric vocabulary above:
1587
+ * a host whose backend serializes them lists them in the field's
1588
+ * `allowedOperators` (e.g. `['is', ...NUMERIC_COMPARISON_OPERATORS]`).
1589
+ * Adding them to the default vocabulary would surface operators in every
1590
+ * existing numeric field whose host can't serialize them.
1591
+ */
1592
+ const NUMERIC_COMPARISON_OPERATORS = ["gt", "lt"];
1593
+ const NUMERIC_COMPARISON_OPERATOR_SET = new Set(NUMERIC_COMPARISON_OPERATORS);
1594
+ const NUMERIC_FIELD_TYPES = new Set(["integer", "float"]);
1595
+ function isNumericComparisonOperator(operator) {
1596
+ return NUMERIC_COMPARISON_OPERATOR_SET.has(operator);
1547
1597
  }
1548
1598
  /**
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.
1599
+ * Whether a comparison value is complete for a numeric field: a finite
1600
+ * number, and a safe integer when the field is an `integer`. Strings are
1601
+ * rejected — the numeric value controls always emit numbers, so a string
1602
+ * here is a corrupted or hand-crafted rule the backend can't compare.
1603
+ */
1604
+ function isCompleteNumericComparisonValue(value, fieldType) {
1605
+ if (typeof value !== "number" || !Number.isFinite(value)) return false;
1606
+ return fieldType === "integer" ? Number.isSafeInteger(value) : true;
1607
+ }
1608
+ /**
1609
+ * Carry `value` into a numeric comparison. Equality rules on numeric fields
1610
+ * tolerate strings (v1 URLs hydrate `'5'`), but a comparison is only complete
1611
+ * with a number, so a numeric string is converted — truncated on `integer`
1612
+ * fields, matching what `IntegerValueControl` emits — and anything else
1613
+ * becomes an empty draft rather than a committed rule validation rejects.
1614
+ */
1615
+ function numericComparisonValue(value, fieldType) {
1616
+ const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
1617
+ if (!Number.isFinite(parsed)) return "";
1618
+ const normalized = fieldType === "integer" ? Math.trunc(parsed) : parsed;
1619
+ return isCompleteNumericComparisonValue(normalized, fieldType) ? normalized : "";
1620
+ }
1621
+ /**
1622
+ * Whether `(fieldType, operator)` is a built-in numeric comparison — the
1623
+ * case validators and labels specialize. Comparisons on non-numeric types
1624
+ * (only reachable through `condition`) are not.
1625
+ */
1626
+ function isNumericComparison(operator, fieldType) {
1627
+ return fieldType != null && NUMERIC_FIELD_TYPES.has(fieldType) && isNumericComparisonOperator(operator);
1628
+ }
1629
+ /** Operators that have no value control (the operator itself is the predicate). */
1630
+ const VALUELESS_OPERATORS = new Set(["is_empty", "is_not_empty"]);
1631
+ /**
1632
+ * Operators whose value is a {@link DateRangeFilterValue} rather than the
1633
+ * scalar its field type would otherwise carry. The value control, the draft
1634
+ * seed, and every validator branch on this rather than on field type alone —
1635
+ * a `date` field carries an ISO day string for `is` / `before` / `after` and
1636
+ * a `{ startDate, endDate }` pair for `between`.
1637
+ */
1638
+ const RANGE_OPERATORS = new Set(["between"]);
1639
+ const TIME_COMPONENT$1 = /T\d{2}:\d{2}/;
1640
+ function getOperatorsForType(type) {
1641
+ return OPERATORS_BY_TYPE[type] ?? [];
1642
+ }
1643
+ /**
1644
+ * Effective operator vocabulary for a concrete field: its `allowedOperators`
1645
+ * allow-list, or the field type's full vocabulary when the allow-list is
1646
+ * absent or empty. An empty allow-list means "unconstrained" rather than
1647
+ * "no operators" — the latter would leave the field selectable in the picker
1648
+ * but incapable of producing a rule that passes validation.
1551
1649
  *
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.
1650
+ * Prefer this over {@link getOperatorsForType} anywhere a real field is in
1651
+ * hand — the popover's operator dropdown, `FieldRegistry`, validation — so a
1652
+ * field can never surface an operator the host application can't serialize.
1554
1653
  */
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;
1654
+ function getOperatorsForField(field) {
1655
+ if (field.condition != null) return field.condition.operators.map(({ operator }) => operator);
1656
+ const { allowedOperators } = field;
1657
+ if (allowedOperators == null || allowedOperators.length === 0) return getOperatorsForType(field.type);
1658
+ return allowedOperators;
2102
1659
  }
2103
1660
  function getDefaultOperatorForType(type) {
2104
1661
  const list = OPERATORS_BY_TYPE[type];
@@ -2291,25 +1848,6 @@ function reconcileValueForOperator(field, nextOperator, previousOperator, value)
2291
1848
  return value ?? emptyValueForRule(field, nextOperator);
2292
1849
  }
2293
1850
  //#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
1851
  //#region components/DataTable/TableControls/FiltersV2/format-date-value.ts
2314
1852
  const TIME_COMPONENT = /T\d{2}:\d{2}/;
2315
1853
  /**
@@ -2738,7 +2276,7 @@ const FILTER_SIZE_TOKENS = {
2738
2276
  const PopoverContent = styled(PopoverPrimitive.Content)`
2739
2277
  z-index: ${Z_INDICES.modalDropdown};
2740
2278
  `;
2741
- const PopoverCard = styled(Card_default)`
2279
+ const PopoverCard$1 = styled(Card_default)`
2742
2280
  width: ${({ $wide }) => $wide ? "520px" : "384px"};
2743
2281
  max-width: calc(100vw - ${Spacing[4]});
2744
2282
  margin-bottom: 0;
@@ -2972,7 +2510,7 @@ const AdvancedFilterRulesPopover = ({ registry, compound, onChange, trigger, ope
2972
2510
  event.preventDefault();
2973
2511
  handleOpenChange(false);
2974
2512
  },
2975
- children: /* @__PURE__ */ jsxs(PopoverCard, {
2513
+ children: /* @__PURE__ */ jsxs(PopoverCard$1, {
2976
2514
  elevated: true,
2977
2515
  $wide: wide,
2978
2516
  children: [/* @__PURE__ */ jsx(Header, { children: /* @__PURE__ */ jsx(Paragraph, {
@@ -3264,6 +2802,20 @@ function ChoicesValueControl({ field, operator, value, onChange, onCommit, ariaL
3264
2802
  });
3265
2803
  }
3266
2804
  //#endregion
2805
+ //#region components/DataTable/TableControls/FiltersV2/date-rule-intent.ts
2806
+ function dateRuleIntent(change, writesEnabled) {
2807
+ if (change.source === "clear") return { kind: "clear" };
2808
+ if (change.source === "calendar" || !writesEnabled) return change.range == null ? { kind: "clear" } : {
2809
+ kind: "absolute",
2810
+ range: change.range
2811
+ };
2812
+ return {
2813
+ kind: "relative",
2814
+ preset: change.preset,
2815
+ range: change.range
2816
+ };
2817
+ }
2818
+ //#endregion
3267
2819
  //#region components/DataTable/TableControls/FiltersV2/value-controls/DateRangeValueControl/DateRangeValueControl.tsx
3268
2820
  const CALENDAR_DATE = /^(\d{4}-\d{2}-\d{2})/;
3269
2821
  const DATE_ONLY$1 = /^\d{4}-\d{2}-\d{2}$/;
@@ -4500,722 +4052,1182 @@ function isFilterRuleValue(value) {
4500
4052
  return false;
4501
4053
  }
4502
4054
  /**
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.
4055
+ * Strip rules referencing unregistered `fieldId`s from a decoded state.
4056
+ * Pairs with `isFilterStateV2` for defense-in-depth: even after the
4057
+ * shape guard passes, a rule with a `fieldId` that doesn't exist in the
4058
+ * current resource's registry would silently render nothing in the UI
4059
+ * but still reach the adapter and ship to the backend. Pruning closes
4060
+ * that gap.
4061
+ *
4062
+ * Empty AND-groups created by pruning are also dropped, since the
4063
+ * adapter prunes them on serialize anyway and the editor UI hides
4064
+ * empty groups too.
4065
+ *
4066
+ * Skips its own work and returns the input unchanged when `registry`
4067
+ * is `undefined` — useful for callers that hydrate before the registry
4068
+ * is built.
4069
+ *
4070
+ * For valueless operators (`is_empty`, etc.) the value contract is
4071
+ * `null`. Non-valueless operators keep in-progress draft values (an empty
4072
+ * string / empty array) so a pill the user is mid-editing survives the
4073
+ * report/hydrate round-trip and stays visible — top-level and compound
4074
+ * rules are treated alike here, and the serialize boundary
4075
+ * (`hasCompleteValue`) is what drops incomplete rules from the request.
4076
+ */
4077
+ function pruneToRegistry(state, registry) {
4078
+ if (!registry) return state;
4079
+ const coerced = coerceBooleanRules(state, registry);
4080
+ return {
4081
+ topLevelRules: coerced.topLevelRules.filter((rule) => isRuleAllowed(rule, registry, { allowDraftValues: true })),
4082
+ compound: { groups: coerced.compound.groups.map((group) => ({ rules: group.rules.filter((rule) => isRuleAllowed(rule, registry, { allowDraftValues: true })) })).filter((group) => group.rules.length > 0) }
4083
+ };
4084
+ }
4085
+ /**
4086
+ * Normalizes boolean-field rules into the in-memory contract the new UI
4087
+ * and adapter expect — `is` / `is_not` + a real boolean `value`. Two
4088
+ * legacy shapes are coerced, both only for rules whose registered field
4089
+ * type is `boolean`:
4090
+ *
4091
+ * - Pre–value-control rules using the `is_true` / `is_false` operators
4092
+ * (from saved views) become `is` + boolean `value` so they survive
4093
+ * operator-vocabulary pruning and render in the new UI.
4094
+ * - v1-URL bookmarks hydrate query params as strings, so
4095
+ * `filter[custom][active]=true` arrives as `{ operator: 'is', value:
4096
+ * 'true' }`. The string is coerced back to a boolean so the adapter
4097
+ * emits the legacy `is_true` / `is_false` wire operator instead of
4098
+ * shipping the raw string (which would break old boolean bookmarks).
4099
+ * Non-`'true'`/`'false'` strings are left as-is and dropped later by
4100
+ * `isRuleAllowed`.
4101
+ */
4102
+ function coerceBooleanRule(rule, registry) {
4103
+ const field = registry.get(rule.fieldId);
4104
+ if (!field || field.condition != null || field.type !== "boolean") return rule;
4105
+ if (rule.operator === "is_true") return {
4106
+ ...rule,
4107
+ operator: "is",
4108
+ value: true
4109
+ };
4110
+ if (rule.operator === "is_false") return {
4111
+ ...rule,
4112
+ operator: "is",
4113
+ value: false
4114
+ };
4115
+ if (rule.operator === "is" || rule.operator === "is_not") {
4116
+ if (rule.value === "true") return {
4117
+ ...rule,
4118
+ value: true
4119
+ };
4120
+ if (rule.value === "false") return {
4121
+ ...rule,
4122
+ value: false
4123
+ };
4124
+ }
4125
+ return rule;
4126
+ }
4127
+ function coerceBooleanRules(state, registry) {
4128
+ return {
4129
+ topLevelRules: state.topLevelRules.map((r) => coerceBooleanRule(r, registry)),
4130
+ compound: { groups: state.compound.groups.map((g) => ({ rules: g.rules.map((r) => coerceBooleanRule(r, registry)) })) }
4131
+ };
4132
+ }
4133
+ function isDraftRuleValue(rule, registry) {
4134
+ const field = registry.get(rule.fieldId);
4135
+ if (!field) return false;
4136
+ const config = getConditionOperator(field, rule.operator);
4137
+ if (config != null) switch (config.valueType) {
4138
+ case "none":
4139
+ case "boolean": return false;
4140
+ case "string_array": return Array.isArray(rule.value) && rule.value.length === 0;
4141
+ default: return rule.value === "" || rule.value == null;
4142
+ }
4143
+ if (isValuelessOperator(rule.operator)) return false;
4144
+ if (isRangeOperator(rule.operator)) return !isCompleteDateRange(rule.value);
4145
+ switch (field.type) {
4146
+ case "multi_choices":
4147
+ case "tags_conditions":
4148
+ case "signals": return Array.isArray(rule.value) && rule.value.length === 0;
4149
+ case "date": return rule.value === "" || rule.value == null;
4150
+ case "date_range": return false;
4151
+ case "boolean": return false;
4152
+ default: return rule.value === "" || rule.value == null;
4153
+ }
4154
+ }
4155
+ function isRuleAllowed(rule, registry, options = { allowDraftValues: false }) {
4156
+ const field = registry.get(rule.fieldId);
4157
+ if (field == null) return false;
4158
+ if (!registry.operatorsFor(rule.fieldId).includes(rule.operator)) return false;
4159
+ if (field.condition != null) return hasCompleteConditionValue(rule, field) || options.allowDraftValues && isDraftRuleValue(rule, registry);
4160
+ if (isValuelessOperator(rule.operator)) return rule.value == null;
4161
+ if (options.allowDraftValues && isDraftRuleValue(rule, registry)) return true;
4162
+ if (isRangeOperator(rule.operator)) return isCompleteDateRange(rule.value);
4163
+ if (isNumericComparison(rule.operator, field.type)) return isCompleteNumericComparisonValue(rule.value, field.type);
4164
+ if (registry.get(rule.fieldId)?.type === "boolean") return typeof rule.value === "boolean";
4165
+ if (registry.get(rule.fieldId)?.type === "date") return typeof rule.value === "string" && rule.value.length > 0;
4166
+ if (registry.get(rule.fieldId)?.type === "multi_choices") {
4167
+ if (Array.isArray(rule.value)) return rule.value.length > 0;
4168
+ return typeof rule.value === "string" && rule.value.length > 0;
4169
+ }
4170
+ if (registry.get(rule.fieldId)?.type === "date_range") return isCompleteDateRange(rule.value);
4171
+ const conditionGroupType = registry.get(rule.fieldId)?.type;
4172
+ if (conditionGroupType === "tags_conditions" || conditionGroupType === "signals") return Array.isArray(rule.value) && rule.value.length > 0;
4173
+ return rule.value != null && typeof rule.value !== "boolean";
4174
+ }
4175
+ /**
4176
+ * Whether `rule` carries a complete value for its field type — the
4177
+ * serialize-boundary predicate `pruneEmptyRulesForSerialize` uses to drop
4178
+ * half-edited rules from the request (a value-requiring operator like
4179
+ * `is_not` with no value yet makes the backend reject the whole request).
4180
+ *
4181
+ * Deliberately stricter than `isRuleAllowed`'s hydration policy, which keeps
4182
+ * an empty string so an in-progress pill survives the state round-trip and
4183
+ * stays visible for the user to finish. Checks value completeness only — it
4184
+ * does not gate operator vocabulary or registry membership.
4185
+ */
4186
+ function hasCompleteValue(rule, registry) {
4187
+ const field = registry.get(rule.fieldId);
4188
+ if (field?.condition != null) return hasCompleteConditionValue(rule, field);
4189
+ if (isValuelessOperator(rule.operator)) return rule.value == null;
4190
+ const { value } = rule;
4191
+ if (value == null) return false;
4192
+ if (isRangeOperator(rule.operator)) return isCompleteDateRange(value);
4193
+ if (field != null && isNumericComparison(rule.operator, field.type)) return isCompleteNumericComparisonValue(value, field.type);
4194
+ switch (registry.get(rule.fieldId)?.type) {
4195
+ case "boolean": return typeof value === "boolean";
4196
+ case "date": return typeof value === "string" && value.length > 0;
4197
+ case "multi_choices": return Array.isArray(value) ? value.length > 0 : typeof value === "string" && value.length > 0;
4198
+ case "tags_conditions":
4199
+ case "signals": return Array.isArray(value) && value.length > 0;
4200
+ case "date_range": return isCompleteDateRange(value);
4201
+ default:
4202
+ if (typeof value === "string") return value.length > 0;
4203
+ if (Array.isArray(value)) return value.length > 0;
4204
+ return typeof value !== "boolean";
4205
+ }
4206
+ }
4207
+ //#endregion
4208
+ //#region components/DataTable/TableControls/FiltersV2/useFilterStateV2.ts
4209
+ const nextRuleId = () => `top-rule-${v4()}`;
4210
+ /**
4211
+ * Owns `FilterStateV2` for a filter surface and exposes a small mutator API.
4212
+ *
4213
+ * When `advancedRulesEnabled` is false, `compound.groups` is forced to
4214
+ * `[]`.
4215
+ *
4216
+ * Putting that invariant here (rather than in the components) keeps
4217
+ * `TableControls/FiltersV2` callers from accidentally leaking compound state when
4218
+ * the owning view has opted out.
4219
+ *
4220
+ * The mutator surface is deliberately small: top-level rules get
4221
+ * upsert/remove, the compound expression gets whole-value writes. That
4222
+ * matches how the UI emits changes — simple pills mutate one rule at a
4223
+ * time; the rules popover always emits a full compound expression as
4224
+ * one `onChange`.
4225
+ *
4226
+ * A pill the user has picked but not yet valued is held by the bar rather
4227
+ * than committed. Which pills those are is recorded when the picker mints
4228
+ * them rather than inferred: they are never reported, so incoming state never
4229
+ * describes one and hydration has nothing to reconcile against them. They
4230
+ * stay in the same row as the committed pills, so giving one a value doesn't
4231
+ * move it.
4232
+ */
4233
+ function useFilterStateV2(options) {
4234
+ const { initialState = EMPTY_FILTER_STATE, advancedRulesEnabled = true, registry } = options;
4235
+ const [internal, setInternal] = useState(() => ({
4236
+ all: pruneToRegistry(initialState, registry),
4237
+ heldIds: /* @__PURE__ */ new Set()
4238
+ }));
4239
+ const { all, heldIds } = internal;
4240
+ const lastHydratedState = useRef(all);
4241
+ useEffect(() => {
4242
+ const nextHydratedState = pruneToRegistry(initialState, registry);
4243
+ const hydrate = !isEqual(nextHydratedState, lastHydratedState.current);
4244
+ if (hydrate) lastHydratedState.current = nextHydratedState;
4245
+ setInternal((prev) => {
4246
+ const pruned = pruneHeldRules(prev, registry);
4247
+ return hydrate ? withHeldRules(nextHydratedState, pruned) : pruned;
4248
+ });
4249
+ }, [initialState, registry]);
4250
+ const state = useMemo(() => enforceAdvancedRulesEnabled(all, advancedRulesEnabled), [all, advancedRulesEnabled]);
4251
+ return {
4252
+ state,
4253
+ committedState: useMemo(() => heldIds.size === 0 ? state : {
4254
+ ...state,
4255
+ topLevelRules: state.topLevelRules.filter((rule) => !heldIds.has(rule.id))
4256
+ }, [state, heldIds]),
4257
+ advancedRulesEnabled,
4258
+ setState: useCallback((next) => {
4259
+ setInternal({
4260
+ all: pruneToRegistry(next, registry),
4261
+ heldIds: /* @__PURE__ */ new Set()
4262
+ });
4263
+ }, [registry]),
4264
+ setCompound: useCallback((compound) => {
4265
+ setInternal((prev) => ({
4266
+ ...prev,
4267
+ all: {
4268
+ ...prev.all,
4269
+ compound
4270
+ }
4271
+ }));
4272
+ }, []),
4273
+ reset: useCallback(() => {
4274
+ setInternal({
4275
+ all: {
4276
+ topLevelRules: [],
4277
+ compound: { groups: [] }
4278
+ },
4279
+ heldIds: /* @__PURE__ */ new Set()
4280
+ });
4281
+ }, []),
4282
+ addTopLevelRule: useCallback((rule) => {
4283
+ const id = nextRuleId();
4284
+ const added = {
4285
+ ...rule,
4286
+ id
4287
+ };
4288
+ setInternal((prev) => ({
4289
+ all: {
4290
+ ...prev.all,
4291
+ topLevelRules: [...prev.all.topLevelRules, added]
4292
+ },
4293
+ heldIds: registry.get(rule.fieldId)?.condition != null || isHeldRule(added, registry) ? withId(prev.heldIds, id) : prev.heldIds
4294
+ }));
4295
+ return id;
4296
+ }, [registry]),
4297
+ updateTopLevelRule: useCallback((id, update) => {
4298
+ setInternal((prev) => {
4299
+ const idx = prev.all.topLevelRules.findIndex((r) => r.id === id);
4300
+ const currentRule = prev.all.topLevelRules[idx];
4301
+ if (currentRule == null) return prev;
4302
+ const rule = typeof update === "function" ? update(currentRule) : update;
4303
+ const topLevelRules = [...prev.all.topLevelRules];
4304
+ topLevelRules[idx] = {
4305
+ ...rule,
4306
+ id
4307
+ };
4308
+ const heldIds = isHeldRule(rule, registry) ? withId(prev.heldIds, id) : withoutId(prev.heldIds, id);
4309
+ return {
4310
+ all: {
4311
+ ...prev.all,
4312
+ topLevelRules
4313
+ },
4314
+ heldIds
4315
+ };
4316
+ });
4317
+ }, [registry]),
4318
+ removeTopLevelRule: useCallback((id) => {
4319
+ setInternal((prev) => {
4320
+ const topLevelRules = prev.all.topLevelRules.filter((r) => r.id !== id);
4321
+ if (topLevelRules.length === prev.all.topLevelRules.length) return prev;
4322
+ return {
4323
+ all: {
4324
+ ...prev.all,
4325
+ topLevelRules
4326
+ },
4327
+ heldIds: prev.heldIds.has(id) ? withoutId(prev.heldIds, id) : prev.heldIds
4328
+ };
4329
+ });
4330
+ }, [])
4331
+ };
4332
+ }
4333
+ /**
4334
+ * A pill the picker minted that has no value yet. `null` and not "empty":
4335
+ * `''` and `[]` are values a user can choose, and a filter cleared to one is
4336
+ * still a filter.
4524
4337
  */
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
- };
4338
+ function isHeldRule(rule, registry) {
4339
+ if (registry.get(rule.fieldId)?.condition != null) return !hasCompleteValue(rule, registry);
4340
+ return !isValuelessOperator(rule.operator) && rule.value == null;
4341
+ }
4342
+ function heldRulesOf(state) {
4343
+ return state.heldIds.size === 0 ? [] : state.all.topLevelRules.filter((rule) => state.heldIds.has(rule.id));
4532
4344
  }
4533
4345
  /**
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`.
4346
+ * Lays incoming committed rules into the row, keeping the held pills where the
4347
+ * user left them: a held pill goes back after however many committed rules
4348
+ * preceded it before. An id the incoming state now uses is the parent's, so the
4349
+ * held pill of that id gives way rather than duplicating it.
4549
4350
  */
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
4351
+ function withHeldRules(committed, prev) {
4352
+ if (prev.heldIds.size === 0) return {
4353
+ all: committed,
4354
+ heldIds: prev.heldIds
4557
4355
  };
4558
- if (rule.operator === "is_false") return {
4559
- ...rule,
4560
- operator: "is",
4561
- value: false
4356
+ const incoming = committed.topLevelRules;
4357
+ const incomingIds = new Set(incoming.map((rule) => rule.id));
4358
+ const topLevelRules = [];
4359
+ const heldIds = /* @__PURE__ */ new Set();
4360
+ let taken = 0;
4361
+ let committedBefore = 0;
4362
+ prev.all.topLevelRules.forEach((rule) => {
4363
+ if (!prev.heldIds.has(rule.id)) {
4364
+ committedBefore += 1;
4365
+ return;
4366
+ }
4367
+ if (incomingIds.has(rule.id)) return;
4368
+ while (taken < committedBefore && taken < incoming.length) {
4369
+ topLevelRules.push(incoming[taken]);
4370
+ taken += 1;
4371
+ }
4372
+ topLevelRules.push(rule);
4373
+ heldIds.add(rule.id);
4374
+ });
4375
+ if (heldIds.size === 0) return {
4376
+ all: committed,
4377
+ heldIds
4562
4378
  };
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
- };
4379
+ while (taken < incoming.length) {
4380
+ topLevelRules.push(incoming[taken]);
4381
+ taken += 1;
4572
4382
  }
4573
- return rule;
4383
+ return {
4384
+ all: {
4385
+ ...committed,
4386
+ topLevelRules
4387
+ },
4388
+ heldIds
4389
+ };
4574
4390
  }
4575
- function coerceBooleanRules(state, registry) {
4391
+ /**
4392
+ * Drops held pills whose field or operator the registry no longer supports,
4393
+ * preserving the state's identity when there are none so an unchanged registry
4394
+ * can't loop the effect.
4395
+ */
4396
+ function pruneHeldRules(state, registry) {
4397
+ const droppedIds = new Set(heldRulesOf(state).filter((rule) => !registry.has(rule.fieldId) || !registry.operatorsFor(rule.fieldId).includes(rule.operator)).map((rule) => rule.id));
4398
+ if (droppedIds.size === 0) return state;
4399
+ const heldIds = new Set(state.heldIds);
4400
+ droppedIds.forEach((id) => heldIds.delete(id));
4576
4401
  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)) })) }
4402
+ all: {
4403
+ ...state.all,
4404
+ topLevelRules: state.all.topLevelRules.filter((rule) => !droppedIds.has(rule.id))
4405
+ },
4406
+ heldIds
4579
4407
  };
4580
4408
  }
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
- }
4409
+ function withId(ids, id) {
4410
+ return ids.has(id) ? ids : new Set(ids).add(id);
4602
4411
  }
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";
4412
+ function withoutId(ids, id) {
4413
+ if (!ids.has(id)) return ids;
4414
+ const next = new Set(ids);
4415
+ next.delete(id);
4416
+ return next;
4417
+ }
4418
+ function enforceAdvancedRulesEnabled(state, advancedRulesEnabled) {
4419
+ if (advancedRulesEnabled) return state;
4420
+ if (state.compound.groups.length === 0) return state;
4421
+ return {
4422
+ ...state,
4423
+ compound: { groups: [] }
4424
+ };
4622
4425
  }
4426
+ //#endregion
4427
+ //#region components/DataTable/TableControls/FiltersV2/FilterBar/FilterBar.tsx
4428
+ const Bar = styled.div`
4429
+ display: flex;
4430
+ align-items: center;
4431
+ gap: ${Spacing[2]};
4432
+ flex-wrap: nowrap;
4433
+ min-width: 0;
4434
+ padding: ${Spacing[2]} 0;
4435
+ `;
4436
+ const SCROLL_FADE_WIDTH = 48;
4437
+ const SCROLL_SETTLE_FALLBACK_MS = 400;
4438
+ const POPOVER_CLOSE_BUFFER_PX = 8;
4439
+ const PILL_FOCUS_RING_SPACE = 3;
4440
+ /**
4441
+ * Builds the `mask-image` gradient that fades the scroll track's edges.
4442
+ *
4443
+ * A `mask-image` (rather than a same-colored overlay painted on top) fades
4444
+ * the track's *entire rendered output* — pill backgrounds, borders, and any
4445
+ * focus ring that bleeds into the padding above — because masking
4446
+ * composites against the real pixels the element paints, instead of
4447
+ * relying on an opaque sibling layer to visually cover them. An overlay
4448
+ * approach only hides whatever happens to sit below it in paint order;
4449
+ * a mask fades everything, including outlines.
4450
+ */
4451
+ const buildEdgeMaskImage = (showLeftFade, showRightFade) => {
4452
+ 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%"})`;
4453
+ };
4454
+ const PillsScrollRegion = styled.div`
4455
+ display: flex;
4456
+ align-items: center;
4457
+ gap: ${Spacing[1]};
4458
+ flex: 0 1 auto;
4459
+ min-width: 0;
4460
+ width: max-content;
4461
+ max-width: 100%;
4462
+ `;
4463
+ const PillsScrollTrack = styled.div`
4464
+ display: flex;
4465
+ align-items: center;
4466
+ flex: 1 1 auto;
4467
+ min-width: 0;
4468
+ flex-wrap: nowrap;
4469
+ gap: ${Spacing[1]};
4470
+ overflow-x: auto;
4471
+ overflow-y: hidden;
4472
+ -webkit-overflow-scrolling: touch;
4473
+ scrollbar-width: none;
4474
+ padding: ${PILL_FOCUS_RING_SPACE}px;
4475
+ margin: -${PILL_FOCUS_RING_SPACE}px;
4476
+ -webkit-mask-image: ${({ $showLeftFade, $showRightFade }) => buildEdgeMaskImage($showLeftFade, $showRightFade)};
4477
+ mask-image: ${({ $showLeftFade, $showRightFade }) => buildEdgeMaskImage($showLeftFade, $showRightFade)};
4478
+ transition:
4479
+ -webkit-mask-image ${"200ms"} ease-out,
4480
+ mask-image ${"200ms"} ease-out;
4481
+
4482
+ & > * {
4483
+ flex: 0 0 auto;
4484
+ }
4485
+
4486
+ &::-webkit-scrollbar {
4487
+ display: none;
4488
+ }
4489
+ `;
4490
+ const Divider = styled.div`
4491
+ width: 1px;
4492
+ height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].controlHeight};
4493
+ background: ${({ theme }) => semanticColor(theme, "BorderSubtle", Color.BorderDivider)};
4494
+ flex: 0 0 auto;
4495
+ margin: 0 ${({ $flush }) => $flush ? 0 : Spacing[1]};
4496
+ `;
4497
+ const PickerSlot = styled.div`
4498
+ flex: 0 0 auto;
4499
+ `;
4500
+ const EmptyTrailingGroup = styled.div`
4501
+ display: flex;
4502
+ align-items: center;
4503
+ gap: ${Spacing[1]};
4504
+ flex: 0 0 auto;
4505
+ `;
4623
4506
  /**
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.
4507
+ * Returns the previous array whenever its contents still match, so the field
4508
+ * ids can be derived on every render without giving the picker a new array to
4509
+ * rebuild its whole option tree from. Editing a pill's value rewrites the
4510
+ * rules while leaving the fields in use unchanged.
4633
4511
  */
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
- }
4512
+ function useStableFieldIds(fieldIds) {
4513
+ const stable = useRef(fieldIds);
4514
+ const previous = stable.current;
4515
+ if (previous == null || fieldIds == null || previous.length !== fieldIds.length || fieldIds.some((id, index) => previous[index] !== id)) stable.current = fieldIds;
4516
+ return stable.current;
4517
+ }
4518
+ function FilterBar({ registry, initialState, onChange, advancedRulesEnabled = true, relativeDatesEnabled = false, dateTimesEnabled = false, singleUseFields = false, size = "md", valueControlResolver }) {
4519
+ const strings = usePDSStrings();
4520
+ const filterState = useFilterStateV2({
4521
+ registry,
4522
+ initialState,
4523
+ advancedRulesEnabled
4524
+ });
4525
+ const [editingRuleId, setEditingRuleId] = useState(null);
4526
+ const [rulesPopoverOpen, setRulesPopoverOpen] = useState(false);
4527
+ const pillNodesRef = useRef(/* @__PURE__ */ new Map());
4528
+ const onChangeRef = useRef(onChange);
4529
+ useEffect(() => {
4530
+ onChangeRef.current = onChange;
4531
+ }, [onChange]);
4532
+ const { committedState } = filterState;
4533
+ const committedRuleIds = useMemo(() => new Set(committedState.topLevelRules.map((rule) => rule.id)), [committedState.topLevelRules]);
4534
+ const lastReportedState = useRef(committedState);
4535
+ useEffect(() => {
4536
+ if (isEqual(lastReportedState.current, committedState)) return;
4537
+ lastReportedState.current = committedState;
4538
+ onChangeRef.current?.(committedState);
4539
+ }, [committedState]);
4540
+ const pendingEditRuleIdRef = useRef(null);
4541
+ const openedViaScrollIntoViewRef = useRef(false);
4542
+ const { addTopLevelRule } = filterState;
4543
+ const handlePickField = useCallback((field) => {
4544
+ const operator = registry.defaultOperatorFor(field.id);
4545
+ pendingEditRuleIdRef.current = addTopLevelRule({
4546
+ fieldId: field.id,
4547
+ operator,
4548
+ value: field.condition != null ? emptyValueForRule(field, operator) : null
4549
+ });
4550
+ }, [addTopLevelRule, registry]);
4551
+ const hasPills = filterState.state.topLevelRules.length > 0;
4552
+ const excludeFieldIds = useStableFieldIds(singleUseFields ? filterState.state.topLevelRules.map((rule) => rule.fieldId) : void 0);
4553
+ const pillsScrollRef = useRef(null);
4554
+ const { hasHorizontalOverflow, hasMoreScrollLeft, hasMoreScrollRight } = useScrollOverflow(pillsScrollRef, { enabled: hasPills });
4555
+ const ruleCount = filterState.state.topLevelRules.length;
4556
+ const previousRuleCountRef = useRef(ruleCount);
4557
+ useEffect(() => {
4558
+ const grew = ruleCount > previousRuleCountRef.current;
4559
+ previousRuleCountRef.current = ruleCount;
4560
+ if (!grew || pendingEditRuleIdRef.current == null) return;
4561
+ const openPendingRule = () => {
4562
+ openedViaScrollIntoViewRef.current = true;
4563
+ setEditingRuleId(pendingEditRuleIdRef.current);
4564
+ pendingEditRuleIdRef.current = null;
4565
+ };
4566
+ const track = pillsScrollRef.current;
4567
+ if (!track || !track.scrollTo) {
4568
+ openPendingRule();
4569
+ return;
4570
+ }
4571
+ const maxScrollLeft = Math.max(track.scrollWidth - track.clientWidth, 0);
4572
+ if (Math.abs(track.scrollLeft - maxScrollLeft) <= 1) {
4573
+ openPendingRule();
4574
+ return;
4575
+ }
4576
+ let settled = false;
4577
+ const finish = () => {
4578
+ if (settled) return;
4579
+ settled = true;
4580
+ track.removeEventListener("scrollend", finish);
4581
+ window.clearTimeout(fallbackTimer);
4582
+ openPendingRule();
4583
+ };
4584
+ track.addEventListener("scrollend", finish, { once: true });
4585
+ const fallbackTimer = window.setTimeout(finish, SCROLL_SETTLE_FALLBACK_MS);
4586
+ track.scrollTo({
4587
+ left: track.scrollWidth,
4588
+ behavior: "smooth"
4589
+ });
4590
+ return () => {
4591
+ track.removeEventListener("scrollend", finish);
4592
+ window.clearTimeout(fallbackTimer);
4593
+ };
4594
+ }, [ruleCount]);
4595
+ useEffect(() => {
4596
+ if (editingRuleId == null) return;
4597
+ const root = pillsScrollRef.current;
4598
+ const target = pillNodesRef.current.get(editingRuleId);
4599
+ if (!root || !target || typeof IntersectionObserver === "undefined") return;
4600
+ let skipInitialReport = openedViaScrollIntoViewRef.current;
4601
+ openedViaScrollIntoViewRef.current = false;
4602
+ const observer = new IntersectionObserver(([entry]) => {
4603
+ if (skipInitialReport) {
4604
+ skipInitialReport = false;
4605
+ return;
4606
+ }
4607
+ if (entry && entry.intersectionRatio < 1) setEditingRuleId(null);
4608
+ }, {
4609
+ root,
4610
+ threshold: 1,
4611
+ rootMargin: `0px ${POPOVER_CLOSE_BUFFER_PX}px 0px ${POPOVER_CLOSE_BUFFER_PX}px`
4612
+ });
4613
+ observer.observe(target);
4614
+ return () => observer.disconnect();
4615
+ }, [editingRuleId]);
4616
+ useLayoutEffect(() => {
4617
+ if (editingRuleId == null) return;
4618
+ pillNodesRef.current.get(editingRuleId)?.scrollIntoView?.({
4619
+ block: "nearest",
4620
+ inline: "nearest"
4621
+ });
4622
+ }, [editingRuleId, useMemo(() => editingRuleId == null ? null : filterState.state.topLevelRules.find((rule) => rule.id === editingRuleId)?.value ?? null, [editingRuleId, filterState.state.topLevelRules])]);
4623
+ const visibleCustomFieldParents = useMemo(() => deriveVisibleCustomFieldParents(registry, filterState.state.topLevelRules), [registry, filterState.state.topLevelRules]);
4624
+ return /* @__PURE__ */ jsx(FilterBarContext.Provider, {
4625
+ value: { size },
4626
+ children: /* @__PURE__ */ jsxs(Bar, {
4627
+ role: "toolbar",
4628
+ "aria-label": strings.filterBar,
4629
+ children: [filterState.advancedRulesEnabled ? /* @__PURE__ */ jsx(AdvancedFilterRulesPopover, {
4630
+ registry,
4631
+ dateTimesEnabled,
4632
+ compound: filterState.state.compound,
4633
+ open: rulesPopoverOpen,
4634
+ onOpenChange: setRulesPopoverOpen,
4635
+ onChange: (compound) => {
4636
+ filterState.setCompound(compound);
4637
+ },
4638
+ renderValueControl: (field, operator, value, onChange, relativeDate) => resolveValueControl({
4639
+ field,
4640
+ operator,
4641
+ value,
4642
+ relativeDate,
4643
+ onChange,
4644
+ layout: "inline",
4645
+ relativeDatesEnabled,
4646
+ dateTimesEnabled
4647
+ }, valueControlResolver),
4648
+ trigger: /* @__PURE__ */ jsx(RulesPill, {
4649
+ compound: filterState.state.compound,
4650
+ enabled: true,
4651
+ isOpen: rulesPopoverOpen
4652
+ })
4653
+ }) : null, hasPills ? /* @__PURE__ */ jsxs(Fragment$1, { children: [filterState.advancedRulesEnabled ? /* @__PURE__ */ jsx(Divider, {
4654
+ "aria-hidden": "true",
4655
+ $size: size
4656
+ }) : null, /* @__PURE__ */ jsxs(PillsScrollRegion, {
4657
+ "data-test": "filter-bar-pills-scroll-region",
4658
+ children: [/* @__PURE__ */ jsx(PillsScrollTrack, {
4659
+ ref: pillsScrollRef,
4660
+ "data-test": "filter-bar-pills-scroll-track",
4661
+ $showLeftFade: hasMoreScrollLeft,
4662
+ $showRightFade: hasHorizontalOverflow && hasMoreScrollRight,
4663
+ children: filterState.state.topLevelRules.map((rule) => {
4664
+ const field = registry.get(rule.fieldId);
4665
+ if (!field) return null;
4666
+ const optionLabels = registry.optionLabelsFor(field.id);
4667
+ const isOpen = editingRuleId === rule.id;
4668
+ return /* @__PURE__ */ jsx(FilterPopover, {
4669
+ field,
4670
+ operator: rule.operator,
4671
+ wide: isMinutePrecisionDate(field, rule.operator, dateTimesEnabled) && carriesDateRange(rule.operator, field.type),
4672
+ onChangeOperator: (op) => {
4673
+ const { relativeDate: _staleRelativeDate, ...rest } = rule;
4674
+ filterState.updateTopLevelRule(rule.id, {
4675
+ ...rest,
4676
+ operator: op,
4677
+ value: reconcileValueForOperator(field, op, rule.operator, rule.value)
4678
+ });
4679
+ },
4680
+ valueControlSlot: resolveValueControl({
4681
+ field,
4682
+ operator: rule.operator,
4683
+ value: rule.value,
4684
+ relativeDate: rule.relativeDate,
4685
+ onChange: (value, relativeDate) => {
4686
+ const { relativeDate: _staleRelativeDate, ...rest } = rule;
4687
+ filterState.updateTopLevelRule(rule.id, {
4688
+ ...rest,
4689
+ value,
4690
+ ...relativeDate ? { relativeDate } : {}
4691
+ });
4692
+ },
4693
+ onCommit: () => setEditingRuleId(null),
4694
+ relativeDatesEnabled,
4695
+ dateTimesEnabled
4696
+ }, valueControlResolver),
4697
+ onClose: () => setEditingRuleId(null),
4698
+ onApply: field.condition != null && !committedRuleIds.has(rule.id) && hasCompleteValue(rule, registry) ? () => filterState.updateTopLevelRule(rule.id, (currentRule) => currentRule) : void 0,
4699
+ open: isOpen,
4700
+ onOpenChange: (open) => setEditingRuleId(open ? rule.id : null),
4701
+ trigger: /* @__PURE__ */ jsx(FilterPill, {
4702
+ ref: (node) => {
4703
+ if (node) pillNodesRef.current.set(rule.id, node);
4704
+ else pillNodesRef.current.delete(rule.id);
4705
+ },
4706
+ field,
4707
+ rule: field.condition != null && !committedRuleIds.has(rule.id) ? null : rule,
4708
+ variant: isOpen ? "active" : "default",
4709
+ optionLabels,
4710
+ onDismiss: () => filterState.removeTopLevelRule(rule.id)
4711
+ })
4712
+ }, rule.id);
4713
+ })
4714
+ }), /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
4715
+ registry,
4716
+ onPickField: handlePickField,
4717
+ compact: true,
4718
+ visibleCustomFieldParents,
4719
+ excludeFieldIds
4720
+ }) })]
4721
+ })] }) : filterState.advancedRulesEnabled ? /* @__PURE__ */ jsxs(EmptyTrailingGroup, { children: [/* @__PURE__ */ jsx(Divider, {
4722
+ "aria-hidden": "true",
4723
+ $size: size,
4724
+ $flush: true
4725
+ }), /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
4726
+ registry,
4727
+ onPickField: handlePickField,
4728
+ visibleCustomFieldParents,
4729
+ excludeFieldIds
4730
+ }) })] }) : /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
4731
+ registry,
4732
+ onPickField: handlePickField,
4733
+ visibleCustomFieldParents,
4734
+ excludeFieldIds
4735
+ }) })]
4736
+ })
4737
+ });
4654
4738
  }
4655
- //#endregion
4656
- //#region components/DataTable/TableControls/FiltersV2/useFilterStateV2.ts
4657
- const nextRuleId = () => `top-rule-${v4()}`;
4658
4739
  /**
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.
4740
+ * The parent labels the picker's Fields submenu should keep visible,
4741
+ * derived from active `is` pills on scoping selector fields
4742
+ * ({@link FieldRegistry.narrowingSelectorFields}). Each selected option
4743
+ * value is a backend record-type/template id, resolved to its parent
4744
+ * label via {@link FieldRegistry.parentRecordLabelForId}.
4667
4745
  *
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`.
4746
+ * Returns `undefined` only when no `is` selector pill is active (show
4747
+ * everything). An active pill always returns an array, possibly empty:
4748
+ * selecting a scope with no custom fields narrows to *nothing*, not to
4749
+ * everything — conflating the two would leak other scopes' fields in.
4673
4750
  *
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.
4751
+ * Only `is` narrows; multiple pills union their parents.
4798
4752
  */
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;
4753
+ function deriveVisibleCustomFieldParents(registry, topLevelRules) {
4754
+ const selectorFields = registry.narrowingSelectorFields();
4755
+ if (selectorFields.length === 0 || topLevelRules.length === 0) return;
4756
+ const selectorIds = new Set(selectorFields.map((f) => f.id));
4757
+ const allowedParents = /* @__PURE__ */ new Set();
4758
+ let hasActiveSelectorRule = false;
4759
+ for (const rule of topLevelRules) {
4760
+ if (!selectorIds.has(rule.fieldId)) continue;
4761
+ if (rule.operator !== "is") continue;
4762
+ hasActiveSelectorRule = true;
4763
+ const values = Array.isArray(rule.value) ? rule.value : [rule.value];
4764
+ for (const value of values) {
4765
+ if (typeof value !== "string") continue;
4766
+ const parentLabel = registry.parentRecordLabelForId(value);
4767
+ if (parentLabel != null) allowedParents.add(parentLabel);
4819
4768
  }
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
4769
  }
4831
- return {
4832
- all: {
4833
- ...committed,
4834
- topLevelRules
4835
- },
4836
- heldIds
4837
- };
4770
+ return hasActiveSelectorRule ? Array.from(allowedParents) : void 0;
4838
4771
  }
4772
+ //#endregion
4773
+ //#region components/DataTable/dnd/StyledIconDrag.tsx
4774
+ const StyledIconDrag = styled(DragIcon$1)`
4775
+ display: ${({ $shouldHide = false }) => $shouldHide ? "none" : "inline"};
4776
+
4777
+ ${({ $horizontal = false }) => $horizontal ? css`
4778
+ transform: rotate(90deg);
4779
+ ` : ""}
4780
+
4781
+ ${({ $active = false, $hover = false }) => $active ? css`
4782
+ cursor: grabbing;
4783
+ opacity: 1;
4784
+ ` : $hover ? css`
4785
+ cursor: grab;
4786
+ opacity: 1;
4787
+ ` : css`
4788
+ cursor: normal;
4789
+ opacity: 0.4;
4790
+ `}
4791
+
4792
+ transition: opacity 0.2s ease-in-out;
4793
+
4794
+ path {
4795
+ stroke: ${({ theme }) => semanticColor(theme, "FgPrimary", Color.CoreSlate1000)};
4796
+ stroke-width: 1px;
4797
+ }
4798
+ `;
4799
+ //#endregion
4800
+ //#region components/DataTable/dnd/utils.ts
4839
4801
  /**
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.
4802
+ * reorder returns a reordered version of the inputed list by swapping the element
4803
+ * at startIndex with the one at endIndex
4843
4804
  */
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;
4805
+ function reorder(list, startIndex, endIndex) {
4806
+ const result = Array.from(list);
4807
+ const [removed] = result.splice(startIndex, 1);
4808
+ result.splice(endIndex, 0, removed);
4809
+ return result;
4865
4810
  }
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: [] }
4811
+ /**
4812
+ * Translates a drag result into indices into the full list. dnd reports positions among the
4813
+ * rendered rows, which diverge from the full list as soon as the view is filtered.
4814
+ *
4815
+ * Returns null when either end cannot be resolved, in which case the drag should be ignored
4816
+ * rather than applied at the wrong position.
4817
+ */
4818
+ function resolveDragIndices(listKeys, renderedKeys, draggableId, destinationIndex) {
4819
+ const destinationKey = renderedKeys[destinationIndex];
4820
+ if (destinationKey == null) return null;
4821
+ const from = listKeys.indexOf(draggableId);
4822
+ const to = listKeys.indexOf(destinationKey);
4823
+ return from === -1 || to === -1 ? null : {
4824
+ from,
4825
+ to
4872
4826
  };
4873
4827
  }
4874
4828
  //#endregion
4875
- //#region components/DataTable/TableControls/FiltersV2/FilterBar/FilterBar.tsx
4876
- const Bar = styled.div`
4829
+ //#region components/DataTable/TableControls/TableViewConfigurationDropdown.tsx
4830
+ const SHOWN_DROPPABLE_ID = "cases-view__configure-shown-columns";
4831
+ const startsCapitalized = (label) => {
4832
+ const first = label.charAt(0);
4833
+ return first !== "" && first === first.toUpperCase() && first !== first.toLowerCase();
4834
+ };
4835
+ const byLabel = (a, b) => {
4836
+ const aCapitalized = startsCapitalized(a.label);
4837
+ if (aCapitalized !== startsCapitalized(b.label)) return aCapitalized ? -1 : 1;
4838
+ return a.label.localeCompare(b.label, void 0, { numeric: true });
4839
+ };
4840
+ const DndSection = styled("div")`
4841
+ max-height: 400px;
4842
+
4843
+ overflow: scroll;
4844
+ ::-webkit-scrollbar {
4845
+ display: none;
4846
+ }
4847
+ `;
4848
+ const DndItemWrapper = styled("div")`
4849
+ background: white;
4850
+ ${semanticOnly((tokens) => `background: ${tokens.SurfacePrimary};`)}
4851
+ width: 100%;
4852
+ border-bottom: 1px solid ${({ theme }) => semanticColor(theme, "BorderSubtle", theme.color.border.secondary)};
4853
+ padding: ${Spacing[2]} ${Spacing[2]};
4854
+
4877
4855
  display: flex;
4856
+ flex-direction: row;
4878
4857
  align-items: center;
4879
- gap: ${Spacing[2]};
4880
- flex-wrap: nowrap;
4881
- min-width: 0;
4882
- padding: ${Spacing[2]} 0;
4858
+ justify-content: left;
4883
4859
  `;
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%"})`;
4860
+ const SuffixContainer = styled("div")`
4861
+ padding-left: ${Spacing[2]};
4862
+ margin-left: auto;
4863
+ align-self: center;
4864
+ `;
4865
+ const SearchContainer = styled.div`
4866
+ display: flex;
4867
+ `;
4868
+ const StyledSearchInput = styled(SearchInput)`
4869
+ width: 100%;
4870
+ `;
4871
+ const PopoverCard = styled(Card_default)`
4872
+ width: 300px;
4873
+ padding: ${Spacing[4]};
4874
+ `;
4875
+ const TriggerIconButton = forwardRef(function TriggerIconButton({ disabled, ...elemProps }, ref) {
4876
+ const strings = usePDSStrings();
4877
+ return /* @__PURE__ */ jsx(Button, {
4878
+ disabled,
4879
+ width: "icon",
4880
+ variant: "basic",
4881
+ size: "md",
4882
+ ...elemProps,
4883
+ ref,
4884
+ "data-tour-target": "table-properties-button",
4885
+ "aria-label": strings.tableProperties,
4886
+ children: /* @__PURE__ */ jsx(SettingsIcon, {})
4887
+ });
4888
+ });
4889
+ const TooltipButtonWrapper = styled("div")`
4890
+ display: inline-block;
4891
+ `;
4892
+ const TableViewConfigurationDropdown = ({ disabled = false, disabledTooltipMessage, size = "md", variant = "basic", ...props }) => {
4893
+ const strings = usePDSStrings();
4894
+ return disabled ? /* @__PURE__ */ jsx(Tooltip, {
4895
+ trigger: /* @__PURE__ */ jsx(TooltipButtonWrapper, { children: /* @__PURE__ */ jsx(TriggerIconButton, {
4896
+ disabled: true,
4897
+ size,
4898
+ variant
4899
+ }) }),
4900
+ children: disabledTooltipMessage ?? strings.configureViewPermissionDenied
4901
+ }) : /* @__PURE__ */ jsxs(Popover_default, { children: [/* @__PURE__ */ jsx(Popover_default.Trigger, {
4902
+ asChild: true,
4903
+ children: /* @__PURE__ */ jsx(TriggerIconButton, {
4904
+ disabled: false,
4905
+ size,
4906
+ variant
4907
+ })
4908
+ }), /* @__PURE__ */ jsx(Popover_default.Content, {
4909
+ style: { zIndex: Z_INDICES.dropdown },
4910
+ sideOffset: 8,
4911
+ side: "bottom",
4912
+ align: "end",
4913
+ children: /* @__PURE__ */ jsx(PopoverCard, {
4914
+ elevated: true,
4915
+ children: /* @__PURE__ */ jsx(TableViewConfiguration, { ...props })
4916
+ })
4917
+ })] });
4918
+ };
4919
+ const TableViewConfiguration = ({ columnConfiguration, attributeOptions, updateColumnConfiguration, getColumnDisabled, sortByRelevance = false }) => {
4920
+ const strings = usePDSStrings();
4921
+ const [searchTerm, setSearchTerm] = useState("");
4922
+ const fuse = useMemo(() => {
4923
+ return new Fuse(attributeOptions, sortByRelevance ? {
4924
+ shouldSort: true,
4925
+ threshold: .3,
4926
+ ignoreLocation: true,
4927
+ includeScore: true,
4928
+ minMatchCharLength: 1,
4929
+ keys: [{
4930
+ name: "label",
4931
+ weight: 1
4932
+ }]
4933
+ } : {
4934
+ shouldSort: false,
4935
+ threshold: .4,
4936
+ location: 0,
4937
+ distance: 100,
4938
+ minMatchCharLength: 1,
4939
+ keys: [{
4940
+ name: "label",
4941
+ weight: 1
4942
+ }]
4943
+ });
4944
+ }, [attributeOptions, sortByRelevance]);
4945
+ const allOptions = useMemo(() => union(attributeOptions, columnConfiguration), [attributeOptions, columnConfiguration]);
4946
+ const filteredSearchOptions = useMemo(() => {
4947
+ if (searchTerm === "") return allOptions;
4948
+ const results = fuse.search(searchTerm);
4949
+ if (!sortByRelevance) return results.map(({ item }) => item);
4950
+ return [...results].sort((a, b) => (a.score ?? 0) - (b.score ?? 0) || byLabel(a.item, b.item)).map(({ item }) => item);
4951
+ }, [
4952
+ allOptions,
4953
+ fuse,
4954
+ searchTerm,
4955
+ sortByRelevance
4956
+ ]);
4957
+ const hiddenAttributes = useMemo(() => {
4958
+ const columnConfigurationKeys = new Set(columnConfiguration.map((c) => c.key));
4959
+ const hidden = filteredSearchOptions.filter((attribute) => !columnConfigurationKeys.has(attribute.key));
4960
+ if (!sortByRelevance || searchTerm !== "") return hidden;
4961
+ const [systemColumns, customFields] = partition(hidden, (a) => !a.isCustomField);
4962
+ return [...systemColumns, ...[...customFields].sort(byLabel)];
4963
+ }, [
4964
+ columnConfiguration,
4965
+ filteredSearchOptions,
4966
+ sortByRelevance,
4967
+ searchTerm
4968
+ ]);
4969
+ const filteredShownAttributes = useMemo(() => {
4970
+ return columnConfiguration.filter((column) => filteredSearchOptions.map((o) => o.key).includes(column.key));
4971
+ }, [columnConfiguration, filteredSearchOptions]);
4972
+ const handleDragEnd = (result, _provided) => {
4973
+ const { destination, source } = result;
4974
+ if (!destination || destination.droppableId === source.droppableId && destination.index === source.index) return;
4975
+ const indices = resolveDragIndices(columnConfiguration.map((c) => c.key), filteredShownAttributes.map((c) => c.key), result.draggableId, destination.index);
4976
+ if (!indices) return;
4977
+ updateColumnConfiguration(reorder(columnConfiguration, indices.from, indices.to));
4978
+ };
4979
+ const generateShownAttributesToggle = (column) => () => {
4980
+ updateColumnConfiguration(columnConfiguration.filter((a) => a.key !== column.key));
4981
+ };
4982
+ const generateHiddenAttributesToggle = (column) => () => {
4983
+ updateColumnConfiguration(columnConfiguration.concat({
4984
+ key: column.key,
4985
+ label: column.label
4986
+ }));
4987
+ };
4988
+ const shownColumnIds = useMemo(() => {
4989
+ return columnConfiguration.map((c) => c.key);
4990
+ }, [columnConfiguration]);
4991
+ const shownDndItemProps = filteredShownAttributes.map((column, index) => {
4992
+ const isDisabledByDefault = columnConfiguration.length === 1;
4993
+ const isDisabledByCustom = getColumnDisabled?.(column.key, shownColumnIds) ?? false;
4994
+ const shouldDisableDragForColumn = isDisabledByCustom && index === filteredShownAttributes.length - 1;
4995
+ return {
4996
+ column,
4997
+ index,
4998
+ isDisabled: isDisabledByDefault || isDisabledByCustom,
4999
+ isToggled: true,
5000
+ shouldDisableDrag: shouldDisableDragForColumn,
5001
+ handleToggle: generateShownAttributesToggle(column)
5002
+ };
5003
+ });
5004
+ const hiddenDndItemProps = hiddenAttributes.map((column, index) => {
5005
+ return {
5006
+ column,
5007
+ index,
5008
+ isToggled: false,
5009
+ shouldDisableDrag: true,
5010
+ handleToggle: generateHiddenAttributesToggle(column)
5011
+ };
5012
+ });
5013
+ return /* @__PURE__ */ jsxs("div", { children: [
5014
+ /* @__PURE__ */ jsx(Label$1, {
5015
+ size: "lg",
5016
+ weight: "bold",
5017
+ children: strings.configureColumns
5018
+ }),
5019
+ /* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
5020
+ /* @__PURE__ */ jsx(SearchContainer, { children: /* @__PURE__ */ jsx(StyledSearchInput, {
5021
+ title: null,
5022
+ value: searchTerm,
5023
+ handleInput: setSearchTerm,
5024
+ size: "md",
5025
+ placeholder: strings.searchColumns,
5026
+ dataTest: "checklist-item-panel-item-table__search"
5027
+ }) }),
5028
+ /* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
5029
+ shownDndItemProps.length > 0 || hiddenDndItemProps.length > 0 ? /* @__PURE__ */ jsxs(DndSection, { children: [
5030
+ shownDndItemProps.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Paragraph, { children: strings.shownColumns }), /* @__PURE__ */ jsx(DragDropContext, {
5031
+ onDragEnd: handleDragEnd,
5032
+ children: /* @__PURE__ */ jsx(Droppable, {
5033
+ droppableId: SHOWN_DROPPABLE_ID,
5034
+ renderClone: (provided, _snapshot, rubric) => {
5035
+ return /* @__PURE__ */ jsx("div", {
5036
+ ...provided.draggableProps,
5037
+ ...provided.dragHandleProps,
5038
+ ref: provided.innerRef,
5039
+ children: /* @__PURE__ */ jsx(AttributeDnd, { ...shownDndItemProps[rubric.source.index] })
5040
+ });
5041
+ },
5042
+ children: (provided) => {
5043
+ return /* @__PURE__ */ jsxs("div", {
5044
+ ref: provided.innerRef,
5045
+ ...provided.droppableProps,
5046
+ children: [shownDndItemProps.map((props, idx) => {
5047
+ return /* @__PURE__ */ jsx(Draggable, {
5048
+ draggableId: props.column.key,
5049
+ index: idx,
5050
+ isDragDisabled: props.shouldDisableDrag,
5051
+ children: (provided) => {
5052
+ return /* @__PURE__ */ jsx("div", {
5053
+ ref: provided.innerRef,
5054
+ ...provided.draggableProps,
5055
+ ...provided.dragHandleProps,
5056
+ children: /* @__PURE__ */ jsx(AttributeDnd, { ...props })
5057
+ });
5058
+ }
5059
+ }, `${props.column.key}`);
5060
+ }), provided.placeholder]
5061
+ });
5062
+ }
5063
+ })
5064
+ })] }),
5065
+ /* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
5066
+ hiddenDndItemProps.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Paragraph, { children: strings.hiddenColumns }), hiddenDndItemProps.map((props) => /* @__PURE__ */ jsx(AttributeDnd, { ...props }, props.column.key))] })
5067
+ ] }) : /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Paragraph, { children: strings.noMatchingColumns }) })
5068
+ ] });
4901
5069
  };
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`
5070
+ const OverflowText = styled("div")`
5071
+ max-width: 160px;
5072
+ flex-wrap: wrap;
4949
5073
  display: flex;
4950
- align-items: center;
4951
- gap: ${Spacing[1]};
4952
- flex: 0 0 auto;
4953
5074
  `;
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 }) {
5075
+ const AttributeDnd = ({ column, index, isDisabled = false, isToggled, shouldDisableDrag, handleToggle }) => {
4967
5076
  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);
5077
+ return /* @__PURE__ */ jsxs(DndItemWrapper, { children: [
5078
+ /* @__PURE__ */ jsx(StyledIconDrag, {
5079
+ $active: true,
5080
+ $shouldHide: shouldDisableDrag
5081
+ }),
5082
+ /* @__PURE__ */ jsx(OverflowText, { children: /* @__PURE__ */ jsx(Paragraph, {
5083
+ color: "primary",
5084
+ size: "sm",
5085
+ children: column.label
5086
+ }) }),
5087
+ /* @__PURE__ */ jsx(SuffixContainer, { children: /* @__PURE__ */ jsx(Toggle, {
5088
+ value: isToggled,
5089
+ disabled: isDisabled,
5090
+ onChange: handleToggle,
5091
+ "aria-label": isToggled ? strings.hideColumn(column.label) : strings.showColumn(column.label)
5092
+ }) })
5093
+ ] }, `${column.key}-${index}`);
5094
+ };
5095
+ //#endregion
5096
+ //#region components/DataTable/TableControls/TableControls.tsx
5097
+ const EMPTY_FILTERS = [];
5098
+ const resolveFilterArea = (filterPanel, filtersV2Panel, configuredFilterPanel) => filterPanel ?? filtersV2Panel ?? configuredFilterPanel;
5099
+ const hasNewFilterId = (currentIds, previousIds) => {
5100
+ const previous = new Set(previousIds.split("\0").filter(Boolean));
5101
+ return currentIds.split("\0").some((id) => id !== "" && !previous.has(id));
5102
+ };
5103
+ 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 }) => {
5104
+ const filtersId = useId();
5105
+ const enabledFilters = useMemo(() => filtersV2 != null ? EMPTY_FILTERS : filters.filter((filter) => filter.enabled !== false), [filters, filtersV2]);
5106
+ const [activeFilters, setActiveFilters] = useState(() => getActiveFilters(enabledFilters, [], true, filterResolver));
5107
+ const generatedFilterPanel = enabledFilters.length > 0 ? /* @__PURE__ */ jsx(TableControlsProvider, {
5108
+ filters: enabledFilters,
5109
+ activeFilters,
5110
+ setActiveFilters,
5111
+ children: /* @__PURE__ */ jsx(Filters, {
5112
+ id: filtersId,
5113
+ compact,
5114
+ filterResolver
5115
+ })
5116
+ }) : void 0;
5117
+ const usesFiltersV2 = filterPanel == null && filtersV2 != null;
5118
+ const resolvedFilterPanel = resolveFilterArea(filterPanel, filtersV2 != null ? /* @__PURE__ */ jsx("div", {
5119
+ id: filtersId,
5120
+ children: /* @__PURE__ */ jsx(FilterBar, {
5121
+ ...filtersV2,
5122
+ size: filtersV2.size ?? (compact ? "sm" : "md")
5123
+ })
5124
+ }) : void 0, generatedFilterPanel);
5125
+ const [internalShowFilters, internalSetShowFilters] = useState(defaultShowFilters ?? (filterPanel == null && (usesFiltersV2 || activeFilters.length > 0)));
5005
5126
  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]);
5127
+ setActiveFilters((current) => synchronizeActiveFilters(enabledFilters, current, filterResolver));
5128
+ }, [enabledFilters, filterResolver]);
5129
+ const activeFilterIds = enabledFilters.filter((filter) => isFilterActive(filter, filterResolver)).map((filter) => filter.id).sort().join("\0");
5130
+ const configuredFilterIds = activeFilters.flatMap((filter) => filter.config ? [filter.config.id] : []).sort().join("\0");
5131
+ const previousActiveFilterIds = useRef(activeFilterIds);
5132
+ const previousConfiguredFilterIds = useRef(configuredFilterIds);
5133
+ const previouslyUsedFiltersV2 = useRef(usesFiltersV2);
5043
5134
  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
- }) })]
5135
+ const becameActive = hasNewFilterId(activeFilterIds, previousActiveFilterIds.current);
5136
+ const newlyConfigured = hasNewFilterId(configuredFilterIds, previousConfiguredFilterIds.current);
5137
+ const enabledFiltersV2 = usesFiltersV2 && !previouslyUsedFiltersV2.current;
5138
+ previousActiveFilterIds.current = activeFilterIds;
5139
+ previousConfiguredFilterIds.current = configuredFilterIds;
5140
+ previouslyUsedFiltersV2.current = usesFiltersV2;
5141
+ if (defaultShowFilters == null && filterPanel == null && (enabledFiltersV2 || !usesFiltersV2 && (becameActive || newlyConfigured))) internalSetShowFilters(true);
5142
+ }, [
5143
+ activeFilterIds,
5144
+ configuredFilterIds,
5145
+ defaultShowFilters,
5146
+ filterPanel,
5147
+ usesFiltersV2
5148
+ ]);
5149
+ const showFilters = controlledShowFilters != null ? controlledShowFilters : internalShowFilters;
5150
+ const setShowFilters = controlledSetShowFilters != null ? controlledSetShowFilters : internalSetShowFilters;
5151
+ const enhancedPropertiesButton = useMemo(() => {
5152
+ if (!columnConfigurationProps && !propertiesButton) return;
5153
+ const elements = [];
5154
+ if (propertiesButton) elements.push(propertiesButton);
5155
+ if (columnConfigurationProps) elements.push(/* @__PURE__ */ jsx(TableViewConfigurationDropdown, {
5156
+ disabled: false,
5157
+ ...columnConfigurationProps
5158
+ }, "column-config"));
5159
+ if (elements.length === 1) return elements[0];
5160
+ return /* @__PURE__ */ jsx(StyledEnhancedPropertiesButton, { children: elements });
5161
+ }, [columnConfigurationProps, propertiesButton]);
5162
+ return /* @__PURE__ */ jsx(Container, {
5163
+ className,
5164
+ children: /* @__PURE__ */ jsx(ControlBar, {
5165
+ showViews: compact ? false : showViews,
5166
+ views: compact ? void 0 : views,
5167
+ hiddenViewIds: compact ? void 0 : hiddenViewIds,
5168
+ onSelectView: compact ? void 0 : onSelectView,
5169
+ currentView: compact ? void 0 : currentView,
5170
+ onAddView: compact ? void 0 : onAddView,
5171
+ onDeleteView: compact ? void 0 : onDeleteView,
5172
+ onDuplicateView: compact ? void 0 : onDuplicateView,
5173
+ onViewNameChange: compact ? void 0 : onViewNameChange,
5174
+ onReorderViews: compact ? void 0 : onReorderViews,
5175
+ onViewHiddenChange: compact ? void 0 : onViewHiddenChange,
5176
+ onSaveView: compact ? void 0 : onSaveView,
5177
+ ...!compact && onSetDefaultView ? {
5178
+ onSetDefaultView,
5179
+ onUnsetDefaultView
5180
+ } : {
5181
+ onSetDefaultView: void 0,
5182
+ onUnsetDefaultView: void 0
5183
+ },
5184
+ isViewDirty,
5185
+ canEditViews: compact ? false : canEditViews,
5186
+ showFilters: showFilters && (showFiltersAlways || !isFilterPanelLoading),
5187
+ setShowFilters: (resolvedFilterPanel != null || isFilterPanelLoading) && !showFiltersAlways ? setShowFilters : void 0,
5188
+ filtersId,
5189
+ searchProps,
5190
+ propertiesButton: enhancedPropertiesButton,
5191
+ tableActions,
5192
+ isLoading,
5193
+ isFilterPanelLoading,
5194
+ selectedRows,
5195
+ bulkActions,
5196
+ onClearSelection,
5197
+ title,
5198
+ "data-tour-target": dataTourTarget,
5199
+ presetViews: compact ? void 0 : presetViews,
5200
+ compact,
5201
+ onError,
5202
+ filterPanel: resolvedFilterPanel
5184
5203
  })
5185
5204
  });
5186
- }
5205
+ };
5206
+ const Container = styled(Stack)`
5207
+ margin-bottom: ${Spacing[2]};
5208
+ `;
5209
+ const StyledEnhancedPropertiesButton = styled.div`
5210
+ display: flex;
5211
+ gap: ${Spacing[1]};
5212
+ `;
5213
+ //#endregion
5214
+ //#region components/DataTable/TableControls/FiltersV2/date-range-rule-value.ts
5187
5215
  /**
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.
5216
+ * The absolute bounds a rule should be queried with, or `null` when it does not
5217
+ * carry a date range.
5198
5218
  *
5199
- * Only `is` narrows; multiple pills union their parents.
5219
+ * Relative metadata wins over the stored snapshot: the snapshot is a picture of
5220
+ * the window when it was saved, and the point of a relative rule is that the
5221
+ * window moves. The operator gate is part of this rather than the caller's job —
5222
+ * metadata only ever describes a range, so resolving it onto a scalar rule
5223
+ * would put a `{ startDate, endDate }` object where the wire expects a day.
5200
5224
  */
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;
5225
+ function resolveDateRangeRuleValue(rule, fieldType) {
5226
+ if (!carriesDateRange(rule.operator, fieldType)) return null;
5227
+ const relative = parseDateFilterValue(rule.relativeDate);
5228
+ if (relative?.kind === "relative") return resolveDateFilterValue(relative);
5229
+ const parsed = parseDateFilterValue(rule.value);
5230
+ return parsed ? resolveDateFilterValue(parsed) : null;
5219
5231
  }
5220
5232
  //#endregion
5221
5233
  //#region components/DataTable/TableControls/FiltersV2/FilterBar/index.ts
@@ -5490,6 +5502,6 @@ function decodeBase64(input) {
5490
5502
  return decodeURIComponent(escape(atob(input)));
5491
5503
  }
5492
5504
  //#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 };
5505
+ 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
5506
 
5495
- //# sourceMappingURL=TableControls-DxSFqRwJ.mjs.map
5507
+ //# sourceMappingURL=TableControls-n16XKO9S.mjs.map