@lavalogic/scoria 0.34.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 (125) 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.js +6 -4
  119. package/dist/Types/Examples/ExampleModal.svelte +34 -34
  120. package/dist/scss/_basics.scss +12 -12
  121. package/dist/scss/_colours.scss +134 -134
  122. package/dist/scss/_mixins.scss +3016 -3016
  123. package/dist/scss/_sizing.scss +69 -69
  124. package/dist/scss/_transitions.scss +8 -8
  125. 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>