@lavalogic/scoria 0.40.13 → 0.40.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +58 -58
  2. package/dist/Components/AccordionGroup.svelte +35 -46
  3. package/dist/Components/AccordionGroupButton.svelte +68 -76
  4. package/dist/Components/Action.svelte +103 -135
  5. package/dist/Components/AlertModal.svelte +171 -190
  6. package/dist/Components/AppointmentCalendar/AppointmentCalendar.svelte +614 -697
  7. package/dist/Components/Base/BaseModal.svelte +90 -116
  8. package/dist/Components/Base/ContextWrapper.svelte +67 -72
  9. package/dist/Components/Base/Pagination.svelte +173 -196
  10. package/dist/Components/Base/Snippets.svelte +34 -36
  11. package/dist/Components/BigRadioSet.svelte +36 -38
  12. package/dist/Components/Bubble.svelte +78 -101
  13. package/dist/Components/Button.svelte +175 -208
  14. package/dist/Components/Checkbox.svelte +75 -86
  15. package/dist/Components/CollapsibleCard.svelte +72 -87
  16. package/dist/Components/CollapsibleMenuGroup.svelte +53 -57
  17. package/dist/Components/ConditionBuilder/ConditionBuilder.svelte +39 -64
  18. package/dist/Components/Contexts/ContextMenu.svelte +141 -156
  19. package/dist/Components/Contexts/ContextMenuDivider.svelte +15 -15
  20. package/dist/Components/Contexts/ContextMenuOption.svelte +53 -60
  21. package/dist/Components/Contexts/Toast.svelte +81 -86
  22. package/dist/Components/Contexts/ToastContainer.svelte +58 -62
  23. package/dist/Components/Contexts/Tooltip.svelte +36 -37
  24. package/dist/Components/DataMatrixIcon.svelte +95 -108
  25. package/dist/Components/DateInput.svelte +118 -146
  26. package/dist/Components/DatePicker.svelte +255 -294
  27. package/dist/Components/DesktopModal.svelte +70 -90
  28. package/dist/Components/Dial.svelte +167 -197
  29. package/dist/Components/DimensionInput.svelte +124 -169
  30. package/dist/Components/DragMenu/DragMenuHolder.svelte +98 -114
  31. package/dist/Components/DragMenu/DraggableCard.svelte +64 -76
  32. package/dist/Components/FileCard.svelte +21 -24
  33. package/dist/Components/FileUpload.svelte +190 -225
  34. package/dist/Components/GridInput.svelte +159 -196
  35. package/dist/Components/GridInputSet.svelte +24 -29
  36. package/dist/Components/HorizontalTabGroup.svelte +73 -89
  37. package/dist/Components/HorizontalTabGroupButton.svelte +40 -44
  38. package/dist/Components/Icon.svelte +99 -128
  39. package/dist/Components/Icons.js +357 -357
  40. package/dist/Components/InfoAlert.svelte +35 -38
  41. package/dist/Components/LoadingAnimation.svelte +691 -755
  42. package/dist/Components/LoadingModal.svelte +36 -44
  43. package/dist/Components/LoadingOverlay.svelte +22 -26
  44. package/dist/Components/Modal.svelte +204 -254
  45. package/dist/Components/MultiSelect.svelte +186 -216
  46. package/dist/Components/Nav/Nav.svelte +94 -110
  47. package/dist/Components/Nav/NavServiceMenuList.svelte +57 -61
  48. package/dist/Components/Nav/NavTab.svelte +58 -62
  49. package/dist/Components/Nav/ServiceMenuItem.svelte +103 -111
  50. package/dist/Components/Nav/ServicesNav.svelte +72 -79
  51. package/dist/Components/NumberInput.svelte +121 -155
  52. package/dist/Components/OptionCards.svelte +62 -73
  53. package/dist/Components/PageHeading.svelte +23 -25
  54. package/dist/Components/PasswordInput.svelte +69 -88
  55. package/dist/Components/PopoverButton.svelte +151 -0
  56. package/dist/Components/PopoverButton.svelte.d.ts +9 -0
  57. package/dist/Components/PopoverButtonProps.d.ts +21 -0
  58. package/dist/Components/PopoverButtonProps.js +1 -0
  59. package/dist/Components/Portal.svelte +29 -35
  60. package/dist/Components/ProgressBubble.svelte +91 -106
  61. package/dist/Components/QuerySelect.svelte +260 -346
  62. package/dist/Components/SidebarPanel.svelte +34 -36
  63. package/dist/Components/SingleSelect.svelte +196 -244
  64. package/dist/Components/StepButton.svelte +65 -70
  65. package/dist/Components/StepForm.svelte +115 -135
  66. package/dist/Components/Switch.svelte +57 -69
  67. package/dist/Components/Table/ARCHITECTURE.md +298 -298
  68. package/dist/Components/Table/Body/QuickSearchCell.svelte +578 -655
  69. package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +42 -51
  70. package/dist/Components/Table/Body/Rows/ColumnList.svelte +42 -58
  71. package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +403 -442
  72. package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +51 -78
  73. package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +165 -184
  74. package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +110 -123
  75. package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +134 -160
  76. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +190 -231
  77. package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +82 -104
  78. package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +52 -87
  79. package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +122 -150
  80. package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +132 -159
  81. package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +190 -236
  82. package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +173 -211
  83. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +204 -244
  84. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +140 -177
  85. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +188 -217
  86. package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +173 -193
  87. package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +166 -187
  88. package/dist/Components/Table/Body/Rows/TableRow.svelte +245 -288
  89. package/dist/Components/Table/Body/TableBody.svelte +141 -154
  90. package/dist/Components/Table/Headers/HighlightAllHeader.svelte +35 -41
  91. package/dist/Components/Table/Headers/SelectAllHeader.svelte +24 -30
  92. package/dist/Components/Table/Headers/TableHead.svelte +348 -402
  93. package/dist/Components/Table/Misc/ColumnPanel.svelte +37 -39
  94. package/dist/Components/Table/Misc/ColumnPanelModal.svelte +35 -39
  95. package/dist/Components/Table/Misc/FilterInput.svelte +556 -619
  96. package/dist/Components/Table/Misc/FilterPanel.svelte +170 -183
  97. package/dist/Components/Table/Misc/FilterPanelModal.svelte +25 -31
  98. package/dist/Components/Table/Misc/ShareViewModal.svelte +252 -281
  99. package/dist/Components/Table/Misc/SidePanel.svelte +32 -35
  100. package/dist/Components/Table/Misc/TableConfigurationModal.svelte +574 -629
  101. package/dist/Components/Table/Misc/TableFooter.svelte +326 -332
  102. package/dist/Components/Table/Misc/TableHorizontalBar.svelte +134 -140
  103. package/dist/Components/Table/Misc/TableSidebar.svelte +183 -198
  104. package/dist/Components/Table/Misc/TableViewDropdown.svelte +83 -103
  105. package/dist/Components/Table/Misc/ToolboxPanel.svelte +83 -100
  106. package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +88 -107
  107. package/dist/Components/Table/Misc/UpdateViewOptionsModal.svelte +163 -190
  108. package/dist/Components/Table/NestedTable.svelte +102 -128
  109. package/dist/Components/Table/Table.svelte +531 -663
  110. package/dist/Components/Test/TestContextConsumer.svelte +11 -13
  111. package/dist/Components/TextArea.svelte +95 -130
  112. package/dist/Components/TextInput.svelte +125 -176
  113. package/dist/Components/ThreeStateRadio.svelte +111 -130
  114. package/dist/Components/Touch/BoolCard.svelte +33 -45
  115. package/dist/Components/Touch/DateModal.svelte +164 -204
  116. package/dist/Components/Touch/EditCard.svelte +48 -59
  117. package/dist/Components/Touch/InfoCard.svelte +28 -31
  118. package/dist/Components/Touch/InfoTextCard.svelte +26 -28
  119. package/dist/Components/Touch/ModalExplanation.svelte +15 -17
  120. package/dist/Components/Touch/NumberKeyboard.svelte +181 -218
  121. package/dist/Components/Touch/NumberModal.svelte +59 -79
  122. package/dist/Components/Touch/PlusMinusCard.svelte +43 -46
  123. package/dist/Components/Touch/SelectModal.svelte +111 -126
  124. package/dist/Components/Touch/SummaryCard.svelte +80 -93
  125. package/dist/Components/Touch/TextAreaModal.svelte +72 -94
  126. package/dist/Components/Touch/TextModal.svelte +77 -105
  127. package/dist/Components/Touch/TouchCard.svelte +31 -33
  128. package/dist/Components/Touch/TouchModal.svelte +214 -270
  129. package/dist/Components/Touch/UtilityButton.svelte +88 -109
  130. package/dist/Components/VerticalTabGroup.svelte +60 -78
  131. package/dist/Components/VerticalTabGroupButton.svelte +54 -58
  132. package/dist/Helpers/Datamatrix.d.ts +19 -19
  133. package/dist/Helpers/Datamatrix.js +291 -291
  134. package/dist/Types/Examples/ExampleModal.svelte +31 -35
  135. package/dist/index.d.ts +2 -0
  136. package/dist/index.js +1 -0
  137. package/dist/scss/_basics.scss +12 -12
  138. package/dist/scss/_colours.scss +134 -134
  139. package/dist/scss/_mixins.scss +3392 -3392
  140. package/dist/scss/_motion.scss +57 -57
  141. package/dist/scss/_sizing.scss +85 -85
  142. package/dist/scss/_transitions.scss +8 -8
  143. package/package.json +1 -1
@@ -1,681 +1,604 @@
1
- <svelte:options runes />
2
-
3
- <script
4
- lang="ts"
5
- generics="T extends object, IdType extends Primitive, FilterValueType"
6
- >
7
- import { dev } from '$app/environment';
8
- import Action from '../../Action.svelte';
9
- import Button from '../../Button.svelte';
10
- import MultiSelect from '../../MultiSelect.svelte';
11
- import NumberInput from '../../NumberInput.svelte';
12
- import SingleSelect from '../../SingleSelect.svelte';
13
- import TextInput from '../../TextInput.svelte';
14
- import ThreeStateRadio from '../../ThreeStateRadio.svelte';
15
- import DateModal from '../../Touch/DateModal.svelte';
16
- import { hasQuery } from '../TableHelpers.svelte.js';
17
- import { devCatch, isDateTuple, isNumericArray } from '../../../Helpers/Helpers.svelte.js';
18
- import { Colour, ColourSet } from '../../../scss/colours.js';
19
- import { InputVariant } from '../../../Types/Internal/InputVariant.js';
20
- import type { SelectOption } from '../../../Types/Internal/SelectOption.js';
21
- import { Size } from '../../../Types/Internal/Size.js';
22
- import { getContext, onDestroy, onMount, untrack } from 'svelte';
23
- import { cubicOut } from 'svelte/easing';
24
- import { fade } from 'svelte/transition';
25
-
26
- import QuerySelect from '../../QuerySelect.svelte';
27
- import { Ternary } from '../../../Types/Internal/Ternary.js';
28
- import { numberDateFilterTypes } from '../Misc/numberDateFilterTypes.js';
29
- import { stringFilterTypes } from '../Misc/stringFilterTypes.js';
30
- import { quickFilterContextIdentifier } from '../Table.svelte';
31
- import { CheckboxDef } from '../Types/Columns/Definitions/Accessors/CheckboxDef.svelte.js';
32
- import { DateInputDef } from '../Types/Columns/Definitions/Accessors/DateInputDef.svelte.js';
33
- import { NumberInputDef } from '../Types/Columns/Definitions/Accessors/NumberInputDef.svelte.js';
34
- import { SelectDef } from '../Types/Columns/Definitions/Accessors/SelectDef.svelte.js';
35
- import { TableContext } from '../Types/Context/TableContext.svelte.js';
36
- import type { Primitive } from '../Types/Context/TableInitOptions.js';
37
- import { CustomFilterType } from '../Types/Filtering/CustomFilterType.js';
38
- import { NumberDateFilterMode } from '../Types/Filtering/NumberDateFilterMode.js';
39
- import { StringFilterMode } from '../Types/Filtering/StringFilterMode.js';
40
- import type { QuickSearchCellProps } from './QuickSearchCellProps.js';
41
-
42
- const tableContext = getContext<TableContext<T, IdType>>(TableContext.identifier);
43
-
44
- const { column, customFilters }: QuickSearchCellProps<T, FilterValueType> = $props();
45
-
46
- const def = $derived(column.columnDef);
47
-
48
- const customFilter = $derived(customFilters?.get(def.id));
49
-
50
- const isSelect = $derived(
51
- def instanceof SelectDef ||
52
- def.filterValueType == CustomFilterType.Select ||
53
- customFilter?.type === CustomFilterType.Select
54
- );
55
-
56
- const hasFilterFn = $derived(!!def.filterFn && typeof def.filterFn === 'function');
57
-
58
- // A column is filterable when it has any of: a local `filterFn`, a
59
- // declared `filterValueType` (server-side filtering — required for
60
- // remote-pagination tables whose data repo never assigns `filterFn`),
61
- // or a `customFilters` entry from `defsWrapper.remoteFilters`.
62
- const hasDeclaredFilterType = $derived(def.filterValueType != null);
63
-
64
- const isSearchable = $derived(
65
- def.allowFiltering &&
66
- (hasFilterFn || hasDeclaredFilterType || customFilters?.has(def.id) == true)
67
- );
68
-
69
- const isNumeric = $derived(
70
- def instanceof NumberInputDef ||
71
- def.filterValueType == 'Number' ||
72
- customFilter?.type === CustomFilterType.Number
73
- );
74
-
75
- function emptyOptsAndWarn(): Array<SelectOption<unknown>> {
76
- console.warn(`def ${def.id} is declared as a select filter but has no getOptions() function`);
77
- return [];
1
+ <svelte:options runes />
2
+
3
+ <script
4
+ lang="ts"
5
+ generics="T extends object, IdType extends Primitive, FilterValueType"
6
+ >import { dev } from "$app/environment";
7
+ import Action from "../../Action.svelte";
8
+ import Button from "../../Button.svelte";
9
+ import MultiSelect from "../../MultiSelect.svelte";
10
+ import NumberInput from "../../NumberInput.svelte";
11
+ import SingleSelect from "../../SingleSelect.svelte";
12
+ import TextInput from "../../TextInput.svelte";
13
+ import ThreeStateRadio from "../../ThreeStateRadio.svelte";
14
+ import DateModal from "../../Touch/DateModal.svelte";
15
+ import { hasQuery } from "../TableHelpers.svelte.js";
16
+ import { devCatch, isDateTuple, isNumericArray } from "../../../Helpers/Helpers.svelte.js";
17
+ import { Colour, ColourSet } from "../../../scss/colours.js";
18
+ import { InputVariant } from "../../../Types/Internal/InputVariant.js";
19
+ import { Size } from "../../../Types/Internal/Size.js";
20
+ import { getContext, onDestroy, onMount, untrack } from "svelte";
21
+ import { cubicOut } from "svelte/easing";
22
+ import { fade } from "svelte/transition";
23
+ import QuerySelect from "../../QuerySelect.svelte";
24
+ import { Ternary } from "../../../Types/Internal/Ternary.js";
25
+ import { numberDateFilterTypes } from "../Misc/numberDateFilterTypes.js";
26
+ import { stringFilterTypes } from "../Misc/stringFilterTypes.js";
27
+ import { quickFilterContextIdentifier } from "../Table.svelte";
28
+ import { CheckboxDef } from "../Types/Columns/Definitions/Accessors/CheckboxDef.svelte.js";
29
+ import { DateInputDef } from "../Types/Columns/Definitions/Accessors/DateInputDef.svelte.js";
30
+ import { NumberInputDef } from "../Types/Columns/Definitions/Accessors/NumberInputDef.svelte.js";
31
+ import { SelectDef } from "../Types/Columns/Definitions/Accessors/SelectDef.svelte.js";
32
+ import { TableContext } from "../Types/Context/TableContext.svelte.js";
33
+ import { CustomFilterType } from "../Types/Filtering/CustomFilterType.js";
34
+ import { NumberDateFilterMode } from "../Types/Filtering/NumberDateFilterMode.js";
35
+ import { StringFilterMode } from "../Types/Filtering/StringFilterMode.js";
36
+ const tableContext = getContext(TableContext.identifier);
37
+ const { column, customFilters } = $props();
38
+ const def = $derived(column.columnDef);
39
+ const customFilter = $derived(customFilters?.get(def.id));
40
+ const isSelect = $derived(def instanceof SelectDef || def.filterValueType == CustomFilterType.Select || customFilter?.type === CustomFilterType.Select);
41
+ const hasFilterFn = $derived(!!def.filterFn && typeof def.filterFn === "function");
42
+ // A column is filterable when it has any of: a local `filterFn`, a
43
+ // declared `filterValueType` (server-side filtering — required for
44
+ // remote-pagination tables whose data repo never assigns `filterFn`),
45
+ // or a `customFilters` entry from `defsWrapper.remoteFilters`.
46
+ const hasDeclaredFilterType = $derived(def.filterValueType != null);
47
+ const isSearchable = $derived(def.allowFiltering && (hasFilterFn || hasDeclaredFilterType || customFilters?.has(def.id) == true));
48
+ const isNumeric = $derived(def instanceof NumberInputDef || def.filterValueType == "Number" || customFilter?.type === CustomFilterType.Number);
49
+ function emptyOptsAndWarn() {
50
+ console.warn(`def ${def.id} is declared as a select filter but has no getOptions() function`);
51
+ return [];
52
+ }
53
+ const options = $derived(isSelect ? def.getOptions?.() ?? customFilter?.options ?? emptyOptsAndWarn() : []);
54
+ const isDate = $derived(def instanceof DateInputDef);
55
+ const isRemoteSelect = $derived(customFilter?.type === CustomFilterType.Select);
56
+ const selectSymbol = Symbol();
57
+ const openedSelect = getContext(quickFilterContextIdentifier);
58
+ const isSelectOpen = $derived(openedSelect?.value === selectSymbol);
59
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
60
+ let value = $state(null);
61
+ let valueMin = $state(null);
62
+ let valueMax = $state(null);
63
+ let selectedOptions = $state([]);
64
+ let lastQueryOptions = $state([]);
65
+ const inputId = $derived(`${tableContext.tableName}-cell-quicksearch-${column.id}`);
66
+ const filterMode = $derived(tableContext.paginationRepo?.filterModes.get(def.id));
67
+ const selectedNumberFilterType = $derived.by(() => {
68
+ const value = filterMode ?? NumberDateFilterMode.In;
69
+ return numberDateFilterTypes.find((it) => it.value === value) ?? null;
70
+ });
71
+ const selectedStringFilterType = $derived.by(() => {
72
+ const value = filterMode ?? StringFilterMode.Contains;
73
+ return stringFilterTypes.find((it) => it.value === value) ?? null;
74
+ });
75
+ const filteringStateValue = $derived(tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value);
76
+ $effect(() => {
77
+ if (isRemoteSelect && !tableContext.paginationRepo?.filtering) {
78
+ return;
78
79
  }
79
- const options = $derived(
80
- isSelect ? (def.getOptions?.() ?? customFilter?.options ?? emptyOptsAndWarn()) : []
81
- );
82
- const isDate = $derived(def instanceof DateInputDef);
83
- const isRemoteSelect = $derived(customFilter?.type === CustomFilterType.Select);
84
-
85
- const selectSymbol = Symbol();
86
-
87
- const openedSelect = getContext<{ value: symbol | null } | null | undefined>(
88
- quickFilterContextIdentifier
89
- );
90
-
91
- const isSelectOpen = $derived(openedSelect?.value === selectSymbol);
92
-
93
- // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
94
- let value: unknown | null = $state(null);
95
- let valueMin: number | null = $state(null);
96
- let valueMax: number | null = $state(null);
97
-
98
- let selectedOptions: Array<SelectOption<unknown>> = $state([]);
99
- let lastQueryOptions: Array<SelectOption<unknown>> = $state([]);
100
-
101
- const inputId = $derived(`${tableContext.tableName}-cell-quicksearch-${column.id}`);
102
-
103
- const filterMode = $derived(tableContext.paginationRepo?.filterModes.get(def.id));
104
-
105
- const selectedNumberFilterType: SelectOption<NumberDateFilterMode> | null = $derived.by(() => {
106
- const value = (filterMode as NumberDateFilterMode | undefined) ?? NumberDateFilterMode.In;
107
- return numberDateFilterTypes.find((it) => it.value === value) ?? null;
108
- });
109
-
110
- const selectedStringFilterType: SelectOption<StringFilterMode> | null = $derived.by(() => {
111
- const value = (filterMode as StringFilterMode | undefined) ?? StringFilterMode.Contains;
112
- return stringFilterTypes.find((it) => it.value === value) ?? null;
113
- });
114
-
115
- const filteringStateValue = $derived(
116
- tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value
117
- );
118
-
119
- $effect(() => {
120
- if (isRemoteSelect && !tableContext.paginationRepo?.filtering) {
121
- return;
80
+ void filteringStateValue;
81
+ if (hasQuery(customFilter)) {
82
+ if (def.debug) {
83
+ console.debug(`${def.id}: `, "has query");
122
84
  }
123
- void filteringStateValue;
124
-
125
- if (hasQuery(customFilter)) {
126
- if (def.debug) {
127
- console.debug(`${def.id}: `, 'has query');
128
- }
129
-
130
- untrack(async () => {
131
- const gotValue = filteringStateValue as Array<unknown> | undefined;
132
-
133
- try {
134
- // as unknown as string | number | undefined;
135
-
136
- if (gotValue != undefined) {
137
- if (def.debug) {
138
- console.debug(`${def.id}: query values:`, tableContext.queryValues.snapshot());
139
- }
140
- const x = tableContext.queryValues.get(def);
141
-
142
- if (def.debug) {
143
- console.debug(`${def.id}: `, 'value:', $state.snapshot(x));
144
- }
145
- value = x ?? null;
146
- } else if (def.debug) {
147
- console.debug(
148
- `${def.id}: `,
149
- 'filtering state value was undefined',
150
- $state.snapshot(tableContext.paginationRepo?.filtering)
151
- );
85
+ untrack(async () => {
86
+ const gotValue = filteringStateValue;
87
+ try {
88
+ // as unknown as string | number | undefined;
89
+ if (gotValue != undefined) {
90
+ if (def.debug) {
91
+ console.debug(`${def.id}: query values:`, tableContext.queryValues.snapshot());
152
92
  }
153
- } catch (e) {
154
- console.warn(e);
155
- value = null;
156
- }
157
- }).catch(devCatch);
158
- } else if (isSelect) {
159
- if (def.debug) {
160
- console.debug(`${def.id}: `, 'is select (not query)');
161
- }
162
-
163
- if (!tableContext.paginationRepo?.filtering) {
164
- return;
165
- }
166
-
167
- const gotValue = filteringStateValue as Array<unknown> | undefined;
168
-
169
- function isSelectOption(it: unknown): it is SelectOption<unknown> {
170
- return it != null && typeof it == 'object';
171
- }
172
- untrack(async () => {
173
- try {
174
- const opts = await options;
93
+ const x = tableContext.queryValues.get(def);
175
94
  if (def.debug) {
176
- console.debug(`${def.id}: possible options:`, $state.snapshot(opts));
95
+ console.debug(`${def.id}: `, "value:", $state.snapshot(x));
177
96
  }
178
- const x = opts.filter((it) => {
179
- if (!isSelectOption(it)) {
180
- return false;
181
- }
182
- if (it.value == undefined) {
183
- return true;
184
- }
185
-
186
- if (typeof it.value == 'string' || typeof it.value == 'number') {
187
- return (gotValue as unknown as Array<string | number> | undefined)?.includes(
188
- it.value
189
- );
190
- }
191
-
97
+ value = x ?? null;
98
+ } else if (def.debug) {
99
+ console.debug(`${def.id}: `, "filtering state value was undefined", $state.snapshot(tableContext.paginationRepo?.filtering));
100
+ }
101
+ } catch (e) {
102
+ console.warn(e);
103
+ value = null;
104
+ }
105
+ }).catch(devCatch);
106
+ } else if (isSelect) {
107
+ if (def.debug) {
108
+ console.debug(`${def.id}: `, "is select (not query)");
109
+ }
110
+ if (!tableContext.paginationRepo?.filtering) {
111
+ return;
112
+ }
113
+ const gotValue = filteringStateValue;
114
+ function isSelectOption(it) {
115
+ return it != null && typeof it == "object";
116
+ }
117
+ untrack(async () => {
118
+ try {
119
+ const opts = await options;
120
+ if (def.debug) {
121
+ console.debug(`${def.id}: possible options:`, $state.snapshot(opts));
122
+ }
123
+ const x = opts.filter((it) => {
124
+ if (!isSelectOption(it)) {
192
125
  return false;
193
- });
194
-
195
- if (def.debug) {
196
- console.debug(`${def.id}: `, 'selected options:', x);
197
126
  }
198
- selectedOptions = x;
199
- } catch (e) {
200
- if (dev || def.debug) {
201
- console.debug(`${def.id}: `, e);
127
+ if (it.value == undefined) {
128
+ return true;
202
129
  }
130
+ if (typeof it.value == "string" || typeof it.value == "number") {
131
+ return gotValue?.includes(it.value);
132
+ }
133
+ return false;
134
+ });
135
+ if (def.debug) {
136
+ console.debug(`${def.id}: `, "selected options:", x);
203
137
  }
204
- }).catch(devCatch);
205
- } else if (isNumeric) {
206
- if (
207
- selectedNumberFilterType?.value === NumberDateFilterMode.Between &&
208
- tableContext.paginationRepo?.filtering
209
- ) {
210
- const gotValue = filteringStateValue;
211
- if (gotValue != undefined) {
212
- const [min, max] = gotValue as [number, number | undefined];
213
- value = min;
214
- valueMin = min;
215
- valueMax = max ?? null;
216
- } else {
217
- value = valueMin = valueMax = null;
218
- }
219
- } else if (tableContext.paginationRepo?.filtering) {
220
- const gotValue = filteringStateValue;
221
-
222
- if (gotValue != undefined) {
223
- const [min] = gotValue as [number, number | undefined];
224
- value = min;
225
- valueMin = min;
226
- valueMax = min;
227
- } else {
228
- value = valueMin = valueMax = null;
138
+ selectedOptions = x;
139
+ } catch (e) {
140
+ if (dev || def.debug) {
141
+ console.debug(`${def.id}: `, e);
229
142
  }
230
143
  }
144
+ }).catch(devCatch);
145
+ } else if (isNumeric) {
146
+ if (selectedNumberFilterType?.value === NumberDateFilterMode.Between && tableContext.paginationRepo?.filtering) {
147
+ const gotValue = filteringStateValue;
148
+ if (gotValue != undefined) {
149
+ const [min, max] = gotValue;
150
+ value = min;
151
+ valueMin = min;
152
+ valueMax = max ?? null;
153
+ } else {
154
+ value = valueMin = valueMax = null;
155
+ }
231
156
  } else if (tableContext.paginationRepo?.filtering) {
232
157
  const gotValue = filteringStateValue;
233
- if (gotValue == null || typeof gotValue == 'string' || typeof gotValue == 'number') {
234
- assertNotPromise(gotValue);
235
- value = gotValue;
158
+ if (gotValue != undefined) {
159
+ const [min] = gotValue;
160
+ value = min;
161
+ valueMin = min;
162
+ valueMax = min;
163
+ } else {
164
+ value = valueMin = valueMax = null;
236
165
  }
237
166
  }
238
- });
239
-
240
- /**
241
- * Dev-only invariant: a Promise must never reach the synchronous primitive
242
- * sink (we should always have awaited it upstream). Throws in dev to make
243
- * any regression loud; no-ops in production builds.
244
- */
245
- function assertNotPromise(gotValue: unknown) {
246
- if (dev && gotValue != null && typeof gotValue == 'object' && gotValue instanceof Promise) {
247
- throw new Error(`Unexpected Promise`);
167
+ } else if (tableContext.paginationRepo?.filtering) {
168
+ const gotValue = filteringStateValue;
169
+ if (gotValue == null || typeof gotValue == "string" || typeof gotValue == "number") {
170
+ assertNotPromise(gotValue);
171
+ value = gotValue;
248
172
  }
249
173
  }
250
- onMount(() => {
251
- const filterOption =
252
- tableContext.paginationRepo?.filtering &&
253
- tableContext.paginationRepo.filtering.find((it) => it.id === def.id);
254
-
255
- if (filterOption) {
256
- if (isNumericArray(filterOption.value)) {
257
- [valueMin, valueMax] = filterOption.value;
258
- if (valueMax == undefined) {
259
- valueMax = valueMin;
260
- value = valueMin?.toString();
261
- } else if (valueMin == undefined) {
262
- valueMin = valueMax;
263
- value = valueMax.toString();
264
- } else {
265
- value = valueMin.toString();
266
- }
267
- } else if (!hasQuery(customFilter)) {
268
- const gotValue = filterOption.value;
269
- if (gotValue == null || typeof gotValue == 'string' || typeof gotValue == 'number') {
270
- assertNotPromise(gotValue);
271
- value = gotValue;
272
- }
174
+ });
175
+ /**
176
+ * Dev-only invariant: a Promise must never reach the synchronous primitive
177
+ * sink (we should always have awaited it upstream). Throws in dev to make
178
+ * any regression loud; no-ops in production builds.
179
+ */
180
+ function assertNotPromise(gotValue) {
181
+ if (dev && gotValue != null && typeof gotValue == "object" && gotValue instanceof Promise) {
182
+ throw new Error(`Unexpected Promise`);
183
+ }
184
+ }
185
+ onMount(() => {
186
+ const filterOption = tableContext.paginationRepo?.filtering && tableContext.paginationRepo.filtering.find((it) => it.id === def.id);
187
+ if (filterOption) {
188
+ if (isNumericArray(filterOption.value)) {
189
+ [valueMin, valueMax] = filterOption.value;
190
+ if (valueMax == undefined) {
191
+ valueMax = valueMin;
192
+ value = valueMin?.toString();
193
+ } else if (valueMin == undefined) {
194
+ valueMin = valueMax;
195
+ value = valueMax.toString();
196
+ } else {
197
+ value = valueMin.toString();
198
+ }
199
+ } else if (!hasQuery(customFilter)) {
200
+ const gotValue = filterOption.value;
201
+ if (gotValue == null || typeof gotValue == "string" || typeof gotValue == "number") {
202
+ assertNotPromise(gotValue);
203
+ value = gotValue;
273
204
  }
274
205
  }
275
- });
276
-
277
- function closeOnCtrlF(e: KeyboardEvent) {
278
- if (e.key.toLocaleUpperCase() === 'F' && e.ctrlKey) {
279
- return;
280
- }
281
- e.stopPropagation();
282
206
  }
283
-
284
- let showFromDateModal = $state(false);
285
- let showToDateModal = $state(false);
286
-
287
- function generateOnChangeObject(_label: string) {
288
- return (newValue: SelectOption<unknown> | null) => {
289
- tableContext.paginationRepo?.filterBy(def.id, newValue?.value ?? null);
290
- tableContext.queryValues.set(def, newValue);
291
- assertNotPromise(newValue);
292
- value = newValue;
293
- };
207
+ });
208
+ function closeOnCtrlF(e) {
209
+ if (e.key.toLocaleUpperCase() === "F" && e.ctrlKey) {
210
+ return;
294
211
  }
295
-
296
- function generateOnChangePrimitive(_label: string) {
297
- // AsPrimitive QuerySelect hands us the primitive value directly.
298
- return (newValue: unknown) => {
299
- tableContext.paginationRepo?.filterBy(def.id, newValue ?? null);
300
- tableContext.queryValues.set(def, newValue ?? null);
301
- assertNotPromise(newValue);
302
- value = newValue ?? null;
303
- };
212
+ e.stopPropagation();
213
+ }
214
+ let showFromDateModal = $state(false);
215
+ let showToDateModal = $state(false);
216
+ function generateOnChangeObject(_label) {
217
+ return (newValue) => {
218
+ tableContext.paginationRepo?.filterBy(def.id, newValue?.value ?? null);
219
+ tableContext.queryValues.set(def, newValue);
220
+ assertNotPromise(newValue);
221
+ value = newValue;
222
+ };
223
+ }
224
+ function generateOnChangePrimitive(_label) {
225
+ // AsPrimitive QuerySelect hands us the primitive value directly.
226
+ return (newValue) => {
227
+ tableContext.paginationRepo?.filterBy(def.id, newValue ?? null);
228
+ tableContext.queryValues.set(def, newValue ?? null);
229
+ assertNotPromise(newValue);
230
+ value = newValue ?? null;
231
+ };
232
+ }
233
+ let isNullFilter = $derived(selectedNumberFilterType?.value == NumberDateFilterMode.null);
234
+ /**
235
+ * Debounce window for typed filter inputs. For a 10k-row table each
236
+ * keystroke would otherwise kick off a full filter pass; coalescing
237
+ * adjacent keystrokes within `FILTER_DEBOUNCE_MS` cuts the cost to
238
+ * roughly one filter pass per pause-in-typing.
239
+ */
240
+ const FILTER_DEBOUNCE_MS = 300;
241
+ /**
242
+ * Sequence counter used to discard the result of a stale debounce
243
+ * timer. When the user types again before the timer fires, the
244
+ * `pendingSeq` increments; only the timer whose captured `seq`
245
+ * matches `pendingSeq` is allowed to call `filterBy`.
246
+ */
247
+ let pendingSeq = 0;
248
+ let debounceTimer;
249
+ function debouncedFilterBy(value) {
250
+ pendingSeq += 1;
251
+ const seq = pendingSeq;
252
+ if (debounceTimer !== undefined) {
253
+ clearTimeout(debounceTimer);
304
254
  }
305
-
306
- let isNullFilter = $derived(selectedNumberFilterType?.value == NumberDateFilterMode.null);
307
-
308
- /**
309
- * Debounce window for typed filter inputs. For a 10k-row table each
310
- * keystroke would otherwise kick off a full filter pass; coalescing
311
- * adjacent keystrokes within `FILTER_DEBOUNCE_MS` cuts the cost to
312
- * roughly one filter pass per pause-in-typing.
313
- */
314
- const FILTER_DEBOUNCE_MS = 300;
315
-
316
- /**
317
- * Sequence counter used to discard the result of a stale debounce
318
- * timer. When the user types again before the timer fires, the
319
- * `pendingSeq` increments; only the timer whose captured `seq`
320
- * matches `pendingSeq` is allowed to call `filterBy`.
321
- */
322
- let pendingSeq = 0;
323
- let debounceTimer: ReturnType<typeof setTimeout> | undefined;
324
-
325
- function debouncedFilterBy(value: unknown): void {
326
- pendingSeq += 1;
327
- const seq = pendingSeq;
328
- if (debounceTimer !== undefined) {
329
- clearTimeout(debounceTimer);
255
+ debounceTimer = setTimeout(() => {
256
+ debounceTimer = undefined;
257
+ if (seq !== pendingSeq) {
258
+ // A newer keystroke superseded this one before the timer
259
+ // fired; the newer timer will handle the filter call.
260
+ return;
330
261
  }
331
- debounceTimer = setTimeout(() => {
332
- debounceTimer = undefined;
333
- if (seq !== pendingSeq) {
334
- // A newer keystroke superseded this one before the timer
335
- // fired; the newer timer will handle the filter call.
336
- return;
337
- }
338
- tableContext.paginationRepo?.filterBy(def.id, value);
339
- }, FILTER_DEBOUNCE_MS);
262
+ tableContext.paginationRepo?.filterBy(def.id, value);
263
+ }, FILTER_DEBOUNCE_MS);
264
+ }
265
+ onDestroy(() => {
266
+ if (debounceTimer !== undefined) {
267
+ clearTimeout(debounceTimer);
268
+ debounceTimer = undefined;
340
269
  }
341
-
342
- onDestroy(() => {
343
- if (debounceTimer !== undefined) {
344
- clearTimeout(debounceTimer);
345
- debounceTimer = undefined;
346
- }
347
- });
348
- </script>
349
-
350
- <div class="input-cell {inputId}">
351
- {#if isSearchable}
352
- <div class="input-wrapper-parent">
353
- {#if isDate}
354
- {@const dateValues = tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)
355
- ?.value ?? [null, null]}
356
- {#if isDateTuple(dateValues)}
357
- {@const [fromDate, toDate] = dateValues}
358
- <div class="spaced-around">
359
- <Button
360
- size={Size.FillHeight}
361
- fullWidth
362
- onclick={() => {
363
- showFromDateModal = true;
364
- }}>From {fromDate?.toLocaleDateString() ?? 'Any Date'}</Button
365
- >
366
-
367
- <Button
368
- size={Size.FillHeight}
369
- fullWidth
370
- onclick={() => {
371
- showToDateModal = true;
372
- }}>To {toDate?.toLocaleDateString() ?? 'Any Date'}</Button
373
- >
374
- </div>
375
-
376
- {#if showFromDateModal}
377
- <DateModal
378
- header="Edit Date From"
379
- value={fromDate}
380
- cancelEditing={() => {
381
- showFromDateModal = false;
382
- }}
383
- confirm={(e) => {
384
- tableContext.paginationRepo?.filterBy(def.id, [e, toDate]);
385
- showFromDateModal = false;
386
- }}
387
- />
388
- {/if}
389
-
390
- {#if showToDateModal}
391
- <DateModal
392
- inclusive
393
- header="Edit Date To"
394
- value={toDate}
395
- cancelEditing={() => {
396
- showToDateModal = false;
397
- }}
398
- confirm={(e) => {
399
- tableContext.paginationRepo?.filterBy(def.id, [fromDate, e]);
400
- showToDateModal = false;
401
- }}
402
- />
403
- {/if}
404
- {/if}
405
- {:else if isSelect}
406
- {#if hasQuery(customFilter)}
407
- {#if customFilter.valueAsObject !== true}
408
- {@const label = customFilter.id}
409
-
410
- <!-- TODO: multiselect -->
411
-
412
- <!-- FIXME: Most likely Unsafe -->
413
- <QuerySelect
414
- valueAsObject={false}
415
- clearable
416
- name={customFilter.objectName ?? 'Item'}
417
- valueProp="value"
418
- labelProp="label"
419
- value={value as null}
420
- query={customFilter.query}
421
- bind:options={lastQueryOptions}
422
- onchange={generateOnChangePrimitive(label)}
423
- />
424
- {:else}
425
- {@const label = customFilter.id}
426
-
427
- <!-- TODO: multiselect -->
428
-
429
- <QuerySelect
430
- valueAsObject={true}
431
- clearable
432
- name={customFilter.objectName ?? 'Item'}
433
- valueProp="value"
434
- labelProp="label"
435
- value={value as null}
436
- query={customFilter.query}
437
- bind:options={lastQueryOptions}
438
- onchange={generateOnChangeObject(label)}
439
- />
440
- {/if}
441
- {:else}
442
- {#await options then options}
443
- <MultiSelect
444
- labelProp="label"
445
- valueProp="value"
446
- {options}
447
- valueAsObject
448
- onchange={(e: Array<SelectOption<unknown>>) => {
449
- tableContext.paginationRepo?.filterBy(
450
- def.id,
451
- e.map((it) => it.value)
452
- );
453
- }}
454
- selectedValues={selectedOptions}
455
- collapseSelection
456
- />
457
- {/await}
458
- {/if}
459
- {:else if def instanceof CheckboxDef}
460
- {@const value = (() => {
461
- switch (tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value) {
462
- case true: {
463
- return Ternary.True;
464
- }
465
- case false: {
466
- return Ternary.False;
467
- }
468
- default: {
469
- return Ternary.Neutral;
470
- }
471
- }
472
- })()}
473
- <div class="centered">
474
- <ThreeStateRadio
475
- {value}
476
- yesLabel="Selected"
477
- noLabel="Not Selected"
478
- neutralLabel="Any"
479
- neutralIcon={{ name: 'circle-minus' }}
480
- onchange={(v) => {
481
- switch (v) {
482
- case Ternary.False: {
483
- tableContext.paginationRepo?.filterBy(def.id, false);
484
- return;
485
- }
486
- case Ternary.True: {
487
- tableContext.paginationRepo?.filterBy(def.id, true);
488
- return;
489
- }
490
- case Ternary.Neutral:
491
- default: {
492
- tableContext.paginationRepo?.filterBy(def.id, null);
493
- return;
494
- }
495
- }
496
- }}
497
- />
498
- </div>
499
- {:else if isNumeric}
500
- {#if selectedNumberFilterType?.value === NumberDateFilterMode.Between}
501
- <div class="input-wrapper left">
502
- <NumberInput
503
- data-type="quicksearch"
504
- id={inputId}
505
- bind:value={valueMin}
506
- variant={InputVariant.Table}
507
- placeholder="Min&hellip;"
508
- oninput={() => {
509
- debouncedFilterBy([valueMin, valueMax]);
510
- }}
511
- onkeydown={closeOnCtrlF}
512
- />
513
- </div>
514
- <div class="input-wrapper right">
515
- <NumberInput
516
- data-type="quicksearch"
517
- id={inputId}
518
- bind:value={valueMax}
519
- placeholder="Max&hellip;"
520
- variant={InputVariant.Table}
521
- oninput={() => {
522
- debouncedFilterBy([valueMin, valueMax]);
523
- }}
524
- onkeydown={closeOnCtrlF}
525
- />
526
- </div>
527
- {:else}
528
- <div class="input-wrapper right">
529
- {#await isNullFilter}
530
- loading
531
- {:then isNullFilter}
532
- {#if isNullFilter}
533
- <TextInput
534
- data-type="quicksearch"
535
- id={inputId}
536
- value="is Null"
537
- disabled
538
- variant={InputVariant.Table}
539
- />
540
- {:else}
541
- <!-- NOTE: This is a text input in order to support the __in functionality, which is a comma separated list of numbers -->
542
- <TextInput
543
- data-type="quicksearch"
544
- id={inputId}
545
- bind:value={value as string | null}
546
- placeholder="Filter&hellip;"
547
- variant={InputVariant.Table}
548
- oninput={() => {
549
- debouncedFilterBy([value || null]);
550
- }}
551
- onkeydown={closeOnCtrlF}
552
- />
553
- {/if}
554
- {:catch e}
555
- <!-- eslint-disable-next-line svelte/no-at-debug-tags -->
556
- {@debug e}
557
- {/await}
558
- </div>
559
- {/if}
560
- {:else if value == null || typeof value == 'string'}
561
- <div class="input-wrapper right">
562
- {#if selectedStringFilterType?.value == StringFilterMode.null}
563
- <TextInput
564
- data-type="quicksearch"
565
- id={inputId}
566
- value="is Null"
567
- disabled
568
- variant={InputVariant.Table}
569
- />
570
- {:else if selectedStringFilterType?.value == StringFilterMode.Empty}
571
- <TextInput
572
- data-type="quicksearch"
573
- id={inputId}
574
- value="is Empty"
575
- disabled
576
- variant={InputVariant.Table}
577
- />
578
- {:else}
579
- <TextInput
580
- data-type="quicksearch"
581
- id={inputId}
582
- oninput={() => {
583
- debouncedFilterBy(value);
584
- }}
585
- variant={InputVariant.Table}
586
- bind:value={value as string | null}
587
- placeholder="Filter&hellip;"
588
- />
589
- {/if}
590
- </div>
591
- {/if}
592
-
593
- <!-- TODO button for date, checkbox or select for checkbox -->
594
- </div>
595
-
596
- {#if !isSelect && !isDate && !(def instanceof CheckboxDef)}
597
- <div class="stick-right">
598
- <Action
599
- data-type="quicksearch"
600
- size={Size.FillHeight}
601
- colourSet={ColourSet.Auxiliary}
602
- icon={{
603
- name: 'filter-2',
604
- size: Size.MediumLarge,
605
- colour: Colour['$ui-blue-5'],
606
- }}
607
- onclick={() => {
608
- if (!openedSelect) {
609
- return;
610
- }
611
- if (openedSelect.value === selectSymbol) {
612
- openedSelect.value = null;
613
- } else {
614
- openedSelect.value = selectSymbol;
615
- }
616
- }}
617
- />
618
- </div>
619
- {/if}
620
- {/if}
621
-
622
- {#if isSelectOpen}
623
- <div
624
- class="floating"
625
- transition:fade={{ easing: cubicOut, duration: 100 }}
626
- >
627
- <div class="select-wrapper">
628
- {#if isNumeric}
629
- <SingleSelect
630
- options={numberDateFilterTypes}
631
- labelProp="label"
632
- valueProp="value"
633
- name="Filter Type"
634
- valueAsObject
635
- value={selectedNumberFilterType}
636
- onchange={(newValue: SelectOption<NumberDateFilterMode> | null) => {
637
- const oldFilterType = selectedNumberFilterType;
638
-
639
- const newFilterType = newValue?.value ?? NumberDateFilterMode.In;
640
-
641
- if (newFilterType !== oldFilterType?.value) {
642
- tableContext.paginationRepo?.setFilterMode(def.id, newFilterType);
643
-
644
- if (newFilterType === NumberDateFilterMode.Between) {
645
- tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMin]);
646
- } else if (newFilterType === NumberDateFilterMode.In) {
647
- tableContext.paginationRepo?.filterBy(def.id, [value]);
648
- } else {
649
- tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMax]);
650
- }
651
- }
652
- }}
653
- onkeydown={(e: KeyboardEvent) => {
654
- e.stopPropagation();
655
- }}
656
- />
657
- {:else}
658
- <SingleSelect
659
- options={stringFilterTypes}
660
- labelProp="label"
661
- valueProp="value"
662
- name="Filter Type"
663
- valueAsObject
664
- value={selectedStringFilterType}
665
- onchange={(newValue: SelectOption<StringFilterMode> | null) => {
666
- const filterType = newValue?.value ?? StringFilterMode.Contains;
667
- tableContext.paginationRepo?.setFilterMode(def.id, filterType);
668
- }}
669
- onkeydown={(e: KeyboardEvent) => {
670
- e.stopPropagation();
671
- }}
672
- />
673
- {/if}
674
- </div>
675
- </div>
676
- {/if}
677
- </div>
678
-
270
+ });
271
+ </script>
272
+
273
+ <div class="input-cell {inputId}">
274
+ {#if isSearchable}
275
+ <div class="input-wrapper-parent">
276
+ {#if isDate}
277
+ {@const dateValues = tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)
278
+ ?.value ?? [null, null]}
279
+ {#if isDateTuple(dateValues)}
280
+ {@const [fromDate, toDate] = dateValues}
281
+ <div class="spaced-around">
282
+ <Button
283
+ size={Size.FillHeight}
284
+ fullWidth
285
+ onclick={() => {
286
+ showFromDateModal = true;
287
+ }}>From {fromDate?.toLocaleDateString() ?? 'Any Date'}</Button
288
+ >
289
+
290
+ <Button
291
+ size={Size.FillHeight}
292
+ fullWidth
293
+ onclick={() => {
294
+ showToDateModal = true;
295
+ }}>To {toDate?.toLocaleDateString() ?? 'Any Date'}</Button
296
+ >
297
+ </div>
298
+
299
+ {#if showFromDateModal}
300
+ <DateModal
301
+ header="Edit Date From"
302
+ value={fromDate}
303
+ cancelEditing={() => {
304
+ showFromDateModal = false;
305
+ }}
306
+ confirm={(e) => {
307
+ tableContext.paginationRepo?.filterBy(def.id, [e, toDate]);
308
+ showFromDateModal = false;
309
+ }}
310
+ />
311
+ {/if}
312
+
313
+ {#if showToDateModal}
314
+ <DateModal
315
+ inclusive
316
+ header="Edit Date To"
317
+ value={toDate}
318
+ cancelEditing={() => {
319
+ showToDateModal = false;
320
+ }}
321
+ confirm={(e) => {
322
+ tableContext.paginationRepo?.filterBy(def.id, [fromDate, e]);
323
+ showToDateModal = false;
324
+ }}
325
+ />
326
+ {/if}
327
+ {/if}
328
+ {:else if isSelect}
329
+ {#if hasQuery(customFilter)}
330
+ {#if customFilter.valueAsObject !== true}
331
+ {@const label = customFilter.id}
332
+
333
+ <!-- TODO: multiselect -->
334
+
335
+ <!-- FIXME: Most likely Unsafe -->
336
+ <QuerySelect
337
+ valueAsObject={false}
338
+ clearable
339
+ name={customFilter.objectName ?? 'Item'}
340
+ valueProp="value"
341
+ labelProp="label"
342
+ value={value as null}
343
+ query={customFilter.query}
344
+ bind:options={lastQueryOptions}
345
+ onchange={generateOnChangePrimitive(label)}
346
+ />
347
+ {:else}
348
+ {@const label = customFilter.id}
349
+
350
+ <!-- TODO: multiselect -->
351
+
352
+ <QuerySelect
353
+ valueAsObject={true}
354
+ clearable
355
+ name={customFilter.objectName ?? 'Item'}
356
+ valueProp="value"
357
+ labelProp="label"
358
+ value={value as null}
359
+ query={customFilter.query}
360
+ bind:options={lastQueryOptions}
361
+ onchange={generateOnChangeObject(label)}
362
+ />
363
+ {/if}
364
+ {:else}
365
+ {#await options then options}
366
+ <MultiSelect
367
+ labelProp="label"
368
+ valueProp="value"
369
+ {options}
370
+ valueAsObject
371
+ onchange={(e: Array<SelectOption<unknown>>) => {
372
+ tableContext.paginationRepo?.filterBy(
373
+ def.id,
374
+ e.map((it) => it.value)
375
+ );
376
+ }}
377
+ selectedValues={selectedOptions}
378
+ collapseSelection
379
+ />
380
+ {/await}
381
+ {/if}
382
+ {:else if def instanceof CheckboxDef}
383
+ {@const value = (() => {
384
+ switch (tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value) {
385
+ case true: {
386
+ return Ternary.True;
387
+ }
388
+ case false: {
389
+ return Ternary.False;
390
+ }
391
+ default: {
392
+ return Ternary.Neutral;
393
+ }
394
+ }
395
+ })()}
396
+ <div class="centered">
397
+ <ThreeStateRadio
398
+ {value}
399
+ yesLabel="Selected"
400
+ noLabel="Not Selected"
401
+ neutralLabel="Any"
402
+ neutralIcon={{ name: 'circle-minus' }}
403
+ onchange={(v) => {
404
+ switch (v) {
405
+ case Ternary.False: {
406
+ tableContext.paginationRepo?.filterBy(def.id, false);
407
+ return;
408
+ }
409
+ case Ternary.True: {
410
+ tableContext.paginationRepo?.filterBy(def.id, true);
411
+ return;
412
+ }
413
+ case Ternary.Neutral:
414
+ default: {
415
+ tableContext.paginationRepo?.filterBy(def.id, null);
416
+ return;
417
+ }
418
+ }
419
+ }}
420
+ />
421
+ </div>
422
+ {:else if isNumeric}
423
+ {#if selectedNumberFilterType?.value === NumberDateFilterMode.Between}
424
+ <div class="input-wrapper left">
425
+ <NumberInput
426
+ data-type="quicksearch"
427
+ id={inputId}
428
+ bind:value={valueMin}
429
+ variant={InputVariant.Table}
430
+ placeholder="Min&hellip;"
431
+ oninput={() => {
432
+ debouncedFilterBy([valueMin, valueMax]);
433
+ }}
434
+ onkeydown={closeOnCtrlF}
435
+ />
436
+ </div>
437
+ <div class="input-wrapper right">
438
+ <NumberInput
439
+ data-type="quicksearch"
440
+ id={inputId}
441
+ bind:value={valueMax}
442
+ placeholder="Max&hellip;"
443
+ variant={InputVariant.Table}
444
+ oninput={() => {
445
+ debouncedFilterBy([valueMin, valueMax]);
446
+ }}
447
+ onkeydown={closeOnCtrlF}
448
+ />
449
+ </div>
450
+ {:else}
451
+ <div class="input-wrapper right">
452
+ {#await isNullFilter}
453
+ loading
454
+ {:then isNullFilter}
455
+ {#if isNullFilter}
456
+ <TextInput
457
+ data-type="quicksearch"
458
+ id={inputId}
459
+ value="is Null"
460
+ disabled
461
+ variant={InputVariant.Table}
462
+ />
463
+ {:else}
464
+ <!-- NOTE: This is a text input in order to support the __in functionality, which is a comma separated list of numbers -->
465
+ <TextInput
466
+ data-type="quicksearch"
467
+ id={inputId}
468
+ bind:value={value as string | null}
469
+ placeholder="Filter&hellip;"
470
+ variant={InputVariant.Table}
471
+ oninput={() => {
472
+ debouncedFilterBy([value || null]);
473
+ }}
474
+ onkeydown={closeOnCtrlF}
475
+ />
476
+ {/if}
477
+ {:catch e}
478
+ <!-- eslint-disable-next-line svelte/no-at-debug-tags -->
479
+ {@debug e}
480
+ {/await}
481
+ </div>
482
+ {/if}
483
+ {:else if value == null || typeof value == 'string'}
484
+ <div class="input-wrapper right">
485
+ {#if selectedStringFilterType?.value == StringFilterMode.null}
486
+ <TextInput
487
+ data-type="quicksearch"
488
+ id={inputId}
489
+ value="is Null"
490
+ disabled
491
+ variant={InputVariant.Table}
492
+ />
493
+ {:else if selectedStringFilterType?.value == StringFilterMode.Empty}
494
+ <TextInput
495
+ data-type="quicksearch"
496
+ id={inputId}
497
+ value="is Empty"
498
+ disabled
499
+ variant={InputVariant.Table}
500
+ />
501
+ {:else}
502
+ <TextInput
503
+ data-type="quicksearch"
504
+ id={inputId}
505
+ oninput={() => {
506
+ debouncedFilterBy(value);
507
+ }}
508
+ variant={InputVariant.Table}
509
+ bind:value={value as string | null}
510
+ placeholder="Filter&hellip;"
511
+ />
512
+ {/if}
513
+ </div>
514
+ {/if}
515
+
516
+ <!-- TODO button for date, checkbox or select for checkbox -->
517
+ </div>
518
+
519
+ {#if !isSelect && !isDate && !(def instanceof CheckboxDef)}
520
+ <div class="stick-right">
521
+ <Action
522
+ data-type="quicksearch"
523
+ size={Size.FillHeight}
524
+ colourSet={ColourSet.Auxiliary}
525
+ icon={{
526
+ name: 'filter-2',
527
+ size: Size.MediumLarge,
528
+ colour: Colour['$ui-blue-5'],
529
+ }}
530
+ onclick={() => {
531
+ if (!openedSelect) {
532
+ return;
533
+ }
534
+ if (openedSelect.value === selectSymbol) {
535
+ openedSelect.value = null;
536
+ } else {
537
+ openedSelect.value = selectSymbol;
538
+ }
539
+ }}
540
+ />
541
+ </div>
542
+ {/if}
543
+ {/if}
544
+
545
+ {#if isSelectOpen}
546
+ <div
547
+ class="floating"
548
+ transition:fade={{ easing: cubicOut, duration: 100 }}
549
+ >
550
+ <div class="select-wrapper">
551
+ {#if isNumeric}
552
+ <SingleSelect
553
+ options={numberDateFilterTypes}
554
+ labelProp="label"
555
+ valueProp="value"
556
+ name="Filter Type"
557
+ valueAsObject
558
+ value={selectedNumberFilterType}
559
+ onchange={(newValue: SelectOption<NumberDateFilterMode> | null) => {
560
+ const oldFilterType = selectedNumberFilterType;
561
+
562
+ const newFilterType = newValue?.value ?? NumberDateFilterMode.In;
563
+
564
+ if (newFilterType !== oldFilterType?.value) {
565
+ tableContext.paginationRepo?.setFilterMode(def.id, newFilterType);
566
+
567
+ if (newFilterType === NumberDateFilterMode.Between) {
568
+ tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMin]);
569
+ } else if (newFilterType === NumberDateFilterMode.In) {
570
+ tableContext.paginationRepo?.filterBy(def.id, [value]);
571
+ } else {
572
+ tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMax]);
573
+ }
574
+ }
575
+ }}
576
+ onkeydown={(e: KeyboardEvent) => {
577
+ e.stopPropagation();
578
+ }}
579
+ />
580
+ {:else}
581
+ <SingleSelect
582
+ options={stringFilterTypes}
583
+ labelProp="label"
584
+ valueProp="value"
585
+ name="Filter Type"
586
+ valueAsObject
587
+ value={selectedStringFilterType}
588
+ onchange={(newValue: SelectOption<StringFilterMode> | null) => {
589
+ const filterType = newValue?.value ?? StringFilterMode.Contains;
590
+ tableContext.paginationRepo?.setFilterMode(def.id, filterType);
591
+ }}
592
+ onkeydown={(e: KeyboardEvent) => {
593
+ e.stopPropagation();
594
+ }}
595
+ />
596
+ {/if}
597
+ </div>
598
+ </div>
599
+ {/if}
600
+ </div>
601
+
679
602
  <style>.focusable,
680
603
  .checkbox-cell,
681
604
  .input-cell,
@@ -815,4 +738,4 @@ div.input-wrapper-parent > :global(.select-wrapper) {
815
738
  height: 60px;
816
739
  --input-height: 60px;
817
740
  background-color: #ffffff;
818
- }</style>
741
+ }</style>