@lavalogic/scoria 0.40.13 → 0.40.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -58
- package/dist/Components/AccordionGroup.svelte +35 -46
- package/dist/Components/AccordionGroupButton.svelte +68 -76
- package/dist/Components/Action.svelte +103 -135
- package/dist/Components/AlertModal.svelte +171 -190
- package/dist/Components/AppointmentCalendar/AppointmentCalendar.svelte +614 -697
- package/dist/Components/Base/BaseModal.svelte +90 -116
- package/dist/Components/Base/ContextWrapper.svelte +67 -72
- package/dist/Components/Base/Pagination.svelte +173 -196
- package/dist/Components/Base/Snippets.svelte +34 -36
- package/dist/Components/BigRadioSet.svelte +36 -38
- package/dist/Components/Bubble.svelte +78 -101
- package/dist/Components/Button.svelte +175 -208
- package/dist/Components/Checkbox.svelte +75 -86
- package/dist/Components/CollapsibleCard.svelte +72 -87
- package/dist/Components/CollapsibleMenuGroup.svelte +53 -57
- package/dist/Components/ConditionBuilder/ConditionBuilder.svelte +39 -64
- package/dist/Components/Contexts/ContextMenu.svelte +141 -156
- package/dist/Components/Contexts/ContextMenuDivider.svelte +15 -15
- package/dist/Components/Contexts/ContextMenuOption.svelte +53 -60
- package/dist/Components/Contexts/Toast.svelte +81 -86
- package/dist/Components/Contexts/ToastContainer.svelte +58 -62
- package/dist/Components/Contexts/Tooltip.svelte +36 -37
- package/dist/Components/DataMatrixIcon.svelte +95 -108
- package/dist/Components/DateInput.svelte +118 -146
- package/dist/Components/DatePicker.svelte +255 -294
- package/dist/Components/DesktopModal.svelte +70 -90
- package/dist/Components/Dial.svelte +167 -197
- package/dist/Components/DimensionInput.svelte +124 -169
- package/dist/Components/DragMenu/DragMenuHolder.svelte +98 -114
- package/dist/Components/DragMenu/DraggableCard.svelte +64 -76
- package/dist/Components/FileCard.svelte +21 -24
- package/dist/Components/FileUpload.svelte +190 -225
- package/dist/Components/GridInput.svelte +159 -196
- package/dist/Components/GridInputSet.svelte +24 -29
- package/dist/Components/HorizontalTabGroup.svelte +73 -89
- package/dist/Components/HorizontalTabGroupButton.svelte +40 -44
- package/dist/Components/Icon.svelte +99 -128
- package/dist/Components/Icons.js +357 -357
- package/dist/Components/InfoAlert.svelte +35 -38
- package/dist/Components/LoadingAnimation.svelte +691 -755
- package/dist/Components/LoadingModal.svelte +36 -44
- package/dist/Components/LoadingOverlay.svelte +22 -26
- package/dist/Components/Modal.svelte +204 -254
- package/dist/Components/MultiSelect.svelte +186 -216
- package/dist/Components/Nav/Nav.svelte +94 -110
- package/dist/Components/Nav/NavServiceMenuList.svelte +57 -61
- package/dist/Components/Nav/NavTab.svelte +58 -62
- package/dist/Components/Nav/ServiceMenuItem.svelte +103 -111
- package/dist/Components/Nav/ServicesNav.svelte +72 -79
- package/dist/Components/NumberInput.svelte +121 -155
- package/dist/Components/OptionCards.svelte +62 -73
- package/dist/Components/PageHeading.svelte +23 -25
- package/dist/Components/PasswordInput.svelte +69 -88
- package/dist/Components/PopoverButton.svelte +151 -0
- package/dist/Components/PopoverButton.svelte.d.ts +9 -0
- package/dist/Components/PopoverButtonProps.d.ts +21 -0
- package/dist/Components/PopoverButtonProps.js +1 -0
- package/dist/Components/Portal.svelte +29 -35
- package/dist/Components/ProgressBubble.svelte +91 -106
- package/dist/Components/QuerySelect.svelte +260 -346
- package/dist/Components/SidebarPanel.svelte +34 -36
- package/dist/Components/SingleSelect.svelte +196 -244
- package/dist/Components/StepButton.svelte +65 -70
- package/dist/Components/StepForm.svelte +115 -135
- package/dist/Components/Switch.svelte +57 -69
- package/dist/Components/Table/ARCHITECTURE.md +298 -298
- package/dist/Components/Table/Body/QuickSearchCell.svelte +578 -655
- package/dist/Components/Table/Body/Rows/ColumnHighlight.svelte +42 -51
- package/dist/Components/Table/Body/Rows/ColumnList.svelte +42 -58
- package/dist/Components/Table/Body/Rows/ColumnListRow.svelte +403 -442
- package/dist/Components/Table/Body/Rows/Columns/AccessorComponent.svelte +51 -78
- package/dist/Components/Table/Body/Rows/Columns/ActionsCell.svelte +165 -184
- package/dist/Components/Table/Body/Rows/Columns/BubbleCell.svelte +110 -123
- package/dist/Components/Table/Body/Rows/Columns/ExpandCell.svelte +134 -160
- package/dist/Components/Table/Body/Rows/Columns/FocusableImmutableCell.svelte +190 -231
- package/dist/Components/Table/Body/Rows/Columns/ProgressCell.svelte +82 -104
- package/dist/Components/Table/Body/Rows/Columns/TableAction.svelte +52 -87
- package/dist/Components/Table/Body/Rows/Columns/TableCheckbox.svelte +122 -150
- package/dist/Components/Table/Body/Rows/Columns/TableColumn.svelte +132 -159
- package/dist/Components/Table/Body/Rows/Columns/TableDatePicker.svelte +190 -236
- package/dist/Components/Table/Body/Rows/Columns/TableNumberInput.svelte +173 -211
- package/dist/Components/Table/Body/Rows/Columns/TableQuerySelect.svelte +204 -244
- package/dist/Components/Table/Body/Rows/Columns/TableRowSelectionCheckbox.svelte +140 -177
- package/dist/Components/Table/Body/Rows/Columns/TableSelect.svelte +188 -217
- package/dist/Components/Table/Body/Rows/Columns/TableTextInput.svelte +173 -193
- package/dist/Components/Table/Body/Rows/Columns/ValidityCell.svelte +166 -187
- package/dist/Components/Table/Body/Rows/TableRow.svelte +245 -288
- package/dist/Components/Table/Body/TableBody.svelte +141 -154
- package/dist/Components/Table/Headers/HighlightAllHeader.svelte +35 -41
- package/dist/Components/Table/Headers/SelectAllHeader.svelte +24 -30
- package/dist/Components/Table/Headers/TableHead.svelte +348 -402
- package/dist/Components/Table/Misc/ColumnPanel.svelte +37 -39
- package/dist/Components/Table/Misc/ColumnPanelModal.svelte +35 -39
- package/dist/Components/Table/Misc/FilterInput.svelte +556 -619
- package/dist/Components/Table/Misc/FilterPanel.svelte +170 -183
- package/dist/Components/Table/Misc/FilterPanelModal.svelte +25 -31
- package/dist/Components/Table/Misc/ShareViewModal.svelte +252 -281
- package/dist/Components/Table/Misc/SidePanel.svelte +32 -35
- package/dist/Components/Table/Misc/TableConfigurationModal.svelte +574 -629
- package/dist/Components/Table/Misc/TableFooter.svelte +326 -332
- package/dist/Components/Table/Misc/TableHorizontalBar.svelte +134 -140
- package/dist/Components/Table/Misc/TableSidebar.svelte +183 -198
- package/dist/Components/Table/Misc/TableViewDropdown.svelte +83 -103
- package/dist/Components/Table/Misc/ToolboxPanel.svelte +83 -100
- package/dist/Components/Table/Misc/ToolboxPanelModal.svelte +88 -107
- package/dist/Components/Table/Misc/UpdateViewOptionsModal.svelte +163 -190
- package/dist/Components/Table/NestedTable.svelte +102 -128
- package/dist/Components/Table/Table.svelte +531 -663
- package/dist/Components/Test/TestContextConsumer.svelte +11 -13
- package/dist/Components/TextArea.svelte +95 -130
- package/dist/Components/TextInput.svelte +125 -176
- package/dist/Components/ThreeStateRadio.svelte +111 -130
- package/dist/Components/Touch/BoolCard.svelte +33 -45
- package/dist/Components/Touch/DateModal.svelte +164 -204
- package/dist/Components/Touch/EditCard.svelte +48 -59
- package/dist/Components/Touch/InfoCard.svelte +28 -31
- package/dist/Components/Touch/InfoTextCard.svelte +26 -28
- package/dist/Components/Touch/ModalExplanation.svelte +15 -17
- package/dist/Components/Touch/NumberKeyboard.svelte +181 -218
- package/dist/Components/Touch/NumberModal.svelte +59 -79
- package/dist/Components/Touch/PlusMinusCard.svelte +43 -46
- package/dist/Components/Touch/SelectModal.svelte +111 -126
- package/dist/Components/Touch/SummaryCard.svelte +80 -93
- package/dist/Components/Touch/TextAreaModal.svelte +72 -94
- package/dist/Components/Touch/TextModal.svelte +77 -105
- package/dist/Components/Touch/TouchCard.svelte +31 -33
- package/dist/Components/Touch/TouchModal.svelte +214 -270
- package/dist/Components/Touch/UtilityButton.svelte +88 -109
- package/dist/Components/VerticalTabGroup.svelte +60 -78
- package/dist/Components/VerticalTabGroupButton.svelte +54 -58
- package/dist/Helpers/Datamatrix.d.ts +19 -19
- package/dist/Helpers/Datamatrix.js +291 -291
- package/dist/Types/Examples/ExampleModal.svelte +31 -35
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/scss/_basics.scss +12 -12
- package/dist/scss/_colours.scss +134 -134
- package/dist/scss/_mixins.scss +3392 -3392
- package/dist/scss/_motion.scss +57 -57
- package/dist/scss/_sizing.scss +85 -85
- package/dist/scss/_transitions.scss +8 -8
- package/package.json +1 -1
|
@@ -1,643 +1,580 @@
|
|
|
1
|
-
<svelte:options runes />
|
|
2
|
-
|
|
3
|
-
<script
|
|
4
|
-
lang="ts"
|
|
5
|
-
generics="T extends object, IdType extends Primitive, FilterValueType"
|
|
6
|
-
>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* usual case in flowms migrations).
|
|
42
|
-
* 2. The customFilter's `label` (set on the remoteFilters entry).
|
|
43
|
-
* 3. The raw `customFilter.id` (last-ditch fallback).
|
|
44
|
-
* Avoids surfacing dotted-path / camelCase IDs like
|
|
45
|
-
* `orderLines.allowSplitFulfilment` as the visible label. */
|
|
46
|
-
const displayLabel: string = $derived.by(() => {
|
|
47
|
-
if (typeof def.header === 'string' && def.header.length > 0) {
|
|
48
|
-
return def.header;
|
|
49
|
-
}
|
|
50
|
-
if (customFilter?.label) {
|
|
51
|
-
return customFilter.label;
|
|
52
|
-
}
|
|
53
|
-
return customFilter?.id ?? def.id;
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const isRemoteSelect = $derived(customFilter?.type === CustomFilterType.Select);
|
|
57
|
-
|
|
58
|
-
let value: unknown = $state(null);
|
|
59
|
-
let valueMin: number | null = $state(null);
|
|
60
|
-
let valueMax: number | null = $state(null);
|
|
61
|
-
|
|
62
|
-
let selectedOptions: Array<SelectOption<unknown>> = $state([]);
|
|
63
|
-
let lastQueryOptions: Array<SelectOption<unknown>> = $state([]);
|
|
64
|
-
|
|
65
|
-
const isNumeric = $derived(
|
|
66
|
-
def instanceof NumberInputDef || customFilter?.type === CustomFilterType.Number
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
const hasFilterFn = $derived(!!def.filterFn && typeof def.filterFn === 'function');
|
|
70
|
-
|
|
71
|
-
// A column is filterable when it has any of:
|
|
72
|
-
// - a local `filterFn` (AccessorDef subclasses, or Display/Bubble/
|
|
73
|
-
// Progress columns whose data repo is local),
|
|
74
|
-
// - a declared `filterValueType` (server-side filtering — the UI
|
|
75
|
-
// renders an input matching the declared type),
|
|
76
|
-
// - a `customFilters` entry from `defsWrapper.remoteFilters`.
|
|
77
|
-
// The `filterValueType` case is required for remote-pagination tables:
|
|
78
|
-
// their data repo never assigns `filterFn`, so without it every typed
|
|
79
|
-
// column would silently disappear from the filter UI.
|
|
80
|
-
const hasDeclaredFilterType = $derived(def.filterValueType != null);
|
|
81
|
-
|
|
82
|
-
const isSearchable = $derived(
|
|
83
|
-
def.allowFiltering &&
|
|
84
|
-
(hasFilterFn || hasDeclaredFilterType || customFilters?.has(def.id) == true)
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
const isSelect = $derived(
|
|
88
|
-
def instanceof SelectDef || customFilter?.type === CustomFilterType.Select
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const options = $derived(isSelect ? (def.getOptions?.() ?? customFilter?.options ?? []) : []);
|
|
92
|
-
|
|
93
|
-
let optionsPromise: Array<SelectOption<unknown>> | Promise<Array<SelectOption<unknown>>> = $state(
|
|
94
|
-
[] as Array<SelectOption<unknown>> | Promise<Array<SelectOption<unknown>>>
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
onMount(() => {
|
|
98
|
-
retryOptionsPromise();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
function retryOptionsPromise() {
|
|
102
|
-
optionsPromise = def instanceof SelectDef ? def.getOptions() : [];
|
|
1
|
+
<svelte:options runes />
|
|
2
|
+
|
|
3
|
+
<script
|
|
4
|
+
lang="ts"
|
|
5
|
+
generics="T extends object, IdType extends Primitive, FilterValueType"
|
|
6
|
+
>import { dev } from "$app/environment";
|
|
7
|
+
import Button from "../../Button.svelte";
|
|
8
|
+
import MultiSelect from "../../MultiSelect.svelte";
|
|
9
|
+
import NumberInput from "../../NumberInput.svelte";
|
|
10
|
+
import QuerySelect from "../../QuerySelect.svelte";
|
|
11
|
+
import SingleSelect from "../../SingleSelect.svelte";
|
|
12
|
+
import TextInput from "../../TextInput.svelte";
|
|
13
|
+
import DateModal from "../../Touch/DateModal.svelte";
|
|
14
|
+
import { hasQuery } from "../TableHelpers.svelte.js";
|
|
15
|
+
import { devCatch, isDateTuple, isNumericArray } from "../../../Helpers/Helpers.svelte.js";
|
|
16
|
+
import { getContext, onMount, untrack } from "svelte";
|
|
17
|
+
import { CheckboxDef } from "../Types/Columns/Definitions/Accessors/CheckboxDef.svelte.js";
|
|
18
|
+
import { DateInputDef } from "../Types/Columns/Definitions/Accessors/DateInputDef.svelte.js";
|
|
19
|
+
import { NumberInputDef } from "../Types/Columns/Definitions/Accessors/NumberInputDef.svelte.js";
|
|
20
|
+
import { SelectDef } from "../Types/Columns/Definitions/Accessors/SelectDef.svelte.js";
|
|
21
|
+
import { TableContext } from "../Types/Context/TableContext.svelte.js";
|
|
22
|
+
import { CustomFilterType } from "../Types/Filtering/CustomFilterType.js";
|
|
23
|
+
import { NumberDateFilterMode } from "../Types/Filtering/NumberDateFilterMode.js";
|
|
24
|
+
import { StringFilterMode } from "../Types/Filtering/StringFilterMode.js";
|
|
25
|
+
import { numberDateFilterTypes } from "./numberDateFilterTypes.js";
|
|
26
|
+
import { stringFilterTypes } from "./stringFilterTypes.js";
|
|
27
|
+
const tableContext = getContext(TableContext.identifier);
|
|
28
|
+
const { def, customFilters } = $props();
|
|
29
|
+
const customFilter = $derived(customFilters?.get(def.id));
|
|
30
|
+
/** Human-readable label used by every inner input (QuerySelect /
|
|
31
|
+
* MultiSelect / SingleSelect / etc.). Priority:
|
|
32
|
+
* 1. The column's `header` (when it is a plain string, which is the
|
|
33
|
+
* usual case in flowms migrations).
|
|
34
|
+
* 2. The customFilter's `label` (set on the remoteFilters entry).
|
|
35
|
+
* 3. The raw `customFilter.id` (last-ditch fallback).
|
|
36
|
+
* Avoids surfacing dotted-path / camelCase IDs like
|
|
37
|
+
* `orderLines.allowSplitFulfilment` as the visible label. */
|
|
38
|
+
const displayLabel = $derived.by(() => {
|
|
39
|
+
if (typeof def.header === "string" && def.header.length > 0) {
|
|
40
|
+
return def.header;
|
|
103
41
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
42
|
+
if (customFilter?.label) {
|
|
43
|
+
return customFilter.label;
|
|
44
|
+
}
|
|
45
|
+
return customFilter?.id ?? def.id;
|
|
46
|
+
});
|
|
47
|
+
const isRemoteSelect = $derived(customFilter?.type === CustomFilterType.Select);
|
|
48
|
+
let value = $state(null);
|
|
49
|
+
let valueMin = $state(null);
|
|
50
|
+
let valueMax = $state(null);
|
|
51
|
+
let selectedOptions = $state([]);
|
|
52
|
+
let lastQueryOptions = $state([]);
|
|
53
|
+
const isNumeric = $derived(def instanceof NumberInputDef || customFilter?.type === CustomFilterType.Number);
|
|
54
|
+
const hasFilterFn = $derived(!!def.filterFn && typeof def.filterFn === "function");
|
|
55
|
+
// A column is filterable when it has any of:
|
|
56
|
+
// - a local `filterFn` (AccessorDef subclasses, or Display/Bubble/
|
|
57
|
+
// Progress columns whose data repo is local),
|
|
58
|
+
// - a declared `filterValueType` (server-side filtering — the UI
|
|
59
|
+
// renders an input matching the declared type),
|
|
60
|
+
// - a `customFilters` entry from `defsWrapper.remoteFilters`.
|
|
61
|
+
// The `filterValueType` case is required for remote-pagination tables:
|
|
62
|
+
// their data repo never assigns `filterFn`, so without it every typed
|
|
63
|
+
// column would silently disappear from the filter UI.
|
|
64
|
+
const hasDeclaredFilterType = $derived(def.filterValueType != null);
|
|
65
|
+
const isSearchable = $derived(def.allowFiltering && (hasFilterFn || hasDeclaredFilterType || customFilters?.has(def.id) == true));
|
|
66
|
+
const isSelect = $derived(def instanceof SelectDef || customFilter?.type === CustomFilterType.Select);
|
|
67
|
+
const options = $derived(isSelect ? def.getOptions?.() ?? customFilter?.options ?? [] : []);
|
|
68
|
+
let optionsPromise = $state([]);
|
|
69
|
+
onMount(() => {
|
|
70
|
+
retryOptionsPromise();
|
|
71
|
+
});
|
|
72
|
+
function retryOptionsPromise() {
|
|
73
|
+
optionsPromise = def instanceof SelectDef ? def.getOptions() : [];
|
|
74
|
+
}
|
|
75
|
+
const filteringStateValue = $derived(tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value);
|
|
76
|
+
$effect(() => {
|
|
77
|
+
if (isRemoteSelect && !tableContext.paginationRepo?.filtering) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
void filteringStateValue;
|
|
81
|
+
if (def.debug) {
|
|
82
|
+
console.debug(`${def.id}: `, "FILTER INPUT: filtering state value:", $state.snapshot(filteringStateValue));
|
|
83
|
+
}
|
|
84
|
+
if (hasQuery(customFilter)) {
|
|
114
85
|
if (def.debug) {
|
|
115
|
-
console.debug(
|
|
116
|
-
`${def.id}: `,
|
|
117
|
-
'FILTER INPUT: filtering state value:',
|
|
118
|
-
$state.snapshot(filteringStateValue)
|
|
119
|
-
);
|
|
86
|
+
console.debug(`${def.id}: `, "has query");
|
|
120
87
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
if (filteringStateValue == undefined) {
|
|
130
|
-
// Filter cleared externally (saved view applied with an
|
|
131
|
-
// empty filter, or "Clear All" hit). Drop the typeahead
|
|
132
|
-
// echo so the next selection isn't masked by a stale
|
|
133
|
-
// cache, and clear the local value so the chip clears.
|
|
134
|
-
if (def.debug) {
|
|
135
|
-
console.debug(
|
|
136
|
-
`${def.id}: filtering state value was undefined`,
|
|
137
|
-
$state.snapshot(tableContext.paginationRepo?.filtering)
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
value = null;
|
|
141
|
-
tableContext.queryValues.delete(def);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (def.debug) {
|
|
146
|
-
console.debug(`${def.id}: query values:`, tableContext.queryValues.snapshot());
|
|
147
|
-
}
|
|
148
|
-
// Prefer the typeahead echo — it carries the real label
|
|
149
|
-
// from the option the user clicked. Fall back to the saved
|
|
150
|
-
// primitive (the usual case after applying a saved view)
|
|
151
|
-
// so the chip is at least visible; QuerySelect's own
|
|
152
|
-
// `$effect.pre` adds a placeholder option keyed on the
|
|
153
|
-
// primitive so it renders.
|
|
154
|
-
const cached = tableContext.queryValues.get(def);
|
|
88
|
+
untrack(() => {
|
|
89
|
+
try {
|
|
90
|
+
if (filteringStateValue == undefined) {
|
|
91
|
+
// Filter cleared externally (saved view applied with an
|
|
92
|
+
// empty filter, or "Clear All" hit). Drop the typeahead
|
|
93
|
+
// echo so the next selection isn't masked by a stale
|
|
94
|
+
// cache, and clear the local value so the chip clears.
|
|
155
95
|
if (def.debug) {
|
|
156
|
-
console.debug(`${def.id}:
|
|
96
|
+
console.debug(`${def.id}: filtering state value was undefined`, $state.snapshot(tableContext.paginationRepo?.filtering));
|
|
157
97
|
}
|
|
158
|
-
if (cached != null) {
|
|
159
|
-
value = cached;
|
|
160
|
-
} else if (customFilter.valueAsObject === true) {
|
|
161
|
-
value = {
|
|
162
|
-
value: filteringStateValue,
|
|
163
|
-
label: String(filteringStateValue),
|
|
164
|
-
};
|
|
165
|
-
} else {
|
|
166
|
-
value = filteringStateValue;
|
|
167
|
-
}
|
|
168
|
-
} catch (e) {
|
|
169
|
-
console.warn(e);
|
|
170
98
|
value = null;
|
|
99
|
+
tableContext.queryValues.delete(def);
|
|
100
|
+
return;
|
|
171
101
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (def.debug) {
|
|
175
|
-
console.debug(`${def.id}: is select (not query)`);
|
|
176
|
-
}
|
|
177
|
-
untrack(async () => {
|
|
178
|
-
try {
|
|
179
|
-
const opts = await options;
|
|
180
|
-
if (def.debug) {
|
|
181
|
-
console.debug(`${def.id}: possible options:`, $state.snapshot(options));
|
|
182
|
-
}
|
|
183
|
-
const x = opts.filter((it) => {
|
|
184
|
-
if (it.value != null && (typeof it.value == 'string' || typeof it.value == 'number')) {
|
|
185
|
-
return (filteringStateValue as Array<unknown> | undefined)?.includes(it.value);
|
|
186
|
-
}
|
|
187
|
-
return false;
|
|
188
|
-
});
|
|
189
|
-
if (def.debug) {
|
|
190
|
-
console.debug(`${def.id}: selected options:`, x);
|
|
191
|
-
}
|
|
192
|
-
selectedOptions = x;
|
|
193
|
-
} catch (e) {
|
|
194
|
-
if (dev || def.debug) {
|
|
195
|
-
console.debug(`${def.id}: `, e);
|
|
196
|
-
}
|
|
102
|
+
if (def.debug) {
|
|
103
|
+
console.debug(`${def.id}: query values:`, tableContext.queryValues.snapshot());
|
|
197
104
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
value
|
|
207
|
-
valueMin = min;
|
|
208
|
-
valueMax = max ?? null;
|
|
209
|
-
} else {
|
|
210
|
-
value = valueMin = valueMax = null;
|
|
105
|
+
// Prefer the typeahead echo — it carries the real label
|
|
106
|
+
// from the option the user clicked. Fall back to the saved
|
|
107
|
+
// primitive (the usual case after applying a saved view)
|
|
108
|
+
// so the chip is at least visible; QuerySelect's own
|
|
109
|
+
// `$effect.pre` adds a placeholder option keyed on the
|
|
110
|
+
// primitive so it renders.
|
|
111
|
+
const cached = tableContext.queryValues.get(def);
|
|
112
|
+
if (def.debug) {
|
|
113
|
+
console.debug(`${def.id}: cached value:`, $state.snapshot(cached));
|
|
211
114
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
value =
|
|
216
|
-
|
|
217
|
-
|
|
115
|
+
if (cached != null) {
|
|
116
|
+
value = cached;
|
|
117
|
+
} else if (customFilter.valueAsObject === true) {
|
|
118
|
+
value = {
|
|
119
|
+
value: filteringStateValue,
|
|
120
|
+
label: String(filteringStateValue)
|
|
121
|
+
};
|
|
218
122
|
} else {
|
|
219
|
-
value =
|
|
123
|
+
value = filteringStateValue;
|
|
220
124
|
}
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.warn(e);
|
|
127
|
+
value = null;
|
|
221
128
|
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
typeof filteringStateValue == 'number'
|
|
227
|
-
) {
|
|
228
|
-
value = filteringStateValue;
|
|
229
|
-
} else if (typeof filteringStateValue !== 'boolean') {
|
|
230
|
-
console.warn('unknown value type:', $state.snapshot(filteringStateValue));
|
|
231
|
-
value = filteringStateValue as string; // HACK attempt to keep production running
|
|
232
|
-
}
|
|
129
|
+
});
|
|
130
|
+
} else if (isSelect) {
|
|
131
|
+
if (def.debug) {
|
|
132
|
+
console.debug(`${def.id}: is select (not query)`);
|
|
233
133
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
tableContext.paginationRepo.filtering.find((it) => it.id === def.id);
|
|
240
|
-
|
|
241
|
-
if (filterOption) {
|
|
242
|
-
if (isNumericArray(filterOption.value)) {
|
|
243
|
-
[valueMin, valueMax] = filterOption.value;
|
|
244
|
-
if (valueMax == undefined) {
|
|
245
|
-
valueMax = valueMin;
|
|
246
|
-
} else if (valueMin == undefined) {
|
|
247
|
-
valueMin = valueMax;
|
|
134
|
+
untrack(async () => {
|
|
135
|
+
try {
|
|
136
|
+
const opts = await options;
|
|
137
|
+
if (def.debug) {
|
|
138
|
+
console.debug(`${def.id}: possible options:`, $state.snapshot(options));
|
|
248
139
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
140
|
+
const x = opts.filter((it) => {
|
|
141
|
+
if (it.value != null && (typeof it.value == "string" || typeof it.value == "number")) {
|
|
142
|
+
return filteringStateValue?.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);
|
|
256
153
|
}
|
|
257
154
|
}
|
|
155
|
+
}).catch(devCatch);
|
|
156
|
+
} else if (isNumeric) {
|
|
157
|
+
if (selectedNumberFilterType?.value === NumberDateFilterMode.Between && tableContext.paginationRepo?.filtering) {
|
|
158
|
+
if (filteringStateValue != undefined) {
|
|
159
|
+
const [min, max] = filteringStateValue;
|
|
160
|
+
value = min;
|
|
161
|
+
valueMin = min;
|
|
162
|
+
valueMax = max ?? null;
|
|
163
|
+
} else {
|
|
164
|
+
value = valueMin = valueMax = null;
|
|
165
|
+
}
|
|
166
|
+
} else if (tableContext.paginationRepo?.filtering) {
|
|
167
|
+
if (filteringStateValue != undefined) {
|
|
168
|
+
const [min] = filteringStateValue;
|
|
169
|
+
value = min;
|
|
170
|
+
valueMin = min;
|
|
171
|
+
valueMax = min;
|
|
172
|
+
} else {
|
|
173
|
+
value = valueMin = valueMax = null;
|
|
174
|
+
}
|
|
258
175
|
}
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
});
|
|
266
|
-
const selectedStringFilterType: SelectOption<StringFilterMode> | null = $derived.by(() => {
|
|
267
|
-
const value = (filterMode as StringFilterMode | undefined) ?? StringFilterMode.Contains;
|
|
268
|
-
return stringFilterTypes.find((it) => it.value === value) ?? null;
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
const inputId: string = $derived(`${tableContext.tableName}-${def.id}-filter-type`);
|
|
272
|
-
|
|
273
|
-
let showFromDateModal = $state(false);
|
|
274
|
-
let showToDateModal = $state(false);
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Debounce window for typed filter inputs (Text / Number / Between).
|
|
278
|
-
* Discrete selections (selects, date pickers, filter-mode dropdowns)
|
|
279
|
-
* skip this and fire `filterBy` immediately. Combined with the 400 ms
|
|
280
|
-
* debounce in `createRemoteRowSource`, server-side filtering lands
|
|
281
|
-
* roughly a second after the last keystroke.
|
|
282
|
-
*/
|
|
283
|
-
const FILTER_INPUT_DEBOUNCE_MS = 500;
|
|
284
|
-
|
|
285
|
-
let typingDebounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
286
|
-
|
|
287
|
-
/** Fire the table's `filterBy` after the user stops typing for
|
|
288
|
-
* `FILTER_INPUT_DEBOUNCE_MS`. Repeated calls coalesce; the most
|
|
289
|
-
* recent value wins. */
|
|
290
|
-
function debouncedFilterBy(id: string, newValue: unknown): void {
|
|
291
|
-
if (typingDebounceTimer !== undefined) {
|
|
292
|
-
clearTimeout(typingDebounceTimer);
|
|
176
|
+
} else if (tableContext.paginationRepo?.filtering) {
|
|
177
|
+
if (filteringStateValue == null || typeof filteringStateValue == "string" || typeof filteringStateValue == "number") {
|
|
178
|
+
value = filteringStateValue;
|
|
179
|
+
} else if (typeof filteringStateValue !== "boolean") {
|
|
180
|
+
console.warn("unknown value type:", $state.snapshot(filteringStateValue));
|
|
181
|
+
value = filteringStateValue;
|
|
293
182
|
}
|
|
294
|
-
typingDebounceTimer = setTimeout(() => {
|
|
295
|
-
typingDebounceTimer = undefined;
|
|
296
|
-
tableContext.paginationRepo?.filterBy(id, newValue);
|
|
297
|
-
}, FILTER_INPUT_DEBOUNCE_MS);
|
|
298
183
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
184
|
+
});
|
|
185
|
+
onMount(() => {
|
|
186
|
+
const filterOption = tableContext.paginationRepo?.filtering && tableContext.paginationRepo.filtering.find((it) => it.id === def.id);
|
|
187
|
+
if (filterOption) {
|
|
188
|
+
if (isNumericArray(filterOption.value)) {
|
|
189
|
+
[valueMin, valueMax] = filterOption.value;
|
|
190
|
+
if (valueMax == undefined) {
|
|
191
|
+
valueMax = valueMin;
|
|
192
|
+
} else if (valueMin == undefined) {
|
|
193
|
+
valueMin = valueMax;
|
|
307
194
|
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
195
|
+
} else if (!hasQuery(customFilter)) {
|
|
196
|
+
const gotValue = filterOption.value;
|
|
197
|
+
if (gotValue == null || typeof gotValue == "string" || typeof gotValue == "number") {
|
|
198
|
+
value = gotValue;
|
|
199
|
+
} else if (typeof filteringStateValue !== "boolean") {
|
|
200
|
+
console.warn("unknown value type:", $state.snapshot(gotValue));
|
|
201
|
+
value = gotValue;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
317
204
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
205
|
+
});
|
|
206
|
+
const filterMode = $derived(tableContext.paginationRepo?.filterModes.get(def.id));
|
|
207
|
+
const selectedNumberFilterType = $derived.by(() => {
|
|
208
|
+
const value = filterMode ?? NumberDateFilterMode.In;
|
|
209
|
+
return numberDateFilterTypes.find((it) => it.value === value) ?? null;
|
|
210
|
+
});
|
|
211
|
+
const selectedStringFilterType = $derived.by(() => {
|
|
212
|
+
const value = filterMode ?? StringFilterMode.Contains;
|
|
213
|
+
return stringFilterTypes.find((it) => it.value === value) ?? null;
|
|
214
|
+
});
|
|
215
|
+
const inputId = $derived(`${tableContext.tableName}-${def.id}-filter-type`);
|
|
216
|
+
let showFromDateModal = $state(false);
|
|
217
|
+
let showToDateModal = $state(false);
|
|
218
|
+
/**
|
|
219
|
+
* Debounce window for typed filter inputs (Text / Number / Between).
|
|
220
|
+
* Discrete selections (selects, date pickers, filter-mode dropdowns)
|
|
221
|
+
* skip this and fire `filterBy` immediately. Combined with the 400 ms
|
|
222
|
+
* debounce in `createRemoteRowSource`, server-side filtering lands
|
|
223
|
+
* roughly a second after the last keystroke.
|
|
224
|
+
*/
|
|
225
|
+
const FILTER_INPUT_DEBOUNCE_MS = 500;
|
|
226
|
+
let typingDebounceTimer;
|
|
227
|
+
/** Fire the table's `filterBy` after the user stops typing for
|
|
228
|
+
* `FILTER_INPUT_DEBOUNCE_MS`. Repeated calls coalesce; the most
|
|
229
|
+
* recent value wins. */
|
|
230
|
+
function debouncedFilterBy(id, newValue) {
|
|
231
|
+
if (typingDebounceTimer !== undefined) {
|
|
232
|
+
clearTimeout(typingDebounceTimer);
|
|
326
233
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
{
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
{
|
|
640
|
-
|
|
234
|
+
typingDebounceTimer = setTimeout(() => {
|
|
235
|
+
typingDebounceTimer = undefined;
|
|
236
|
+
tableContext.paginationRepo?.filterBy(id, newValue);
|
|
237
|
+
}, FILTER_INPUT_DEBOUNCE_MS);
|
|
238
|
+
}
|
|
239
|
+
// Clear any pending debounce when the row unmounts so we do not
|
|
240
|
+
// fire `filterBy` against a torn-down `paginationRepo`.
|
|
241
|
+
$effect(() => {
|
|
242
|
+
return () => {
|
|
243
|
+
if (typingDebounceTimer !== undefined) {
|
|
244
|
+
clearTimeout(typingDebounceTimer);
|
|
245
|
+
typingDebounceTimer = undefined;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
function generateOnChangeObject(_label) {
|
|
250
|
+
return (newValue) => {
|
|
251
|
+
tableContext.paginationRepo?.filterBy(def.id, newValue?.value ?? null);
|
|
252
|
+
tableContext.queryValues.set(def, newValue ?? null);
|
|
253
|
+
value = newValue ?? null;
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function generateOnChangePrimitive(_label) {
|
|
257
|
+
// AsPrimitive QuerySelect hands us the primitive value directly.
|
|
258
|
+
return (newValue) => {
|
|
259
|
+
tableContext.paginationRepo?.filterBy(def.id, newValue ?? null);
|
|
260
|
+
tableContext.queryValues.set(def, newValue ?? null);
|
|
261
|
+
value = newValue ?? null;
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
</script>
|
|
265
|
+
|
|
266
|
+
{#if isSearchable}
|
|
267
|
+
<!-- TODO remove this check once datePicker is supported -->
|
|
268
|
+
{#if def instanceof DateInputDef || def instanceof CheckboxDef}
|
|
269
|
+
<label for={inputId}>{displayLabel}</label>
|
|
270
|
+
{/if}
|
|
271
|
+
<div class="row">
|
|
272
|
+
{#if def instanceof DateInputDef}
|
|
273
|
+
{@const dateValues =
|
|
274
|
+
tableContext.paginationRepo?.filtering.find((it) => it.id === def.id)?.value ?? []}
|
|
275
|
+
{#if isDateTuple(dateValues)}
|
|
276
|
+
{@const [fromDate, toDate] = dateValues}
|
|
277
|
+
<div class="spaced-around">
|
|
278
|
+
<Button
|
|
279
|
+
onclick={() => {
|
|
280
|
+
showFromDateModal = true;
|
|
281
|
+
}}>From {fromDate?.toLocaleDateString() ?? 'Any Date'}</Button
|
|
282
|
+
>
|
|
283
|
+
|
|
284
|
+
<Button
|
|
285
|
+
onclick={() => {
|
|
286
|
+
showToDateModal = true;
|
|
287
|
+
}}>To {toDate?.toLocaleDateString() ?? 'Any Date'}</Button
|
|
288
|
+
>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
{#if showFromDateModal}
|
|
292
|
+
<DateModal
|
|
293
|
+
header="Edit Date From"
|
|
294
|
+
value={fromDate}
|
|
295
|
+
cancelEditing={() => {
|
|
296
|
+
showFromDateModal = false;
|
|
297
|
+
}}
|
|
298
|
+
confirm={(e) => {
|
|
299
|
+
tableContext.paginationRepo?.filterBy(def.id, [e, toDate]);
|
|
300
|
+
showFromDateModal = false;
|
|
301
|
+
}}
|
|
302
|
+
/>
|
|
303
|
+
{/if}
|
|
304
|
+
|
|
305
|
+
{#if showToDateModal}
|
|
306
|
+
<DateModal
|
|
307
|
+
inclusive
|
|
308
|
+
header="Edit Date To"
|
|
309
|
+
value={toDate}
|
|
310
|
+
cancelEditing={() => {
|
|
311
|
+
showToDateModal = false;
|
|
312
|
+
}}
|
|
313
|
+
confirm={(e) => {
|
|
314
|
+
tableContext.paginationRepo?.filterBy(def.id, [fromDate, e]);
|
|
315
|
+
showToDateModal = false;
|
|
316
|
+
}}
|
|
317
|
+
/>
|
|
318
|
+
{/if}
|
|
319
|
+
{/if}
|
|
320
|
+
{:else if def instanceof SelectDef && !isRemoteSelect}
|
|
321
|
+
<div class="maxwidth">
|
|
322
|
+
{#await optionsPromise then options}
|
|
323
|
+
{@const selectedPrimitives = selectedOptions
|
|
324
|
+
.map((it) => it.value)
|
|
325
|
+
.filter((v): v is string | number => typeof v === 'string' || typeof v === 'number')}
|
|
326
|
+
<MultiSelect
|
|
327
|
+
label={customFilter?.id ??
|
|
328
|
+
customFilter?.label ??
|
|
329
|
+
customFilter?.objectName ??
|
|
330
|
+
customFilter?.queryKey ??
|
|
331
|
+
def.id}
|
|
332
|
+
labelProp="label"
|
|
333
|
+
valueProp="value"
|
|
334
|
+
name={customFilter?.objectName ?? 'Item'}
|
|
335
|
+
{options}
|
|
336
|
+
valueAsObject={false}
|
|
337
|
+
onchange={(e: Array<unknown>) => {
|
|
338
|
+
// Phase 7.6: `valueAsObject={false}` returns
|
|
339
|
+
// primitives directly.
|
|
340
|
+
tableContext.paginationRepo?.filterBy(def.id, e);
|
|
341
|
+
}}
|
|
342
|
+
selectedValues={selectedPrimitives}
|
|
343
|
+
collapseSelection
|
|
344
|
+
/>
|
|
345
|
+
{:catch e}
|
|
346
|
+
<span>Error retrieving options: {e.message}</span>
|
|
347
|
+
<Button
|
|
348
|
+
onclick={() => {
|
|
349
|
+
retryOptionsPromise();
|
|
350
|
+
}}>Retry</Button
|
|
351
|
+
>
|
|
352
|
+
{/await}
|
|
353
|
+
</div>
|
|
354
|
+
{:else if customFilter && isRemoteSelect}
|
|
355
|
+
<div class="contents">
|
|
356
|
+
{#await options then resolvedOptions}
|
|
357
|
+
{@const options = resolvedOptions as Array<SelectOption<unknown>>}
|
|
358
|
+
{#if hasQuery(customFilter)}
|
|
359
|
+
{#if customFilter.valueAsObject !== true}
|
|
360
|
+
{@const label = displayLabel}
|
|
361
|
+
|
|
362
|
+
<!-- TODO: multiselect -->
|
|
363
|
+
|
|
364
|
+
<QuerySelect
|
|
365
|
+
valueAsObject={false}
|
|
366
|
+
{label}
|
|
367
|
+
clearable
|
|
368
|
+
name={customFilter.objectName ?? 'Item'}
|
|
369
|
+
valueProp={'value' as never}
|
|
370
|
+
labelProp={'label' as never}
|
|
371
|
+
value={value as never}
|
|
372
|
+
query={customFilter.query}
|
|
373
|
+
bind:options={lastQueryOptions as never}
|
|
374
|
+
onchange={generateOnChangePrimitive(label)}
|
|
375
|
+
/>
|
|
376
|
+
{:else}
|
|
377
|
+
{@const label = displayLabel}
|
|
378
|
+
|
|
379
|
+
<!-- TODO: multiselect -->
|
|
380
|
+
|
|
381
|
+
<QuerySelect
|
|
382
|
+
valueAsObject={true}
|
|
383
|
+
{label}
|
|
384
|
+
clearable
|
|
385
|
+
name={customFilter.objectName ?? 'Item'}
|
|
386
|
+
valueProp="value"
|
|
387
|
+
labelProp="label"
|
|
388
|
+
value={value as null}
|
|
389
|
+
query={customFilter.query}
|
|
390
|
+
bind:options={lastQueryOptions}
|
|
391
|
+
onchange={generateOnChangeObject(label)}
|
|
392
|
+
/>
|
|
393
|
+
{/if}
|
|
394
|
+
{:else}
|
|
395
|
+
{@const selectedPrimitives = selectedOptions
|
|
396
|
+
.map((it) => it.value)
|
|
397
|
+
.filter(
|
|
398
|
+
(v): v is string | number => typeof v === 'string' || typeof v === 'number'
|
|
399
|
+
)}
|
|
400
|
+
<!--
|
|
401
|
+
With `valueAsObject={false}`, MultiSelect compares
|
|
402
|
+
its `selectedValues` against options by primitive
|
|
403
|
+
value, so we map `selectedOptions` to their
|
|
404
|
+
primitive `.value` rather than passing the
|
|
405
|
+
SelectOption objects directly.
|
|
406
|
+
-->
|
|
407
|
+
<MultiSelect
|
|
408
|
+
label={displayLabel}
|
|
409
|
+
labelProp="label"
|
|
410
|
+
valueProp="value"
|
|
411
|
+
name={customFilter.objectName ?? 'Item'}
|
|
412
|
+
{options}
|
|
413
|
+
valueAsObject={false}
|
|
414
|
+
onchange={(e: Array<unknown>) => {
|
|
415
|
+
// Phase 7.6: `valueAsObject={false}` returns
|
|
416
|
+
// primitives directly.
|
|
417
|
+
tableContext.paginationRepo?.filterBy(def.id, e);
|
|
418
|
+
}}
|
|
419
|
+
selectedValues={selectedPrimitives}
|
|
420
|
+
collapseSelection
|
|
421
|
+
/>
|
|
422
|
+
{/if}
|
|
423
|
+
{/await}
|
|
424
|
+
</div>
|
|
425
|
+
{:else if def instanceof CheckboxDef}
|
|
426
|
+
<div class="inner-row">
|
|
427
|
+
<label>
|
|
428
|
+
<input
|
|
429
|
+
value={false}
|
|
430
|
+
type="radio"
|
|
431
|
+
bind:group={value}
|
|
432
|
+
onchange={() => {
|
|
433
|
+
tableContext.paginationRepo?.filterBy(def.id, value);
|
|
434
|
+
}}
|
|
435
|
+
/>
|
|
436
|
+
No
|
|
437
|
+
</label>
|
|
438
|
+
<label>
|
|
439
|
+
<input
|
|
440
|
+
value={undefined}
|
|
441
|
+
type="radio"
|
|
442
|
+
bind:group={value}
|
|
443
|
+
onchange={() => {
|
|
444
|
+
tableContext.paginationRepo?.filterBy(def.id, value);
|
|
445
|
+
}}
|
|
446
|
+
/>
|
|
447
|
+
Any
|
|
448
|
+
</label>
|
|
449
|
+
<label>
|
|
450
|
+
<input
|
|
451
|
+
value={true}
|
|
452
|
+
type="radio"
|
|
453
|
+
bind:group={value}
|
|
454
|
+
onchange={() => {
|
|
455
|
+
tableContext.paginationRepo?.filterBy(def.id, value);
|
|
456
|
+
}}
|
|
457
|
+
/>
|
|
458
|
+
Yes
|
|
459
|
+
</label>
|
|
460
|
+
</div>
|
|
461
|
+
{:else if isNumeric}
|
|
462
|
+
{@const label = typeof def.header === 'string' ? def.header : undefined}
|
|
463
|
+
<div class="left">
|
|
464
|
+
<SingleSelect
|
|
465
|
+
{label}
|
|
466
|
+
name="Filter Type"
|
|
467
|
+
options={numberDateFilterTypes}
|
|
468
|
+
labelProp="label"
|
|
469
|
+
valueProp="value"
|
|
470
|
+
valueAsObject
|
|
471
|
+
value={selectedNumberFilterType}
|
|
472
|
+
onchange={(newValue: SelectOption<NumberDateFilterMode> | null) => {
|
|
473
|
+
const oldFilterType = selectedNumberFilterType;
|
|
474
|
+
|
|
475
|
+
const newFilterType = newValue?.value ?? NumberDateFilterMode.In;
|
|
476
|
+
tableContext.paginationRepo?.setFilterMode(def.id, newFilterType);
|
|
477
|
+
|
|
478
|
+
if (selectedNumberFilterType?.value !== oldFilterType?.value) {
|
|
479
|
+
if (oldFilterType?.value === NumberDateFilterMode.Between) {
|
|
480
|
+
valueMax = null;
|
|
481
|
+
tableContext.paginationRepo?.filterBy(def.id, [valueMin, valueMin]);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (newFilterType === NumberDateFilterMode.In) {
|
|
485
|
+
tableContext.paginationRepo?.filterBy(def.id, [value]);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// TODO what about the other modes?
|
|
489
|
+
}
|
|
490
|
+
}}
|
|
491
|
+
onkeydown={(e: KeyboardEvent) => {
|
|
492
|
+
e.stopPropagation();
|
|
493
|
+
}}
|
|
494
|
+
/>
|
|
495
|
+
</div>
|
|
496
|
+
<div class="right">
|
|
497
|
+
{#if selectedNumberFilterType?.value === NumberDateFilterMode.Between}
|
|
498
|
+
<NumberInput
|
|
499
|
+
id={inputId}
|
|
500
|
+
bind:value={valueMin}
|
|
501
|
+
oninput={() => {
|
|
502
|
+
debouncedFilterBy(def.id, [valueMin, valueMax]);
|
|
503
|
+
}}
|
|
504
|
+
onkeydown={(e: KeyboardEvent) => {
|
|
505
|
+
e.stopPropagation();
|
|
506
|
+
}}
|
|
507
|
+
/>
|
|
508
|
+
<span>and</span>
|
|
509
|
+
<NumberInput
|
|
510
|
+
id={inputId}
|
|
511
|
+
bind:value={valueMax}
|
|
512
|
+
oninput={() => {
|
|
513
|
+
debouncedFilterBy(def.id, [valueMin, valueMax]);
|
|
514
|
+
}}
|
|
515
|
+
onkeydown={(e: KeyboardEvent) => {
|
|
516
|
+
e.stopPropagation();
|
|
517
|
+
}}
|
|
518
|
+
/>
|
|
519
|
+
{:else if value == null || typeof value == 'string'}
|
|
520
|
+
<TextInput
|
|
521
|
+
id={inputId}
|
|
522
|
+
bind:value
|
|
523
|
+
oninput={() => {
|
|
524
|
+
debouncedFilterBy(def.id, [value]);
|
|
525
|
+
}}
|
|
526
|
+
onkeydown={(e: KeyboardEvent) => {
|
|
527
|
+
e.stopPropagation();
|
|
528
|
+
}}
|
|
529
|
+
/>
|
|
530
|
+
{:else if dev}
|
|
531
|
+
error: {value?.toString()}
|
|
532
|
+
<!-- eslint-disable-next-line svelte/no-at-debug-tags -->
|
|
533
|
+
{@debug value}
|
|
534
|
+
{/if}
|
|
535
|
+
</div>
|
|
536
|
+
{:else}
|
|
537
|
+
{@const label = typeof def.header === 'string' ? def.header : undefined}
|
|
538
|
+
<div class="left">
|
|
539
|
+
<SingleSelect
|
|
540
|
+
name="Filter Type"
|
|
541
|
+
valueAsObject
|
|
542
|
+
{label}
|
|
543
|
+
options={stringFilterTypes}
|
|
544
|
+
labelProp="label"
|
|
545
|
+
valueProp="value"
|
|
546
|
+
value={selectedStringFilterType}
|
|
547
|
+
onchange={(newValue: SelectOption<StringFilterMode> | null) => {
|
|
548
|
+
const filterType = newValue?.value ?? StringFilterMode.Contains;
|
|
549
|
+
tableContext.paginationRepo?.setFilterMode(def.id, filterType);
|
|
550
|
+
}}
|
|
551
|
+
onkeydown={(e: KeyboardEvent) => {
|
|
552
|
+
e.stopPropagation();
|
|
553
|
+
}}
|
|
554
|
+
/>
|
|
555
|
+
</div>
|
|
556
|
+
<div class="right">
|
|
557
|
+
{#if value == null || typeof value == 'string'}
|
|
558
|
+
<TextInput
|
|
559
|
+
id={inputId}
|
|
560
|
+
bind:value
|
|
561
|
+
oninput={() => {
|
|
562
|
+
debouncedFilterBy(def.id, value);
|
|
563
|
+
}}
|
|
564
|
+
onkeydown={(e: KeyboardEvent) => {
|
|
565
|
+
e.stopPropagation();
|
|
566
|
+
}}
|
|
567
|
+
/>
|
|
568
|
+
{:else if dev}
|
|
569
|
+
error: {value?.toString()}
|
|
570
|
+
<!-- eslint-disable-next-line svelte/no-at-debug-tags -->
|
|
571
|
+
{@debug value}
|
|
572
|
+
{/if}
|
|
573
|
+
</div>
|
|
574
|
+
{/if}
|
|
575
|
+
</div>
|
|
576
|
+
{/if}
|
|
577
|
+
|
|
641
578
|
<style>.contents {
|
|
642
579
|
display: contents;
|
|
643
580
|
--input-width: 100%;
|
|
@@ -708,4 +645,4 @@
|
|
|
708
645
|
.inner-row label input {
|
|
709
646
|
cursor: pointer;
|
|
710
647
|
width: 2rem;
|
|
711
|
-
}</style>
|
|
648
|
+
}</style>
|