@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.
- package/README.md +58 -58
- package/dist/Components/AccordionGroup.svelte +42 -42
- package/dist/Components/AccordionGroupButton.svelte +99 -99
- package/dist/Components/Action.svelte +138 -138
- package/dist/Components/AlertModal.svelte +125 -125
- package/dist/Components/Base/ContextWrapper.svelte +50 -50
- package/dist/Components/Base/Pagination.svelte +190 -190
- package/dist/Components/Base/Snippets.svelte +29 -29
- package/dist/Components/BigRadioSet.svelte +29 -29
- package/dist/Components/Bubble.svelte +77 -77
- package/dist/Components/Button.svelte +197 -197
- package/dist/Components/Checkbox.svelte +68 -68
- package/dist/Components/CollapsibleCard.svelte +70 -70
- package/dist/Components/Contexts/ContextMenu.svelte +157 -157
- package/dist/Components/Contexts/ContextMenuDivider.svelte +11 -11
- package/dist/Components/Contexts/ContextMenuOption.svelte +52 -52
- package/dist/Components/Contexts/Toast.svelte +61 -61
- package/dist/Components/Contexts/ToastContainer.svelte +46 -46
- package/dist/Components/Contexts/Tooltip.svelte +26 -26
- package/dist/Components/DataMatrixIcon.svelte +41 -41
- package/dist/Components/DateInput.svelte +109 -109
- package/dist/Components/DatePicker.svelte +312 -312
- package/dist/Components/DesktopModal.svelte +106 -106
- package/dist/Components/Dial.svelte +151 -151
- package/dist/Components/DimensionInput.svelte +184 -184
- package/dist/Components/DragMenu/DragMenuHolder.svelte +106 -106
- package/dist/Components/DragMenu/DraggableCard.svelte +41 -41
- package/dist/Components/FileCard.svelte +20 -20
- package/dist/Components/FileUpload.svelte +196 -196
- package/dist/Components/GridInput.svelte +190 -190
- package/dist/Components/GridInputSet.svelte +22 -22
- package/dist/Components/HorizontalTabGroup.svelte +78 -78
- package/dist/Components/HorizontalTabGroupButton.svelte +96 -96
- package/dist/Components/Icon.svelte +97 -97
- package/dist/Components/Icons.js +357 -357
- package/dist/Components/LoadingAnimation.svelte +32 -32
- package/dist/Components/LoadingModal.svelte +47 -47
- package/dist/Components/LoadingOverlay.svelte +27 -27
- package/dist/Components/Modal.svelte +251 -251
- package/dist/Components/MultiSelect.svelte +197 -197
- package/dist/Components/Nav/Nav.svelte +97 -97
- package/dist/Components/Nav/NavServiceMenuList.svelte +34 -34
- package/dist/Components/Nav/NavTab.svelte +91 -91
- package/dist/Components/Nav/ServiceMenuItem.svelte +91 -91
- package/dist/Components/Nav/ServicesNav.svelte +53 -53
- package/dist/Components/NumberInput.svelte +144 -144
- package/dist/Components/PageHeading.svelte +36 -36
- package/dist/Components/PasswordInput.svelte +73 -73
- package/dist/Components/Portal.svelte +25 -25
- package/dist/Components/ProgressBubble.svelte +80 -80
- package/dist/Components/QuerySelect.svelte +411 -411
- package/dist/Components/SingleSelect.svelte +261 -261
- package/dist/Components/StepButton.svelte +120 -120
- package/dist/Components/StepForm.svelte +108 -108
- package/dist/Components/Switch.svelte +36 -36
- package/dist/Components/Table/Body/QuickSearchCell.svelte +632 -632
- package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +30 -30
- package/dist/Components/Table/Body/Rows/ColumnList.svelte +56 -56
- package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +179 -179
- package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +49 -49
- package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +180 -180
- package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +102 -102
- package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +119 -119
- package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +114 -114
- package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +96 -96
- package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +72 -72
- package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +131 -131
- package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +144 -144
- package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +238 -238
- package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +358 -358
- package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +338 -338
- package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +183 -183
- package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +309 -309
- package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +369 -369
- package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +170 -170
- package/dist/Components/Table/Body/Rows/TableRow.svelte +259 -259
- package/dist/Components/Table/Body/TableBody.svelte +67 -67
- package/dist/Components/Table/Headers/HighlightAllHeader.svelte +39 -39
- package/dist/Components/Table/Headers/SelectAllHeader.svelte +28 -28
- package/dist/Components/Table/Headers/TableHead.svelte +260 -260
- package/dist/Components/Table/Misc/ColumnPanel.svelte +78 -78
- package/dist/Components/Table/Misc/ColumnPanelModal.svelte +99 -99
- package/dist/Components/Table/Misc/FilterInput.svelte +556 -556
- package/dist/Components/Table/Misc/FilterPanel.svelte +46 -46
- package/dist/Components/Table/Misc/FilterPanelModal.svelte +67 -67
- package/dist/Components/Table/Misc/SidePanel.svelte +32 -32
- package/dist/Components/Table/Misc/TableFooter.svelte +304 -304
- package/dist/Components/Table/Misc/TableHorizontalBar.svelte +156 -156
- package/dist/Components/Table/Misc/TableSidebar.svelte +176 -176
- package/dist/Components/Table/Misc/ToolboxPanel.svelte +77 -77
- package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +91 -91
- package/dist/Components/Table/NestedTable.svelte +84 -84
- package/dist/Components/Table/Table.svelte +492 -492
- package/dist/Components/Test/TestContextConsumer.svelte +13 -13
- package/dist/Components/TextArea.svelte +108 -108
- package/dist/Components/TextInput.svelte +147 -147
- package/dist/Components/ThreeStateRadio.svelte +124 -124
- package/dist/Components/Touch/BoolCard.svelte +52 -52
- package/dist/Components/Touch/DateModal.svelte +217 -217
- package/dist/Components/Touch/EditCard.svelte +52 -52
- package/dist/Components/Touch/InfoCard.svelte +24 -24
- package/dist/Components/Touch/InfoTextCard.svelte +21 -21
- package/dist/Components/Touch/ModalExplanation.svelte +20 -20
- package/dist/Components/Touch/NumberKeyboard.svelte +268 -268
- package/dist/Components/Touch/NumberModal.svelte +69 -69
- package/dist/Components/Touch/PlusMinusCard.svelte +50 -50
- package/dist/Components/Touch/SelectModal.svelte +105 -105
- package/dist/Components/Touch/SummaryCard.svelte +65 -65
- package/dist/Components/Touch/TextAreaModal.svelte +86 -86
- package/dist/Components/Touch/TextModal.svelte +123 -123
- package/dist/Components/Touch/TouchCard.svelte +28 -28
- package/dist/Components/Touch/TouchModal.svelte +259 -259
- package/dist/Components/Touch/UtilityButton.svelte +91 -91
- package/dist/Components/VerticalTabGroup.svelte +66 -66
- package/dist/Components/VerticalTabGroupButton.svelte +97 -97
- package/dist/Helpers/Datamatrix.d.ts +10 -10
- package/dist/Helpers/Datamatrix.js +1 -1
- package/dist/Helpers/Helpers.svelte.js +6 -4
- package/dist/Types/Examples/ExampleModal.svelte +34 -34
- package/dist/scss/_basics.scss +12 -12
- package/dist/scss/_colours.scss +134 -134
- package/dist/scss/_mixins.scss +3016 -3016
- package/dist/scss/_sizing.scss +69 -69
- package/dist/scss/_transitions.scss +8 -8
- 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>
|