@lavalogic/scoria 0.33.0 → 0.35.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 (130) hide show
  1. package/README.md +58 -58
  2. package/dist/Components/AccordionGroup.svelte +44 -63
  3. package/dist/Components/AccordionGroup.svelte.d.ts +2 -25
  4. package/dist/Components/AccordionGroupButton.svelte +106 -114
  5. package/dist/Components/AccordionGroupButton.svelte.d.ts +2 -2
  6. package/dist/Components/AccordionGroupButtonProps.d.ts +21 -0
  7. package/dist/Components/AccordionGroupButtonProps.js +1 -0
  8. package/dist/Components/Action.svelte +138 -138
  9. package/dist/Components/AlertModal.svelte +125 -125
  10. package/dist/Components/Base/ContextWrapper.svelte +50 -50
  11. package/dist/Components/Base/Pagination.svelte +190 -190
  12. package/dist/Components/Base/Snippets.svelte +29 -29
  13. package/dist/Components/BigRadioSet.svelte +29 -29
  14. package/dist/Components/Bubble.svelte +77 -77
  15. package/dist/Components/Button.svelte +197 -197
  16. package/dist/Components/Checkbox.svelte +68 -68
  17. package/dist/Components/CollapsibleCard.svelte +70 -70
  18. package/dist/Components/Contexts/ContextMenu.svelte +157 -157
  19. package/dist/Components/Contexts/ContextMenuDivider.svelte +11 -11
  20. package/dist/Components/Contexts/ContextMenuOption.svelte +52 -52
  21. package/dist/Components/Contexts/Toast.svelte +61 -61
  22. package/dist/Components/Contexts/ToastContainer.svelte +46 -46
  23. package/dist/Components/Contexts/Tooltip.svelte +26 -26
  24. package/dist/Components/DataMatrixIcon.svelte +41 -41
  25. package/dist/Components/DateInput.svelte +109 -109
  26. package/dist/Components/DatePicker.svelte +312 -312
  27. package/dist/Components/DesktopModal.svelte +106 -106
  28. package/dist/Components/Dial.svelte +151 -151
  29. package/dist/Components/DimensionInput.svelte +184 -184
  30. package/dist/Components/DragMenu/DragMenuHolder.svelte +106 -106
  31. package/dist/Components/DragMenu/DraggableCard.svelte +41 -41
  32. package/dist/Components/FileCard.svelte +20 -20
  33. package/dist/Components/FileUpload.svelte +196 -196
  34. package/dist/Components/GridInput.svelte +190 -190
  35. package/dist/Components/GridInputSet.svelte +22 -22
  36. package/dist/Components/HorizontalTabGroup.svelte +78 -78
  37. package/dist/Components/HorizontalTabGroupButton.svelte +96 -96
  38. package/dist/Components/Icon.svelte +97 -97
  39. package/dist/Components/Icons.js +357 -357
  40. package/dist/Components/LoadingAnimation.svelte +32 -32
  41. package/dist/Components/LoadingModal.svelte +47 -47
  42. package/dist/Components/LoadingOverlay.svelte +27 -27
  43. package/dist/Components/Modal.svelte +251 -251
  44. package/dist/Components/MultiSelect.svelte +197 -197
  45. package/dist/Components/Nav/Nav.svelte +97 -97
  46. package/dist/Components/Nav/NavServiceMenuList.svelte +34 -34
  47. package/dist/Components/Nav/NavTab.svelte +91 -91
  48. package/dist/Components/Nav/ServiceMenuItem.svelte +91 -91
  49. package/dist/Components/Nav/ServicesNav.svelte +53 -53
  50. package/dist/Components/NumberInput.svelte +144 -144
  51. package/dist/Components/PageHeading.svelte +36 -36
  52. package/dist/Components/PasswordInput.svelte +73 -73
  53. package/dist/Components/Portal.svelte +25 -25
  54. package/dist/Components/ProgressBubble.svelte +80 -80
  55. package/dist/Components/QuerySelect.svelte +411 -411
  56. package/dist/Components/SingleSelect.svelte +261 -261
  57. package/dist/Components/StepButton.svelte +120 -120
  58. package/dist/Components/StepForm.svelte +108 -108
  59. package/dist/Components/Switch.svelte +36 -36
  60. package/dist/Components/Table/Body/QuickSearchCell.svelte +632 -632
  61. package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +30 -30
  62. package/dist/Components/Table/Body/Rows/ColumnList.svelte +56 -56
  63. package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +179 -179
  64. package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +49 -49
  65. package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +180 -180
  66. package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +102 -102
  67. package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +119 -119
  68. package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +114 -114
  69. package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +96 -96
  70. package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +72 -72
  71. package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +131 -131
  72. package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +144 -144
  73. package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +238 -238
  74. package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +358 -358
  75. package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +338 -338
  76. package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +183 -183
  77. package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +309 -309
  78. package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +369 -369
  79. package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +170 -170
  80. package/dist/Components/Table/Body/Rows/TableRow.svelte +259 -259
  81. package/dist/Components/Table/Body/TableBody.svelte +67 -67
  82. package/dist/Components/Table/Headers/HighlightAllHeader.svelte +39 -39
  83. package/dist/Components/Table/Headers/SelectAllHeader.svelte +28 -28
  84. package/dist/Components/Table/Headers/TableHead.svelte +260 -260
  85. package/dist/Components/Table/Misc/ColumnPanel.svelte +78 -78
  86. package/dist/Components/Table/Misc/ColumnPanelModal.svelte +99 -99
  87. package/dist/Components/Table/Misc/FilterInput.svelte +556 -556
  88. package/dist/Components/Table/Misc/FilterPanel.svelte +46 -46
  89. package/dist/Components/Table/Misc/FilterPanelModal.svelte +67 -67
  90. package/dist/Components/Table/Misc/SidePanel.svelte +32 -32
  91. package/dist/Components/Table/Misc/TableFooter.svelte +304 -304
  92. package/dist/Components/Table/Misc/TableHorizontalBar.svelte +156 -156
  93. package/dist/Components/Table/Misc/TableSidebar.svelte +176 -176
  94. package/dist/Components/Table/Misc/ToolboxPanel.svelte +77 -77
  95. package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +91 -91
  96. package/dist/Components/Table/NestedTable.svelte +84 -84
  97. package/dist/Components/Table/Table.svelte +492 -492
  98. package/dist/Components/Test/TestContextConsumer.svelte +13 -13
  99. package/dist/Components/TextArea.svelte +108 -108
  100. package/dist/Components/TextInput.svelte +147 -147
  101. package/dist/Components/ThreeStateRadio.svelte +124 -124
  102. package/dist/Components/Touch/BoolCard.svelte +52 -52
  103. package/dist/Components/Touch/DateModal.svelte +217 -217
  104. package/dist/Components/Touch/EditCard.svelte +52 -52
  105. package/dist/Components/Touch/InfoCard.svelte +24 -24
  106. package/dist/Components/Touch/InfoTextCard.svelte +21 -21
  107. package/dist/Components/Touch/ModalExplanation.svelte +20 -20
  108. package/dist/Components/Touch/NumberKeyboard.svelte +268 -268
  109. package/dist/Components/Touch/NumberModal.svelte +69 -69
  110. package/dist/Components/Touch/PlusMinusCard.svelte +50 -50
  111. package/dist/Components/Touch/SelectModal.svelte +105 -105
  112. package/dist/Components/Touch/SummaryCard.svelte +65 -65
  113. package/dist/Components/Touch/TextAreaModal.svelte +86 -86
  114. package/dist/Components/Touch/TextModal.svelte +123 -123
  115. package/dist/Components/Touch/TouchCard.svelte +28 -28
  116. package/dist/Components/Touch/TouchModal.svelte +259 -259
  117. package/dist/Components/Touch/UtilityButton.svelte +91 -91
  118. package/dist/Components/VerticalTabGroup.svelte +66 -66
  119. package/dist/Components/VerticalTabGroupButton.svelte +97 -97
  120. package/dist/Helpers/Datamatrix.d.ts +10 -10
  121. package/dist/Helpers/Datamatrix.js +1 -1
  122. package/dist/Helpers/Helpers.svelte.js +6 -4
  123. package/dist/Types/Examples/ExampleModal.svelte +34 -34
  124. package/dist/index.d.ts +1 -0
  125. package/dist/scss/_basics.scss +12 -12
  126. package/dist/scss/_colours.scss +134 -134
  127. package/dist/scss/_mixins.scss +3016 -3016
  128. package/dist/scss/_sizing.scss +69 -69
  129. package/dist/scss/_transitions.scss +8 -8
  130. package/package.json +2 -2
@@ -1,558 +1,558 @@
1
- <svelte:options runes />
2
-
3
- <script
4
- lang="ts"
5
- generics="T extends object, FilterValueType, FilterOptionsType"
6
- >
7
- import { dev } from '$app/environment';
8
- import Button from '../../Button.svelte';
9
- import MultiSelect from '../../MultiSelect.svelte';
10
- import NumberInput from '../../NumberInput.svelte';
11
- import QuerySelect from '../../QuerySelect.svelte';
12
- import SingleSelect from '../../SingleSelect.svelte';
13
- import TextInput from '../../TextInput.svelte';
14
- import DateModal from '../../Touch/DateModal.svelte';
15
- import { devCatch, hasQuery, isDateTuple, isNumericArray } from '../../../Helpers/Helpers.svelte.js';
16
- import type { SelectOption } from '../../../Types/Internal/SelectOption.js';
17
- import { getContext, onMount, untrack } from 'svelte';
18
- import { CheckboxDef } from '../Types/Columns/Definitions/Accessors/CheckboxDef.svelte.js';
19
- import { DateInputDef } from '../Types/Columns/Definitions/Accessors/DateInputDef.svelte.js';
20
- import { NumberInputDef } from '../Types/Columns/Definitions/Accessors/NumberInputDef.svelte.js';
21
- import { SelectDef } from '../Types/Columns/Definitions/Accessors/SelectDef.svelte.js';
22
- import { TableContext } from '../Types/Context/TableContext.svelte.js';
23
- import { CustomFilterType } from '../Types/Filtering/CustomFilterType.js';
24
- import { NumberDateFilterMode } from '../Types/Filtering/NumberDateFilterMode.js';
25
- import { StringFilterMode } from '../Types/Filtering/StringFilterMode.js';
26
- import type { FilterInputProps } from './FilterInputProps.js';
27
- import { numberDateFilterTypes } from './numberDateFilterTypes.js';
28
- import { stringFilterTypes } from './stringFilterTypes.js';
29
-
30
- const tableContext = getContext<TableContext<T, never>>(TableContext.identifier);
31
-
32
- const { def, customFilters }: FilterInputProps<T, FilterValueType, FilterOptionsType> = $props();
33
-
34
- const customFilter = $derived(customFilters?.get(def.id));
35
-
36
- const isRemoteSelect = $derived(customFilter?.type === CustomFilterType.Select);
37
-
38
- let value: /*SelectOption<unknown> |*/ unknown /*| null | undefined*/ = $state(null);
39
- let valueMin: number | null = $state(null);
40
- let valueMax: number | null = $state(null);
41
-
42
- let selectedOptions: Array<SelectOption<unknown>> = $state([]);
43
- let lastQueryOptions: Array<SelectOption<unknown>> = $state([]);
44
-
45
- const isNumeric = $derived(
46
- def instanceof NumberInputDef || customFilter?.type === CustomFilterType.Number
47
- );
48
-
49
- const hasFilterFn = $derived(!!def.filterFn && typeof def.filterFn === 'function');
50
-
51
- // const hasAcceptableColumnType = $derived(
52
- // (def instanceof AccessorDef && searchableTypes.has(def.accessorType)) ||
53
- // (def instanceof AbstractDisplayDef && searchableTypes.has(def.displayType))
54
- // );
55
-
56
- const isSearchable = $derived(
57
- def.allowFiltering && (hasFilterFn || customFilters?.has(def.id) == true)
58
- // ((hasFilterFn && hasAcceptableColumnType) || customFilters?.has(def.id) == true) // removed hasAcceptableColumnType due to it being redundant
59
- );
60
-
61
- const isSelect = $derived(
62
- def instanceof SelectDef || customFilter?.type === CustomFilterType.Select
63
- );
64
-
65
- const options = $derived(isSelect ? (def.getOptions?.() ?? customFilter?.options ?? []) : []);
66
-
67
- let optionsPromise: Array<SelectOption<unknown>> | Promise<Array<SelectOption<unknown>>> = $state(
68
- [] as Array<SelectOption<unknown>> | Promise<Array<SelectOption<unknown>>>
69
- );
70
-
71
- onMount(() => {
72
- retryOptionsPromise();
73
- });
74
-
75
- function retryOptionsPromise() {
76
- optionsPromise = def instanceof SelectDef ? def.getOptions() : [];
77
- }
78
-
79
- // function isNumber(def: ColumnDef<T>): def is INumberInputColumnAccessorDef<T> {
80
- // return 'type' in def && def.type === 'Number';
81
- // }
82
-
83
- const filteringStateValue = $derived(
84
- tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value
85
- );
86
-
87
- $effect(() => {
88
- if (isRemoteSelect && !tableContext.paginationRepo?.filtering) {
89
- return;
90
- }
91
- void filteringStateValue;
92
- if (def.debug) {
93
- console.debug(
94
- `${def.id}: `,
95
- 'FILTER INPUT: filtering state value:',
96
- $state.snapshot(filteringStateValue)
97
- );
98
- }
99
-
100
- if (hasQuery(customFilter)) {
101
- if (def.debug) {
102
- console.debug(`${def.id}: `, 'has query');
103
- }
104
-
105
- untrack(async () => {
106
- try {
107
- // as unknown as string | number | undefined;
108
-
109
- if (filteringStateValue != undefined) {
110
- if (def.debug) {
111
- console.debug(`${def.id}: query values:`, tableContext.dataRepo.__queryValues);
112
- }
113
- const x = await tableContext.dataRepo.__queryValues.get(customFilter.id);
114
-
115
- if (def.debug) {
116
- console.debug(`${def.id}: value:`, $state.snapshot(x));
117
- }
118
- value = (x ?? null) as SelectOption<unknown> | null;
119
- } else if (def.debug) {
120
- console.debug(
121
- `${def.id}: filtering state value was undefined`,
122
- $state.snapshot(tableContext.paginationRepo?.filtering)
123
- );
124
- }
125
- } catch (e) {
126
- console.warn(e);
127
- value = null;
128
- }
129
- }).catch(devCatch);
130
- } else if (isSelect) {
131
- if (def.debug) {
132
- console.debug(`${def.id}: is select (not query)`);
133
- }
134
- untrack(async () => {
135
- try {
136
- const opts = await options;
137
- if (def.debug) {
138
- console.debug(`${def.id}: possible options:`, $state.snapshot(options));
139
- }
140
- const x = opts.filter((it) => {
141
- if (it.value != null && (typeof it.value == 'string' || typeof it.value == 'number')) {
142
- return (filteringStateValue as Array<unknown> | undefined)?.includes(it.value);
143
- }
144
- return false;
145
- });
146
- if (def.debug) {
147
- console.debug(`${def.id}: selected options:`, x);
148
- }
149
- selectedOptions = x;
150
- } catch (e) {
151
- if (dev || def.debug) {
152
- console.debug(`${def.id}: `, e);
153
- }
154
- }
155
- }).catch(devCatch);
156
- } else if (isNumeric) {
157
- if (
158
- selectedNumberFilterType?.value === NumberDateFilterMode.Between &&
159
- tableContext.paginationRepo?.filtering
160
- ) {
161
- if (filteringStateValue != undefined) {
162
- const [min, max] = filteringStateValue as [number, number | undefined];
163
- value = min;
164
- valueMin = min;
165
- valueMax = max ?? null;
166
- } else {
167
- value = valueMin = valueMax = null;
168
- }
169
- } else if (tableContext.paginationRepo?.filtering) {
170
- if (filteringStateValue != undefined) {
171
- const [min] = filteringStateValue as [number, number | undefined];
172
- value = min;
173
- valueMin = min;
174
- valueMax = min;
175
- } else {
176
- value = valueMin = valueMax = null;
177
- }
178
- }
179
- } else if (tableContext.paginationRepo?.filtering) {
180
- if (
181
- filteringStateValue == null ||
182
- typeof filteringStateValue == 'string' ||
183
- typeof filteringStateValue == 'number'
184
- ) {
185
- value = filteringStateValue;
186
- } else if (typeof filteringStateValue !== 'boolean') {
187
- console.warn('unknown value type:', $state.snapshot(filteringStateValue));
188
- value = filteringStateValue as string; // HACK attempt to keep production running
189
- }
190
- }
191
- });
192
-
193
- onMount(() => {
194
- const filterOption =
195
- tableContext.paginationRepo?.filtering &&
196
- tableContext.paginationRepo.filtering.find((it) => it.id === def.id);
197
-
198
- if (filterOption) {
199
- if (isNumericArray(filterOption.value)) {
200
- [valueMin, valueMax] = filterOption.value;
201
- if (valueMax == undefined) {
202
- valueMax = valueMin;
203
- } else if (valueMin == undefined) {
204
- valueMin = valueMax;
205
- }
206
- } else if (!hasQuery(customFilter)) {
207
- const gotValue = filterOption.value;
208
- if (gotValue == null || typeof gotValue == 'string' || typeof gotValue == 'number') {
209
- value = gotValue;
210
- } else if (typeof filteringStateValue !== 'boolean') {
211
- console.warn('unknown value type:', $state.snapshot(gotValue));
212
- value = gotValue as string; // HACK attempt to keep production running
213
- }
214
- }
215
- }
216
-
217
- // selectedNumberFilterType =
218
- // (filterMode as NumberDateFilterMode | undefined) ?? NumberDateFilterMode.In;
219
- // selectedStringFilterType =
220
- // (filterMode as StringFilterMode | undefined) ?? StringFilterMode.Contains;
221
- });
222
-
223
- const filterMode = $derived(tableContext.paginationRepo?.filterModes.get(def.id));
224
- const selectedNumberFilterType: SelectOption<NumberDateFilterMode> | null = $derived.by(() => {
225
- const value = (filterMode as NumberDateFilterMode | undefined) ?? NumberDateFilterMode.In;
226
- return numberDateFilterTypes.find((it) => it.value === value) ?? null;
227
- });
228
- const selectedStringFilterType: SelectOption<StringFilterMode> | null = $derived.by(() => {
229
- const value = (filterMode as StringFilterMode | undefined) ?? StringFilterMode.Contains;
230
- return stringFilterTypes.find((it) => it.value === value) ?? null;
231
- });
232
-
233
- // let selectedNumberFilterType: NumberDateFilterMode = $state(NumberDateFilterMode.In);
234
- // let selectedStringFilterType: StringFilterMode = $state(StringFilterMode.Contains);
235
-
236
- const inputId: string = $derived(`${tableContext.tableName}-${def.id}-filter-type`);
237
-
238
- let showFromDateModal = $state(false);
239
- let showToDateModal = $state(false);
240
-
241
- function generateOnChangeObject(label: string) {
242
- return (newValue: SelectOption<unknown> | null) => {
243
- tableContext.paginationRepo?.filterBy(def.id, newValue?.value ?? null);
244
- tableContext.dataRepo.__queryValues.set(label, newValue ?? null);
245
- value = newValue ?? null;
246
- };
247
- }
248
-
249
- function generateOnChangePrimitive(label: string) {
250
- return (newValue: SelectOption<unknown> | null) => {
251
- tableContext.paginationRepo?.filterBy(def.id, newValue?.value ?? null);
252
- tableContext.dataRepo.__queryValues.set(label, newValue?.value ?? null);
253
- value = newValue?.value ?? null;
254
- };
255
- }
256
- </script>
257
-
258
- {#if isSearchable}
259
- <!-- TODO remove this check once datePicker is supported -->
260
- {#if def instanceof DateInputDef || def instanceof CheckboxDef}
261
- <label for={inputId}>{def.header}</label>
262
- {/if}
263
- <div class="row">
264
- {#if def instanceof DateInputDef}
265
- {@const dateValues =
266
- tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value ?? []}
267
- {#if isDateTuple(dateValues)}
268
- {@const [fromDate, toDate] = dateValues}
269
- <div class="spaced-around">
270
- <Button
271
- onclick={() => {
272
- showFromDateModal = true;
273
- }}>From {fromDate?.toLocaleDateString() ?? 'Any Date'}</Button
274
- >
275
-
276
- <Button
277
- onclick={() => {
278
- showToDateModal = true;
279
- }}>To {toDate?.toLocaleDateString() ?? 'Any Date'}</Button
280
- >
281
- </div>
282
-
283
- {#if showFromDateModal}
284
- <DateModal
285
- header="Edit Date From"
286
- value={fromDate}
287
- cancelEditing={() => {
288
- showFromDateModal = false;
289
- }}
290
- confirm={(e) => {
291
- tableContext.paginationRepo?.filterBy(def.id, [e, toDate]);
292
- showFromDateModal = false;
293
- }}
294
- />
295
- {/if}
296
-
297
- {#if showToDateModal}
298
- <DateModal
299
- inclusive
300
- header="Edit Date To"
301
- value={toDate}
302
- cancelEditing={() => {
303
- showToDateModal = false;
304
- }}
305
- confirm={(e) => {
306
- tableContext.paginationRepo?.filterBy(def.id, [fromDate, e]);
307
- showToDateModal = false;
308
- }}
309
- />
310
- {/if}
311
- {/if}
312
- {:else if def instanceof SelectDef && !isRemoteSelect}
313
- <div class="maxwidth">
314
- {#await optionsPromise then options}
315
- <MultiSelect
316
- label={customFilter?.id ??
317
- customFilter?.label ??
318
- customFilter?.objectName ??
319
- customFilter?.queryKey ??
320
- def.id}
321
- labelProp="label"
322
- valueProp="value"
323
- name={customFilter?.objectName ?? 'Item'}
324
- {options}
325
- valueAsObject={false}
326
- onchange={(e: Array<SelectOption<unknown>>) => {
327
- tableContext.paginationRepo?.filterBy(
328
- def.id,
329
- e.map((it) => it.value)
330
- );
331
- }}
332
- selectedValues={selectedOptions}
333
- collapseSelection
334
- />
335
- {:catch e}
336
- <span>Error retrieving options: {e.message}</span>
337
- <Button
338
- onclick={() => {
339
- retryOptionsPromise();
340
- }}>Retry</Button
341
- >
342
- {/await}
343
- </div>
344
- {:else if customFilter && isRemoteSelect}
345
- <div class="contents">
346
- {#await options then options}
347
- {#if hasQuery(customFilter)}
348
- {#if customFilter.valueAsObject !== true}
349
- {@const label = customFilter.id}
350
-
351
- <!-- TODO: multiselect -->
352
-
353
- <QuerySelect
354
- valueAsObject={false}
355
- {label}
356
- clearable
357
- name={customFilter.objectName ?? 'Item'}
358
- valueProp="value"
359
- labelProp="label"
360
- value={value as string | number | null}
361
- query={customFilter.query}
362
- bind:options={lastQueryOptions}
363
- onchange={generateOnChangePrimitive(label)}
364
- />
365
- {:else}
366
- {@const label = customFilter.id}
367
-
368
- <!-- TODO: multiselect -->
369
-
370
- <QuerySelect
371
- valueAsObject={true}
372
- {label}
373
- clearable
374
- name={customFilter.objectName ?? 'Item'}
375
- valueProp="value"
376
- labelProp="label"
377
- value={value as null}
378
- query={customFilter.query}
379
- bind:options={lastQueryOptions}
380
- onchange={generateOnChangeObject(label)}
381
- />
382
- {/if}
383
- {:else}
384
- <MultiSelect
385
- label={customFilter.id}
386
- labelProp="label"
387
- valueProp="value"
388
- name={customFilter.objectName ?? 'Item'}
389
- {options}
390
- valueAsObject={false}
391
- onchange={(e: Array<SelectOption<unknown>>) => {
392
- tableContext.paginationRepo?.filterBy(
393
- def.id,
394
- e.map((it) => it.value)
395
- );
396
- }}
397
- selectedValues={selectedOptions}
398
- collapseSelection
399
- />
400
- {/if}
401
- {/await}
402
- </div>
403
- {:else if def instanceof CheckboxDef}
404
- <div class="inner-row">
405
- <label>
406
- <input
407
- value={false}
408
- type="radio"
409
- bind:group={value}
410
- onchange={() => {
411
- tableContext.paginationRepo?.filterBy(def.id, value);
412
- }}
413
- />
414
- No
415
- </label>
416
- <label>
417
- <input
418
- value={undefined}
419
- type="radio"
420
- bind:group={value}
421
- onchange={() => {
422
- tableContext.paginationRepo?.filterBy(def.id, value);
423
- }}
424
- />
425
- Any
426
- </label>
427
- <label>
428
- <input
429
- value={true}
430
- type="radio"
431
- bind:group={value}
432
- onchange={() => {
433
- tableContext.paginationRepo?.filterBy(def.id, value);
434
- }}
435
- />
436
- Yes
437
- </label>
438
- </div>
439
- {:else if isNumeric}
440
- {@const label = typeof def.header === 'string' ? def.header : undefined}
441
- <div class="left">
442
- <SingleSelect
443
- {label}
444
- name="Filter Type"
445
- options={numberDateFilterTypes}
446
- labelProp="label"
447
- valueProp="value"
448
- valueAsObject
449
- value={selectedNumberFilterType}
450
- onchange={(newValue: SelectOption<NumberDateFilterMode> | null) => {
451
- const oldFilterType = selectedNumberFilterType;
452
-
453
- const newFilterType = newValue?.value ?? NumberDateFilterMode.In;
454
- tableContext.paginationRepo?.setFilterMode(def.id, newFilterType);
455
-
456
- if (selectedNumberFilterType?.value !== oldFilterType?.value) {
457
- if (oldFilterType?.value === NumberDateFilterMode.Between) {
458
- valueMax = null;
459
- tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMin]);
460
- }
461
-
462
- if (newFilterType === NumberDateFilterMode.In) {
463
- tableContext.paginationRepo?.filterBy(def.id, [value]);
464
- }
465
-
466
- // TODO what about the other modes?
467
- }
468
- }}
469
- onkeydown={(e: KeyboardEvent) => {
470
- e.stopPropagation();
471
- }}
472
- />
473
- </div>
474
- <div class="right">
475
- {#if selectedNumberFilterType?.value === NumberDateFilterMode.Between}
476
- <NumberInput
477
- id={inputId}
478
- bind:value={valueMin}
479
- oninput={() => {
480
- tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMax]);
481
- }}
482
- onkeydown={(e: KeyboardEvent) => {
483
- e.stopPropagation();
484
- }}
485
- />
486
- <span>and</span>
487
- <NumberInput
488
- id={inputId}
489
- bind:value={valueMax}
490
- oninput={() => {
491
- tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMax]);
492
- }}
493
- onkeydown={(e: KeyboardEvent) => {
494
- e.stopPropagation();
495
- }}
496
- />
497
- {:else if value == null || typeof value == 'string'}
498
- <TextInput
499
- id={inputId}
500
- bind:value
501
- oninput={() => {
502
- tableContext.paginationRepo?.filterBy(def.id, [value]);
503
- }}
504
- onkeydown={(e: KeyboardEvent) => {
505
- e.stopPropagation();
506
- }}
507
- />
508
- {:else if dev}
509
- error: {value?.toString()}
510
- <!-- eslint-disable-next-line svelte/no-at-debug-tags -->
511
- {@debug value}
512
- {/if}
513
- </div>
514
- {:else}
515
- {@const label = typeof def.header === 'string' ? def.header : undefined}
516
- <div class="left">
517
- <SingleSelect
518
- name="Filter Type"
519
- valueAsObject
520
- {label}
521
- options={stringFilterTypes}
522
- labelProp="label"
523
- valueProp="value"
524
- value={selectedStringFilterType}
525
- onchange={(newValue: SelectOption<StringFilterMode> | null) => {
526
- const filterType = newValue?.value ?? StringFilterMode.Contains;
527
- tableContext.paginationRepo?.setFilterMode(def.id, filterType);
528
- }}
529
- onkeydown={(e: KeyboardEvent) => {
530
- e.stopPropagation();
531
- }}
532
- />
533
- </div>
534
- <div class="right">
535
- {#if value == null || typeof value == 'string'}
536
- <TextInput
537
- id={inputId}
538
- bind:value
539
- oninput={() => {
540
- tableContext.paginationRepo?.filterBy(def.id, value);
541
- }}
542
- onkeydown={(e: KeyboardEvent) => {
543
- e.stopPropagation();
544
- }}
545
- />
546
- {:else if dev}
547
- error: {value?.toString()}
548
- <!-- eslint-disable-next-line svelte/no-at-debug-tags -->
549
- {@debug value}
550
- {/if}
551
- </div>
552
- {/if}
553
- </div>
554
- {/if}
555
-
1
+ <svelte:options runes />
2
+
3
+ <script
4
+ lang="ts"
5
+ generics="T extends object, FilterValueType, FilterOptionsType"
6
+ >
7
+ import { dev } from '$app/environment';
8
+ import Button from '../../Button.svelte';
9
+ import MultiSelect from '../../MultiSelect.svelte';
10
+ import NumberInput from '../../NumberInput.svelte';
11
+ import QuerySelect from '../../QuerySelect.svelte';
12
+ import SingleSelect from '../../SingleSelect.svelte';
13
+ import TextInput from '../../TextInput.svelte';
14
+ import DateModal from '../../Touch/DateModal.svelte';
15
+ import { devCatch, hasQuery, isDateTuple, isNumericArray } from '../../../Helpers/Helpers.svelte.js';
16
+ import type { SelectOption } from '../../../Types/Internal/SelectOption.js';
17
+ import { getContext, onMount, untrack } from 'svelte';
18
+ import { CheckboxDef } from '../Types/Columns/Definitions/Accessors/CheckboxDef.svelte.js';
19
+ import { DateInputDef } from '../Types/Columns/Definitions/Accessors/DateInputDef.svelte.js';
20
+ import { NumberInputDef } from '../Types/Columns/Definitions/Accessors/NumberInputDef.svelte.js';
21
+ import { SelectDef } from '../Types/Columns/Definitions/Accessors/SelectDef.svelte.js';
22
+ import { TableContext } from '../Types/Context/TableContext.svelte.js';
23
+ import { CustomFilterType } from '../Types/Filtering/CustomFilterType.js';
24
+ import { NumberDateFilterMode } from '../Types/Filtering/NumberDateFilterMode.js';
25
+ import { StringFilterMode } from '../Types/Filtering/StringFilterMode.js';
26
+ import type { FilterInputProps } from './FilterInputProps.js';
27
+ import { numberDateFilterTypes } from './numberDateFilterTypes.js';
28
+ import { stringFilterTypes } from './stringFilterTypes.js';
29
+
30
+ const tableContext = getContext<TableContext<T, never>>(TableContext.identifier);
31
+
32
+ const { def, customFilters }: FilterInputProps<T, FilterValueType, FilterOptionsType> = $props();
33
+
34
+ const customFilter = $derived(customFilters?.get(def.id));
35
+
36
+ const isRemoteSelect = $derived(customFilter?.type === CustomFilterType.Select);
37
+
38
+ let value: /*SelectOption<unknown> |*/ unknown /*| null | undefined*/ = $state(null);
39
+ let valueMin: number | null = $state(null);
40
+ let valueMax: number | null = $state(null);
41
+
42
+ let selectedOptions: Array<SelectOption<unknown>> = $state([]);
43
+ let lastQueryOptions: Array<SelectOption<unknown>> = $state([]);
44
+
45
+ const isNumeric = $derived(
46
+ def instanceof NumberInputDef || customFilter?.type === CustomFilterType.Number
47
+ );
48
+
49
+ const hasFilterFn = $derived(!!def.filterFn && typeof def.filterFn === 'function');
50
+
51
+ // const hasAcceptableColumnType = $derived(
52
+ // (def instanceof AccessorDef && searchableTypes.has(def.accessorType)) ||
53
+ // (def instanceof AbstractDisplayDef && searchableTypes.has(def.displayType))
54
+ // );
55
+
56
+ const isSearchable = $derived(
57
+ def.allowFiltering && (hasFilterFn || customFilters?.has(def.id) == true)
58
+ // ((hasFilterFn && hasAcceptableColumnType) || customFilters?.has(def.id) == true) // removed hasAcceptableColumnType due to it being redundant
59
+ );
60
+
61
+ const isSelect = $derived(
62
+ def instanceof SelectDef || customFilter?.type === CustomFilterType.Select
63
+ );
64
+
65
+ const options = $derived(isSelect ? (def.getOptions?.() ?? customFilter?.options ?? []) : []);
66
+
67
+ let optionsPromise: Array<SelectOption<unknown>> | Promise<Array<SelectOption<unknown>>> = $state(
68
+ [] as Array<SelectOption<unknown>> | Promise<Array<SelectOption<unknown>>>
69
+ );
70
+
71
+ onMount(() => {
72
+ retryOptionsPromise();
73
+ });
74
+
75
+ function retryOptionsPromise() {
76
+ optionsPromise = def instanceof SelectDef ? def.getOptions() : [];
77
+ }
78
+
79
+ // function isNumber(def: ColumnDef<T>): def is INumberInputColumnAccessorDef<T> {
80
+ // return 'type' in def && def.type === 'Number';
81
+ // }
82
+
83
+ const filteringStateValue = $derived(
84
+ tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value
85
+ );
86
+
87
+ $effect(() => {
88
+ if (isRemoteSelect && !tableContext.paginationRepo?.filtering) {
89
+ return;
90
+ }
91
+ void filteringStateValue;
92
+ if (def.debug) {
93
+ console.debug(
94
+ `${def.id}: `,
95
+ 'FILTER INPUT: filtering state value:',
96
+ $state.snapshot(filteringStateValue)
97
+ );
98
+ }
99
+
100
+ if (hasQuery(customFilter)) {
101
+ if (def.debug) {
102
+ console.debug(`${def.id}: `, 'has query');
103
+ }
104
+
105
+ untrack(async () => {
106
+ try {
107
+ // as unknown as string | number | undefined;
108
+
109
+ if (filteringStateValue != undefined) {
110
+ if (def.debug) {
111
+ console.debug(`${def.id}: query values:`, tableContext.dataRepo.__queryValues);
112
+ }
113
+ const x = await tableContext.dataRepo.__queryValues.get(customFilter.id);
114
+
115
+ if (def.debug) {
116
+ console.debug(`${def.id}: value:`, $state.snapshot(x));
117
+ }
118
+ value = (x ?? null) as SelectOption<unknown> | null;
119
+ } else if (def.debug) {
120
+ console.debug(
121
+ `${def.id}: filtering state value was undefined`,
122
+ $state.snapshot(tableContext.paginationRepo?.filtering)
123
+ );
124
+ }
125
+ } catch (e) {
126
+ console.warn(e);
127
+ value = null;
128
+ }
129
+ }).catch(devCatch);
130
+ } else if (isSelect) {
131
+ if (def.debug) {
132
+ console.debug(`${def.id}: is select (not query)`);
133
+ }
134
+ untrack(async () => {
135
+ try {
136
+ const opts = await options;
137
+ if (def.debug) {
138
+ console.debug(`${def.id}: possible options:`, $state.snapshot(options));
139
+ }
140
+ const x = opts.filter((it) => {
141
+ if (it.value != null && (typeof it.value == 'string' || typeof it.value == 'number')) {
142
+ return (filteringStateValue as Array<unknown> | undefined)?.includes(it.value);
143
+ }
144
+ return false;
145
+ });
146
+ if (def.debug) {
147
+ console.debug(`${def.id}: selected options:`, x);
148
+ }
149
+ selectedOptions = x;
150
+ } catch (e) {
151
+ if (dev || def.debug) {
152
+ console.debug(`${def.id}: `, e);
153
+ }
154
+ }
155
+ }).catch(devCatch);
156
+ } else if (isNumeric) {
157
+ if (
158
+ selectedNumberFilterType?.value === NumberDateFilterMode.Between &&
159
+ tableContext.paginationRepo?.filtering
160
+ ) {
161
+ if (filteringStateValue != undefined) {
162
+ const [min, max] = filteringStateValue as [number, number | undefined];
163
+ value = min;
164
+ valueMin = min;
165
+ valueMax = max ?? null;
166
+ } else {
167
+ value = valueMin = valueMax = null;
168
+ }
169
+ } else if (tableContext.paginationRepo?.filtering) {
170
+ if (filteringStateValue != undefined) {
171
+ const [min] = filteringStateValue as [number, number | undefined];
172
+ value = min;
173
+ valueMin = min;
174
+ valueMax = min;
175
+ } else {
176
+ value = valueMin = valueMax = null;
177
+ }
178
+ }
179
+ } else if (tableContext.paginationRepo?.filtering) {
180
+ if (
181
+ filteringStateValue == null ||
182
+ typeof filteringStateValue == 'string' ||
183
+ typeof filteringStateValue == 'number'
184
+ ) {
185
+ value = filteringStateValue;
186
+ } else if (typeof filteringStateValue !== 'boolean') {
187
+ console.warn('unknown value type:', $state.snapshot(filteringStateValue));
188
+ value = filteringStateValue as string; // HACK attempt to keep production running
189
+ }
190
+ }
191
+ });
192
+
193
+ onMount(() => {
194
+ const filterOption =
195
+ tableContext.paginationRepo?.filtering &&
196
+ tableContext.paginationRepo.filtering.find((it) => it.id === def.id);
197
+
198
+ if (filterOption) {
199
+ if (isNumericArray(filterOption.value)) {
200
+ [valueMin, valueMax] = filterOption.value;
201
+ if (valueMax == undefined) {
202
+ valueMax = valueMin;
203
+ } else if (valueMin == undefined) {
204
+ valueMin = valueMax;
205
+ }
206
+ } else if (!hasQuery(customFilter)) {
207
+ const gotValue = filterOption.value;
208
+ if (gotValue == null || typeof gotValue == 'string' || typeof gotValue == 'number') {
209
+ value = gotValue;
210
+ } else if (typeof filteringStateValue !== 'boolean') {
211
+ console.warn('unknown value type:', $state.snapshot(gotValue));
212
+ value = gotValue as string; // HACK attempt to keep production running
213
+ }
214
+ }
215
+ }
216
+
217
+ // selectedNumberFilterType =
218
+ // (filterMode as NumberDateFilterMode | undefined) ?? NumberDateFilterMode.In;
219
+ // selectedStringFilterType =
220
+ // (filterMode as StringFilterMode | undefined) ?? StringFilterMode.Contains;
221
+ });
222
+
223
+ const filterMode = $derived(tableContext.paginationRepo?.filterModes.get(def.id));
224
+ const selectedNumberFilterType: SelectOption<NumberDateFilterMode> | null = $derived.by(() => {
225
+ const value = (filterMode as NumberDateFilterMode | undefined) ?? NumberDateFilterMode.In;
226
+ return numberDateFilterTypes.find((it) => it.value === value) ?? null;
227
+ });
228
+ const selectedStringFilterType: SelectOption<StringFilterMode> | null = $derived.by(() => {
229
+ const value = (filterMode as StringFilterMode | undefined) ?? StringFilterMode.Contains;
230
+ return stringFilterTypes.find((it) => it.value === value) ?? null;
231
+ });
232
+
233
+ // let selectedNumberFilterType: NumberDateFilterMode = $state(NumberDateFilterMode.In);
234
+ // let selectedStringFilterType: StringFilterMode = $state(StringFilterMode.Contains);
235
+
236
+ const inputId: string = $derived(`${tableContext.tableName}-${def.id}-filter-type`);
237
+
238
+ let showFromDateModal = $state(false);
239
+ let showToDateModal = $state(false);
240
+
241
+ function generateOnChangeObject(label: string) {
242
+ return (newValue: SelectOption<unknown> | null) => {
243
+ tableContext.paginationRepo?.filterBy(def.id, newValue?.value ?? null);
244
+ tableContext.dataRepo.__queryValues.set(label, newValue ?? null);
245
+ value = newValue ?? null;
246
+ };
247
+ }
248
+
249
+ function generateOnChangePrimitive(label: string) {
250
+ return (newValue: SelectOption<unknown> | null) => {
251
+ tableContext.paginationRepo?.filterBy(def.id, newValue?.value ?? null);
252
+ tableContext.dataRepo.__queryValues.set(label, newValue?.value ?? null);
253
+ value = newValue?.value ?? null;
254
+ };
255
+ }
256
+ </script>
257
+
258
+ {#if isSearchable}
259
+ <!-- TODO remove this check once datePicker is supported -->
260
+ {#if def instanceof DateInputDef || def instanceof CheckboxDef}
261
+ <label for={inputId}>{def.header}</label>
262
+ {/if}
263
+ <div class="row">
264
+ {#if def instanceof DateInputDef}
265
+ {@const dateValues =
266
+ tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value ?? []}
267
+ {#if isDateTuple(dateValues)}
268
+ {@const [fromDate, toDate] = dateValues}
269
+ <div class="spaced-around">
270
+ <Button
271
+ onclick={() => {
272
+ showFromDateModal = true;
273
+ }}>From {fromDate?.toLocaleDateString() ?? 'Any Date'}</Button
274
+ >
275
+
276
+ <Button
277
+ onclick={() => {
278
+ showToDateModal = true;
279
+ }}>To {toDate?.toLocaleDateString() ?? 'Any Date'}</Button
280
+ >
281
+ </div>
282
+
283
+ {#if showFromDateModal}
284
+ <DateModal
285
+ header="Edit Date From"
286
+ value={fromDate}
287
+ cancelEditing={() => {
288
+ showFromDateModal = false;
289
+ }}
290
+ confirm={(e) => {
291
+ tableContext.paginationRepo?.filterBy(def.id, [e, toDate]);
292
+ showFromDateModal = false;
293
+ }}
294
+ />
295
+ {/if}
296
+
297
+ {#if showToDateModal}
298
+ <DateModal
299
+ inclusive
300
+ header="Edit Date To"
301
+ value={toDate}
302
+ cancelEditing={() => {
303
+ showToDateModal = false;
304
+ }}
305
+ confirm={(e) => {
306
+ tableContext.paginationRepo?.filterBy(def.id, [fromDate, e]);
307
+ showToDateModal = false;
308
+ }}
309
+ />
310
+ {/if}
311
+ {/if}
312
+ {:else if def instanceof SelectDef && !isRemoteSelect}
313
+ <div class="maxwidth">
314
+ {#await optionsPromise then options}
315
+ <MultiSelect
316
+ label={customFilter?.id ??
317
+ customFilter?.label ??
318
+ customFilter?.objectName ??
319
+ customFilter?.queryKey ??
320
+ def.id}
321
+ labelProp="label"
322
+ valueProp="value"
323
+ name={customFilter?.objectName ?? 'Item'}
324
+ {options}
325
+ valueAsObject={false}
326
+ onchange={(e: Array<SelectOption<unknown>>) => {
327
+ tableContext.paginationRepo?.filterBy(
328
+ def.id,
329
+ e.map((it) => it.value)
330
+ );
331
+ }}
332
+ selectedValues={selectedOptions}
333
+ collapseSelection
334
+ />
335
+ {:catch e}
336
+ <span>Error retrieving options: {e.message}</span>
337
+ <Button
338
+ onclick={() => {
339
+ retryOptionsPromise();
340
+ }}>Retry</Button
341
+ >
342
+ {/await}
343
+ </div>
344
+ {:else if customFilter && isRemoteSelect}
345
+ <div class="contents">
346
+ {#await options then options}
347
+ {#if hasQuery(customFilter)}
348
+ {#if customFilter.valueAsObject !== true}
349
+ {@const label = customFilter.id}
350
+
351
+ <!-- TODO: multiselect -->
352
+
353
+ <QuerySelect
354
+ valueAsObject={false}
355
+ {label}
356
+ clearable
357
+ name={customFilter.objectName ?? 'Item'}
358
+ valueProp="value"
359
+ labelProp="label"
360
+ value={value as string | number | null}
361
+ query={customFilter.query}
362
+ bind:options={lastQueryOptions}
363
+ onchange={generateOnChangePrimitive(label)}
364
+ />
365
+ {:else}
366
+ {@const label = customFilter.id}
367
+
368
+ <!-- TODO: multiselect -->
369
+
370
+ <QuerySelect
371
+ valueAsObject={true}
372
+ {label}
373
+ clearable
374
+ name={customFilter.objectName ?? 'Item'}
375
+ valueProp="value"
376
+ labelProp="label"
377
+ value={value as null}
378
+ query={customFilter.query}
379
+ bind:options={lastQueryOptions}
380
+ onchange={generateOnChangeObject(label)}
381
+ />
382
+ {/if}
383
+ {:else}
384
+ <MultiSelect
385
+ label={customFilter.id}
386
+ labelProp="label"
387
+ valueProp="value"
388
+ name={customFilter.objectName ?? 'Item'}
389
+ {options}
390
+ valueAsObject={false}
391
+ onchange={(e: Array<SelectOption<unknown>>) => {
392
+ tableContext.paginationRepo?.filterBy(
393
+ def.id,
394
+ e.map((it) => it.value)
395
+ );
396
+ }}
397
+ selectedValues={selectedOptions}
398
+ collapseSelection
399
+ />
400
+ {/if}
401
+ {/await}
402
+ </div>
403
+ {:else if def instanceof CheckboxDef}
404
+ <div class="inner-row">
405
+ <label>
406
+ <input
407
+ value={false}
408
+ type="radio"
409
+ bind:group={value}
410
+ onchange={() => {
411
+ tableContext.paginationRepo?.filterBy(def.id, value);
412
+ }}
413
+ />
414
+ No
415
+ </label>
416
+ <label>
417
+ <input
418
+ value={undefined}
419
+ type="radio"
420
+ bind:group={value}
421
+ onchange={() => {
422
+ tableContext.paginationRepo?.filterBy(def.id, value);
423
+ }}
424
+ />
425
+ Any
426
+ </label>
427
+ <label>
428
+ <input
429
+ value={true}
430
+ type="radio"
431
+ bind:group={value}
432
+ onchange={() => {
433
+ tableContext.paginationRepo?.filterBy(def.id, value);
434
+ }}
435
+ />
436
+ Yes
437
+ </label>
438
+ </div>
439
+ {:else if isNumeric}
440
+ {@const label = typeof def.header === 'string' ? def.header : undefined}
441
+ <div class="left">
442
+ <SingleSelect
443
+ {label}
444
+ name="Filter Type"
445
+ options={numberDateFilterTypes}
446
+ labelProp="label"
447
+ valueProp="value"
448
+ valueAsObject
449
+ value={selectedNumberFilterType}
450
+ onchange={(newValue: SelectOption<NumberDateFilterMode> | null) => {
451
+ const oldFilterType = selectedNumberFilterType;
452
+
453
+ const newFilterType = newValue?.value ?? NumberDateFilterMode.In;
454
+ tableContext.paginationRepo?.setFilterMode(def.id, newFilterType);
455
+
456
+ if (selectedNumberFilterType?.value !== oldFilterType?.value) {
457
+ if (oldFilterType?.value === NumberDateFilterMode.Between) {
458
+ valueMax = null;
459
+ tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMin]);
460
+ }
461
+
462
+ if (newFilterType === NumberDateFilterMode.In) {
463
+ tableContext.paginationRepo?.filterBy(def.id, [value]);
464
+ }
465
+
466
+ // TODO what about the other modes?
467
+ }
468
+ }}
469
+ onkeydown={(e: KeyboardEvent) => {
470
+ e.stopPropagation();
471
+ }}
472
+ />
473
+ </div>
474
+ <div class="right">
475
+ {#if selectedNumberFilterType?.value === NumberDateFilterMode.Between}
476
+ <NumberInput
477
+ id={inputId}
478
+ bind:value={valueMin}
479
+ oninput={() => {
480
+ tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMax]);
481
+ }}
482
+ onkeydown={(e: KeyboardEvent) => {
483
+ e.stopPropagation();
484
+ }}
485
+ />
486
+ <span>and</span>
487
+ <NumberInput
488
+ id={inputId}
489
+ bind:value={valueMax}
490
+ oninput={() => {
491
+ tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMax]);
492
+ }}
493
+ onkeydown={(e: KeyboardEvent) => {
494
+ e.stopPropagation();
495
+ }}
496
+ />
497
+ {:else if value == null || typeof value == 'string'}
498
+ <TextInput
499
+ id={inputId}
500
+ bind:value
501
+ oninput={() => {
502
+ tableContext.paginationRepo?.filterBy(def.id, [value]);
503
+ }}
504
+ onkeydown={(e: KeyboardEvent) => {
505
+ e.stopPropagation();
506
+ }}
507
+ />
508
+ {:else if dev}
509
+ error: {value?.toString()}
510
+ <!-- eslint-disable-next-line svelte/no-at-debug-tags -->
511
+ {@debug value}
512
+ {/if}
513
+ </div>
514
+ {:else}
515
+ {@const label = typeof def.header === 'string' ? def.header : undefined}
516
+ <div class="left">
517
+ <SingleSelect
518
+ name="Filter Type"
519
+ valueAsObject
520
+ {label}
521
+ options={stringFilterTypes}
522
+ labelProp="label"
523
+ valueProp="value"
524
+ value={selectedStringFilterType}
525
+ onchange={(newValue: SelectOption<StringFilterMode> | null) => {
526
+ const filterType = newValue?.value ?? StringFilterMode.Contains;
527
+ tableContext.paginationRepo?.setFilterMode(def.id, filterType);
528
+ }}
529
+ onkeydown={(e: KeyboardEvent) => {
530
+ e.stopPropagation();
531
+ }}
532
+ />
533
+ </div>
534
+ <div class="right">
535
+ {#if value == null || typeof value == 'string'}
536
+ <TextInput
537
+ id={inputId}
538
+ bind:value
539
+ oninput={() => {
540
+ tableContext.paginationRepo?.filterBy(def.id, value);
541
+ }}
542
+ onkeydown={(e: KeyboardEvent) => {
543
+ e.stopPropagation();
544
+ }}
545
+ />
546
+ {:else if dev}
547
+ error: {value?.toString()}
548
+ <!-- eslint-disable-next-line svelte/no-at-debug-tags -->
549
+ {@debug value}
550
+ {/if}
551
+ </div>
552
+ {/if}
553
+ </div>
554
+ {/if}
555
+
556
556
  <style>.contents {
557
557
  display: contents;
558
558
  --input-width: 100%;
@@ -623,4 +623,4 @@
623
623
  .inner-row label input {
624
624
  cursor: pointer;
625
625
  width: 2rem;
626
- }</style>
626
+ }</style>