@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.
- package/CHANGELOG.md +13 -0
- package/dist/AdvancedFilterRuleRow.d.mts +2 -2
- package/dist/AdvancedFilterRulesPopover.d.mts +2 -2
- package/dist/ArrayValueControl.d.mts +1 -1
- package/dist/BooleanValueControl.d.mts +1 -1
- package/dist/{Calendar-DAfQCLOJ.d.mts → Calendar-CkjYG7Mj.d.mts} +1 -1
- package/dist/Calendar.d.mts +1 -1
- package/dist/{CheckboxGroupField-BYB4OBlW.d.mts → CheckboxGroupField-4dc7YXJY.d.mts} +2 -2
- package/dist/CheckboxGroupField.d.mts +1 -1
- package/dist/ChoicesValueControl.d.mts +1 -1
- package/dist/{Combobox-DH5ycYlG.d.mts → Combobox-Bm2eSg56.d.mts} +3 -3
- package/dist/Combobox.d.mts +2 -2
- package/dist/{ComboboxBase-BQQSKUBU.d.mts → ComboboxBase-B7ZcqU9p.d.mts} +2 -2
- package/dist/ComboboxBase.d.mts +1 -1
- package/dist/{ComboboxMultiSelect-Da3Gv1k5.d.mts → ComboboxMultiSelect-BkdRrH6K.d.mts} +2 -2
- package/dist/ComboboxMultiSelect.d.mts +1 -1
- package/dist/{ComboboxSingleSelect-BUDovONU.d.mts → ComboboxSingleSelect-DIBRZmvS.d.mts} +3 -3
- package/dist/ComboboxSingleSelect.d.mts +1 -1
- package/dist/{CompactBar-D12R7Nm2.d.mts → CompactBar-CfrrZ69k.d.mts} +1 -1
- package/dist/CompactBar.d.mts +1 -1
- package/dist/{DataTable-fZ7SNbIH.d.mts → DataTable--_f1Obhj.d.mts} +7 -5
- package/dist/{DataTable-95ZxiPlV.mjs → DataTable-BoAAKhVH.mjs} +3 -3
- package/dist/DataTable-BoAAKhVH.mjs.map +1 -0
- package/dist/DataTable.d.mts +1 -1
- package/dist/{DateField-DLz13Ksl.d.mts → DateField-CwSq77Ri.d.mts} +1 -1
- package/dist/DateField.d.mts +1 -1
- package/dist/{DatePicker-DYVWmbrb.d.mts → DatePicker-DRhY_0st.d.mts} +1 -1
- package/dist/DatePicker.d.mts +1 -1
- package/dist/{DateRangePicker-Be8TYtjz.d.mts → DateRangePicker-CLuBLF1q.d.mts} +2 -2
- package/dist/DateRangePicker.d.mts +1 -1
- package/dist/DateRangeValueControl.d.mts +2 -2
- package/dist/DateValueControl.d.mts +1 -1
- package/dist/DropdownButton.d.mts +1 -1
- package/dist/{DropdownMenu-DAa2kNrt.d.mts → DropdownMenu-Bie2cWjb.d.mts} +2 -2
- package/dist/DropdownMenu.d.mts +1 -1
- package/dist/{FieldRegistry-d5tGpSch.d.mts → FieldRegistry-B5E-GN91.d.mts} +2 -2
- package/dist/FieldRegistry.d.mts +1 -1
- package/dist/{FilterBar-Bh18VyOK.d.mts → FilterBar-BQWpTi2b.d.mts} +4 -4
- package/dist/FilterBar.d.mts +1 -1
- package/dist/FilterPickerMenu.d.mts +2 -2
- package/dist/FilterPill.d.mts +1 -1
- package/dist/FilterPopover.d.mts +1 -1
- package/dist/FloatValueControl.d.mts +1 -1
- package/dist/{FormField-cSG93kuo.mjs → FormField-C4B2qFgh.mjs} +6 -6
- package/dist/{FormField-cSG93kuo.mjs.map → FormField-C4B2qFgh.mjs.map} +1 -1
- package/dist/HighlightedText.d.mts +1 -1
- package/dist/IndexTableLayout.d.mts +3 -3
- package/dist/InlineCombobox.d.mts +1 -1
- package/dist/InlineInput.d.mts +1 -1
- package/dist/IntegerValueControl.d.mts +1 -1
- package/dist/{ListBox-Bj5c626-.d.mts → ListBox-DIk8suJM.d.mts} +2 -2
- package/dist/{ListBox-C80J7DCG.mjs → ListBox-eoyBHtnJ.mjs} +1 -1
- package/dist/{ListBox-C80J7DCG.mjs.map → ListBox-eoyBHtnJ.mjs.map} +1 -1
- package/dist/ListBox.d.mts +1 -1
- package/dist/{ListBoxField-DsJRabgE.d.mts → ListBoxField-DF8rqPsu.d.mts} +4 -4
- package/dist/ListBoxField.d.mts +1 -1
- package/dist/{LogicRow-CcpD-Kk3.d.mts → LogicRow-uxpe1vZ1.d.mts} +2 -2
- package/dist/LogicRow.d.mts +1 -1
- package/dist/MaskedInput.d.mts +1 -1
- package/dist/{Menu-C0FU9WEF.d.mts → Menu-CrThaUX2.d.mts} +2 -2
- package/dist/Menu.d.mts +1 -1
- package/dist/{PillShell-CXROf8gO.d.mts → PillShell-DqglbFHe.d.mts} +2 -2
- package/dist/PillShell.d.mts +1 -1
- package/dist/{Radio-DDT_l8rd.mjs → Radio-BstZg3DI.mjs} +1 -1
- package/dist/{Radio-DDT_l8rd.mjs.map → Radio-BstZg3DI.mjs.map} +1 -1
- package/dist/{RadioGroup-CmD8NFCL.mjs → RadioGroup-erKerwSt.mjs} +2 -2
- package/dist/{RadioGroup-CmD8NFCL.mjs.map → RadioGroup-erKerwSt.mjs.map} +1 -1
- package/dist/{RangeCalendar-cZSYjuOZ.d.mts → RangeCalendar-CK5ArbIQ.d.mts} +2 -2
- package/dist/RangeCalendar.d.mts +1 -1
- package/dist/RangeCalendarPresets.d.mts +1 -1
- package/dist/RuleEditorFields.d.mts +2 -2
- package/dist/RulesPill.d.mts +1 -1
- package/dist/{SegmentedControl-CcKPfxBR.mjs → SegmentedControl-DQjIO6iX.mjs} +1 -1
- package/dist/{SegmentedControl-CcKPfxBR.mjs.map → SegmentedControl-DQjIO6iX.mjs.map} +1 -1
- package/dist/{Select-sBEBrNgo.d.mts → Select-CsxK6A5m.d.mts} +2 -2
- package/dist/Select.d.mts +1 -1
- package/dist/SmartTextInput.d.mts +22 -5
- package/dist/SmartTextInputField.d.mts +2 -2
- package/dist/SmartTextInputFunctionDialog.d.mts +2 -2
- package/dist/SmartTextInputListbox.d.mts +2 -2
- package/dist/SmartTextInputPopover.d.mts +1 -1
- package/dist/StringValueControl.d.mts +1 -1
- package/dist/TableBox.d.mts +1 -1
- package/dist/{TableBoxCell-BRECnVTs.d.mts → TableBoxCell-okcOQ5Ec.d.mts} +4 -4
- package/dist/TableBoxCell.d.mts +1 -1
- package/dist/{TableBoxField-Lm_tn73M.d.mts → TableBoxField-DR-SJY9z.d.mts} +5 -5
- package/dist/TableBoxField.d.mts +1 -1
- package/dist/{TableBoxRow-BS8Dc-zw.d.mts → TableBoxRow-DiuAB_rV.d.mts} +2 -2
- package/dist/TableBoxRow.d.mts +1 -1
- package/dist/{TableBoxRowActions-oLMb8XcM.d.mts → TableBoxRowActions-Cm4KJsAD.d.mts} +2 -2
- package/dist/TableBoxRowActions.d.mts +1 -1
- package/dist/{TableControls-UfObf-ZN.d.mts → TableControls-B9bvsNUc.d.mts} +10 -5
- package/dist/{TableControls-DxSFqRwJ.mjs → TableControls-n16XKO9S.mjs} +1298 -1286
- package/dist/TableControls-n16XKO9S.mjs.map +1 -0
- package/dist/TableControls.d.mts +2 -2
- package/dist/{TableControlsContext-Bhtb0cMs.d.mts → TableControlsContext-CZThYoj5.d.mts} +2 -2
- package/dist/TableControlsContext.d.mts +1 -1
- package/dist/{TableViewConfigurationDropdown-BDXe2Q1Y.d.mts → TableViewConfigurationDropdown-CvN7tE98.d.mts} +2 -2
- package/dist/TableViewConfigurationDropdown.d.mts +1 -1
- package/dist/{TextArea-CFf0Lspu.mjs → TextArea-CaIEBz8d.mjs} +1 -1
- package/dist/{TextArea-CFf0Lspu.mjs.map → TextArea-CaIEBz8d.mjs.map} +1 -1
- package/dist/{Toggle-BLdemkh_.mjs → Toggle-BsqSNYok.mjs} +1 -1
- package/dist/{Toggle-BLdemkh_.mjs.map → Toggle-BsqSNYok.mjs.map} +1 -1
- package/dist/{chainTypes-BbFZn8a9.d.mts → chainTypes-Cjp0zZ4E.d.mts} +3 -3
- package/dist/{clientFilterUtils-c5xzvoBj.d.mts → clientFilterUtils-BXEpzorX.d.mts} +2 -2
- package/dist/clientFilterUtils.d.mts +1 -1
- package/dist/{codec-DIp7dhcN.d.mts → codec-KAvQRjTW.d.mts} +2 -2
- package/dist/codec.d.mts +1 -1
- package/dist/components/Combobox/index.d.mts +2 -2
- package/dist/components/DataTable/TableControls/index.d.mts +21 -21
- package/dist/components/DataTable/TableControls/index.mjs +1 -1
- package/dist/components/DataTable/index.d.mts +5 -5
- package/dist/components/DataTable/index.mjs +1 -1
- package/dist/components/DataTable/types.d.mts +1 -1
- package/dist/components/DatePicker/index.d.mts +5 -5
- package/dist/components/DropdownMenu/index.d.mts +1 -1
- package/dist/components/FormField/index.d.mts +4 -4
- package/dist/components/FormField/index.mjs +1 -1
- package/dist/components/ListBox/index.d.mts +3 -3
- package/dist/components/ListBox/index.mjs +1 -1
- package/dist/components/Menu/index.d.mts +1 -1
- package/dist/components/Radio/index.mjs +1 -1
- package/dist/components/RadioGroup/index.mjs +1 -1
- package/dist/components/SegmentedControl/index.mjs +1 -1
- package/dist/components/Select/index.d.mts +1 -1
- package/dist/components/SmartTextInput/index.d.mts +2 -2
- package/dist/components/SmartTextInput/index.mjs +12 -3
- package/dist/components/SmartTextInput/index.mjs.map +1 -1
- package/dist/components/TableBox/index.d.mts +1 -1
- package/dist/components/TextArea/index.mjs +1 -1
- package/dist/components/Toggle/index.mjs +1 -1
- package/dist/components/VersionHistory/index.mjs +1 -1
- package/dist/compound-expression.d.mts +1 -1
- package/dist/{date-range-rule-value-D01nn054.d.mts → date-range-rule-value-Dk6pZHnD.d.mts} +2 -2
- package/dist/date-range-rule-value.d.mts +1 -1
- package/dist/{date-rule-intent-B4hQ_APa.d.mts → date-rule-intent-C5C8HB1v.d.mts} +3 -3
- package/dist/date-rule-intent.d.mts +1 -1
- package/dist/display.d.mts +1 -1
- package/dist/fieldPickerOptions.d.mts +2 -2
- package/dist/{filter-size-context-Dj7Idc8j.d.mts → filter-size-context-5xQ3ycnC.d.mts} +2 -2
- package/dist/filter-size-context.d.mts +1 -1
- package/dist/{filters-DDrsgRYy.d.mts → filters-ogUUmLEf.d.mts} +2 -2
- package/dist/filters.d.mts +1 -1
- package/dist/{helpers-D8_2I4ZX.d.mts → helpers-Dqw5PEva.d.mts} +2 -2
- package/dist/helpers.d.mts +1 -1
- package/dist/{index-DsjvDLF8.d.mts → index-Ck939aXd.d.mts} +3 -3
- package/dist/{index-DkXly4xw.d.mts → index-DTAcGzW4.d.mts} +2 -2
- package/dist/index3.d.mts +3 -3
- package/dist/index4.d.mts +1 -1
- package/dist/index5.d.mts +14 -14
- package/dist/{inputFields-DyOZHse2.d.mts → inputFields-DVLg_2wz.d.mts} +4 -4
- package/dist/inputFields.d.mts +1 -1
- package/dist/{jsonSchema-COo0XQLA.d.mts → jsonSchema-DmOA0Lnu.d.mts} +2 -2
- package/dist/lib/PDSProvider.d.mts +8 -0
- package/dist/lib/PDSProvider.mjs +15 -11
- package/dist/lib/PDSProvider.mjs.map +1 -1
- package/dist/lib/chainTypes.d.mts +1 -1
- package/dist/lib/colorMode.d.mts +30 -0
- package/dist/lib/colorMode.mjs +71 -0
- package/dist/lib/colorMode.mjs.map +1 -0
- package/dist/lib/customFunctionSchema.d.mts +1 -1
- package/dist/lib/date-filter/index.d.mts +2 -2
- package/dist/lib/jsonSchema.d.mts +1 -1
- package/dist/lib/jsonSchema.mjs.map +1 -1
- package/dist/lib/jsonSchemaType.d.mts +1 -1
- package/dist/lib/methodChain.d.mts +1 -1
- package/dist/lib/objectSchemaTypes.d.mts +1 -1
- package/dist/lib/platformObject.d.mts +1 -1
- package/dist/lib/schemaOptions.d.mts +2 -2
- package/dist/lib/schemaOptions.mjs +20 -2
- package/dist/lib/schemaOptions.mjs.map +1 -1
- package/dist/lib/useChain.d.mts +1 -1
- package/dist/{listbox-options-D-DyLUdi.d.mts → listbox-options-DCmqWWgy.d.mts} +3 -3
- package/dist/listbox-options.d.mts +1 -1
- package/dist/{methodChain-BAUFBbR9.d.mts → methodChain-DaDvXsHF.d.mts} +2 -2
- package/dist/{operators-DjQXVNVI.d.mts → operators-Seez5p9U.d.mts} +2 -2
- package/dist/operators.d.mts +1 -1
- package/dist/patterns/IndexTableLayout/index.mjs +1 -1
- package/dist/relative-date-label.d.mts +1 -1
- package/dist/{schemaOptions-D536qhSd.d.mts → schemaOptions-BQgLOgtD.d.mts} +8 -3
- package/dist/{types-DHTFd-5t.d.mts → types-39QvBZuZ.d.mts} +1 -1
- package/dist/{types-CjQzH0MM.d.mts → types-BQP14Fng.d.mts} +2 -2
- package/dist/{types-Dx8PgO8a.d.mts → types-Bdi8Y7ll.d.mts} +3 -3
- package/dist/{types-DFU6M0OL.d.mts → types-DWU0Dmy9.d.mts} +3 -3
- package/dist/{types-BMpEizJP.d.mts → types-EodOhsjw.d.mts} +2 -2
- package/dist/{types-BhHdS6QP.d.mts → types-SOkNtcBq.d.mts} +2 -2
- package/dist/types3.d.mts +1 -1
- package/dist/types4.d.mts +2 -2
- package/dist/types5.d.mts +2 -2
- package/dist/types6.d.mts +1 -1
- package/dist/types7.d.mts +1 -1
- package/dist/{url-state-D1muieS7.d.mts → url-state-CwaRVyXR.d.mts} +2 -2
- package/dist/url-state.d.mts +1 -1
- package/dist/{useChain-D5LvupTW.d.mts → useChain-DXkKKe-J.d.mts} +4 -4
- package/dist/useChainAtCursor.d.mts +4 -4
- package/dist/useCodeChainFunctionPanel.d.mts +1 -1
- package/dist/useColumnConfiguration.d.mts +2 -2
- package/dist/useComboboxFooterAction.d.mts +1 -1
- package/dist/{useFilterStateV2-MH3zbQFr.d.mts → useFilterStateV2-Bzo27PR-.d.mts} +3 -3
- package/dist/useFilterStateV2.d.mts +1 -1
- package/dist/{useTableColumns-rLskKJhd.d.mts → useTableColumns-DdU0CLcU.d.mts} +2 -2
- package/dist/useTableColumns.d.mts +1 -1
- package/dist/useTruncation.d.mts +1 -1
- package/dist/utils.d.mts +1 -1
- package/dist/utils4.d.mts +2 -2
- package/dist/{validation-Csi2SJxm.d.mts → validation-D6NPueNk.d.mts} +3 -3
- package/dist/validation.d.mts +1 -1
- package/dist/validation2.d.mts +3 -3
- package/dist/{value-control-resolver-BoBFvQ_0.d.mts → value-control-resolver-C-UrXVlB.d.mts} +2 -2
- package/dist/value-control-resolver.d.mts +1 -1
- package/llms.txt +9 -1
- package/package.json +3 -1
- package/skills/ui-authoring/COMPONENTS.md +1 -1
- package/skills/ui-authoring/TOKENS.md +10 -0
- package/dist/DataTable-95ZxiPlV.mjs.map +0 -1
- package/dist/TableControls-DxSFqRwJ.mjs.map +0 -1
- /package/dist/{index-BYd3Btr1.d.mts → index-DztUdPP_.d.mts} +0 -0
- /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
|
|
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 {
|
|
34
|
-
import {
|
|
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/
|
|
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
|
-
*
|
|
1540
|
-
*
|
|
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
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
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
|
-
*
|
|
1550
|
-
*
|
|
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
|
-
*
|
|
1553
|
-
*
|
|
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
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
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
|
|
4526
|
-
if (
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
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
|
-
*
|
|
4535
|
-
*
|
|
4536
|
-
*
|
|
4537
|
-
*
|
|
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
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
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
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
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
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
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
|
|
4383
|
+
return {
|
|
4384
|
+
all: {
|
|
4385
|
+
...committed,
|
|
4386
|
+
topLevelRules
|
|
4387
|
+
},
|
|
4388
|
+
heldIds
|
|
4389
|
+
};
|
|
4574
4390
|
}
|
|
4575
|
-
|
|
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
|
-
|
|
4578
|
-
|
|
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
|
|
4582
|
-
|
|
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
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
if (
|
|
4611
|
-
if (
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
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
|
-
*
|
|
4625
|
-
*
|
|
4626
|
-
*
|
|
4627
|
-
*
|
|
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
|
|
4635
|
-
const
|
|
4636
|
-
|
|
4637
|
-
if (
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
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
|
-
*
|
|
4660
|
-
*
|
|
4661
|
-
*
|
|
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
|
-
*
|
|
4669
|
-
*
|
|
4670
|
-
*
|
|
4671
|
-
*
|
|
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
|
-
*
|
|
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
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
const
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
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
|
-
*
|
|
4841
|
-
*
|
|
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
|
|
4845
|
-
const
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
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
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
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/
|
|
4876
|
-
const
|
|
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
|
-
|
|
4880
|
-
flex-wrap: nowrap;
|
|
4881
|
-
min-width: 0;
|
|
4882
|
-
padding: ${Spacing[2]} 0;
|
|
4858
|
+
justify-content: left;
|
|
4883
4859
|
`;
|
|
4884
|
-
const
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
const
|
|
4900
|
-
|
|
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
|
|
4903
|
-
|
|
4904
|
-
|
|
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
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
const
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
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
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
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
|
-
|
|
5045
|
-
const
|
|
5046
|
-
const
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
});
|
|
5070
|
-
}, [
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
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
|
|
5189
|
-
*
|
|
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
|
-
*
|
|
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
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
const
|
|
5206
|
-
|
|
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 {
|
|
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-
|
|
5507
|
+
//# sourceMappingURL=TableControls-n16XKO9S.mjs.map
|