@lavalogic/scoria 0.35.0 → 0.36.0

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