@softwareone/spi-sv5-library 1.10.5 → 1.11.1
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/dist/Card/Card.svelte +1 -1
- package/dist/{AttachFile → Controls/AttachFile}/AttachFile.svelte +39 -23
- package/dist/{AttachFile → Controls/AttachFile}/AttachFile.svelte.d.ts +2 -1
- package/dist/{AttachFile → Controls/AttachFile}/Warnings.svelte +1 -1
- package/dist/Controls/Input/Input.svelte +2 -4
- package/dist/Controls/Input/Input.svelte.d.ts +2 -4
- package/dist/Controls/Label/Label.svelte +3 -8
- package/dist/Controls/Label/Label.svelte.d.ts +1 -7
- package/dist/Controls/Label/labelState.svelte.d.ts +7 -0
- package/dist/Controls/Label/labelState.svelte.js +1 -0
- package/dist/Controls/Select/Select.svelte +2 -5
- package/dist/Controls/Select/Select.svelte.d.ts +2 -5
- package/dist/Controls/TextArea/TextArea.svelte +2 -4
- package/dist/Controls/TextArea/TextArea.svelte.d.ts +2 -4
- package/dist/Controls/Toggle/Toggle.svelte +3 -5
- package/dist/Controls/Toggle/Toggle.svelte.d.ts +2 -4
- package/dist/Table/AdvancedFilter.svelte +351 -0
- package/dist/Table/AdvancedFilter.svelte.d.ts +29 -0
- package/dist/Table/Header.svelte +34 -5
- package/dist/Table/Table.svelte +79 -36
- package/dist/Table/Table.svelte.d.ts +2 -1
- package/dist/Table/consts.d.ts +10 -0
- package/dist/Table/consts.js +8 -0
- package/dist/Table/index.d.ts +2 -1
- package/dist/Table/index.js +2 -1
- package/dist/Table/types.js +1 -1
- package/dist/Table/util.d.ts +4 -1
- package/dist/Table/util.js +51 -3
- package/dist/Tabs/Tabs.svelte +5 -2
- package/dist/Tabs/tabsState.svelte.d.ts +7 -5
- package/dist/Tabs/tabsState.svelte.js +3 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/package.json +1 -1
- /package/dist/{AttachFile → Controls/AttachFile}/FileManager.svelte +0 -0
- /package/dist/{AttachFile → Controls/AttachFile}/FileManager.svelte.d.ts +0 -0
- /package/dist/{AttachFile → Controls/AttachFile}/Warnings.svelte.d.ts +0 -0
package/dist/Card/Card.svelte
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Notification } from '../index.js';
|
|
3
2
|
import type { Attachment } from 'svelte/attachments';
|
|
3
|
+
|
|
4
|
+
import type { LabelProps } from '../Label/labelState.svelte.js';
|
|
5
|
+
import { Label, Notification } from '../../index.js';
|
|
4
6
|
import FileManager from './FileManager.svelte';
|
|
5
7
|
import Warnings from './Warnings.svelte';
|
|
6
8
|
|
|
7
|
-
interface Props {
|
|
9
|
+
interface Props extends LabelProps {
|
|
8
10
|
accept?: string;
|
|
9
11
|
multiple?: boolean;
|
|
10
12
|
externalFileValidationMessages?: string[];
|
|
@@ -25,20 +27,29 @@
|
|
|
25
27
|
fileSizeLimitMB,
|
|
26
28
|
error,
|
|
27
29
|
onvalidatefile,
|
|
28
|
-
onchange
|
|
30
|
+
onchange,
|
|
31
|
+
label,
|
|
32
|
+
id,
|
|
33
|
+
required,
|
|
34
|
+
optional,
|
|
35
|
+
infoTooltip
|
|
29
36
|
}: Props = $props();
|
|
30
37
|
|
|
31
38
|
let validationMessages = $state<string[]>([]);
|
|
32
39
|
|
|
33
|
-
const extensionFilesMessage =
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
const extensionFilesMessage = $derived(
|
|
41
|
+
accept
|
|
42
|
+
? `Files of the following type are allowed: ${accept.replaceAll(',', ' & ')}.`
|
|
43
|
+
: 'All file types are allowed.'
|
|
44
|
+
);
|
|
36
45
|
|
|
37
|
-
const reviewMessages =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
const reviewMessages = $derived(
|
|
47
|
+
[
|
|
48
|
+
extensionFilesMessage,
|
|
49
|
+
multiple && 'Duplicate file names are not allowed.',
|
|
50
|
+
...externalFileValidationMessages
|
|
51
|
+
].filter(Boolean)
|
|
52
|
+
);
|
|
42
53
|
|
|
43
54
|
const onChangeInputFile = async (selectedFiles: FileList | null) => {
|
|
44
55
|
if (selectedFiles?.length) {
|
|
@@ -138,7 +149,7 @@
|
|
|
138
149
|
};
|
|
139
150
|
</script>
|
|
140
151
|
|
|
141
|
-
<
|
|
152
|
+
<div class="container">
|
|
142
153
|
<Notification type="info">
|
|
143
154
|
{#snippet content()}
|
|
144
155
|
<ul class="message">
|
|
@@ -148,13 +159,20 @@
|
|
|
148
159
|
</ul>
|
|
149
160
|
{/snippet}
|
|
150
161
|
</Notification>
|
|
151
|
-
<
|
|
162
|
+
<Label
|
|
163
|
+
label={label || `Upload ${multiple ? 'files' : 'file'}`}
|
|
164
|
+
{id}
|
|
165
|
+
{required}
|
|
166
|
+
{optional}
|
|
167
|
+
{infoTooltip}
|
|
168
|
+
/>
|
|
152
169
|
<section class="drop-area">
|
|
153
170
|
<p class="caption">Drag and drop files here</p>
|
|
154
171
|
<p class="text-small">or</p>
|
|
155
172
|
|
|
156
173
|
<input
|
|
157
174
|
{@attach loadFiles}
|
|
175
|
+
{id}
|
|
158
176
|
name="files"
|
|
159
177
|
type="file"
|
|
160
178
|
aria-label="Select files to upload"
|
|
@@ -177,7 +195,7 @@
|
|
|
177
195
|
{#if files?.length}
|
|
178
196
|
<FileManager {fileSizeLimitMB} {files} {updateFiles} />
|
|
179
197
|
{/if}
|
|
180
|
-
</
|
|
198
|
+
</div>
|
|
181
199
|
|
|
182
200
|
<style>
|
|
183
201
|
.container {
|
|
@@ -192,6 +210,10 @@
|
|
|
192
210
|
--spacing-md: 20px;
|
|
193
211
|
--spacing-sm: 10px;
|
|
194
212
|
--border-radius-md: 5px;
|
|
213
|
+
|
|
214
|
+
display: flex;
|
|
215
|
+
flex-direction: column;
|
|
216
|
+
gap: 8px;
|
|
195
217
|
width: 100%;
|
|
196
218
|
}
|
|
197
219
|
|
|
@@ -199,21 +221,15 @@
|
|
|
199
221
|
font-size: 14px;
|
|
200
222
|
line-height: 20px;
|
|
201
223
|
}
|
|
202
|
-
.title {
|
|
203
|
-
font-size: 16px;
|
|
204
|
-
line-height: 24px;
|
|
205
|
-
font-weight: 500;
|
|
206
|
-
margin-top: 12px;
|
|
207
|
-
}
|
|
208
224
|
|
|
209
225
|
.caption {
|
|
210
|
-
font-size:
|
|
226
|
+
font-size: 14px;
|
|
211
227
|
line-height: 24px;
|
|
212
228
|
color: var(--color-gray-outer);
|
|
213
229
|
}
|
|
214
230
|
|
|
215
231
|
.text-small {
|
|
216
|
-
font-size:
|
|
232
|
+
font-size: 12px;
|
|
217
233
|
line-height: 20px;
|
|
218
234
|
color: var(--color-gray-auro);
|
|
219
235
|
margin-top: 4px;
|
|
@@ -233,7 +249,6 @@
|
|
|
233
249
|
border: 2px dashed var(--color-border);
|
|
234
250
|
border-radius: var(--border-radius-md);
|
|
235
251
|
text-align: center;
|
|
236
|
-
margin-top: 8px;
|
|
237
252
|
}
|
|
238
253
|
|
|
239
254
|
.drop-area input[type='file'] {
|
|
@@ -247,6 +262,7 @@
|
|
|
247
262
|
|
|
248
263
|
.file-select {
|
|
249
264
|
display: inline-block;
|
|
265
|
+
font-size: 14px;
|
|
250
266
|
background: var(--color-bg-select);
|
|
251
267
|
color: var(--color-text-select);
|
|
252
268
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
3
|
|
|
4
|
+
import type { LabelProps } from '../Label/labelState.svelte.js';
|
|
4
5
|
import Label from '../Label/Label.svelte';
|
|
5
6
|
import InputIcon from './InputIcon.svelte';
|
|
6
7
|
|
|
7
8
|
type InputType = 'text' | 'password' | 'number' | 'date' | 'money';
|
|
8
9
|
|
|
9
|
-
interface InputProps extends Omit<HTMLInputAttributes, 'type' | 'value'
|
|
10
|
-
label?: string;
|
|
10
|
+
interface InputProps extends Omit<HTMLInputAttributes, 'type' | 'value'>, LabelProps {
|
|
11
11
|
type?: InputType;
|
|
12
12
|
value?: string | number | null;
|
|
13
|
-
optional?: boolean;
|
|
14
13
|
error?: string | string[];
|
|
15
14
|
description?: string;
|
|
16
15
|
currency?: string;
|
|
17
16
|
disableValidationColor?: boolean;
|
|
18
|
-
infoTooltip?: string;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
let {
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import type { LabelProps } from '../Label/labelState.svelte.js';
|
|
2
3
|
type InputType = 'text' | 'password' | 'number' | 'date' | 'money';
|
|
3
|
-
interface InputProps extends Omit<HTMLInputAttributes, 'type' | 'value'
|
|
4
|
-
label?: string;
|
|
4
|
+
interface InputProps extends Omit<HTMLInputAttributes, 'type' | 'value'>, LabelProps {
|
|
5
5
|
type?: InputType;
|
|
6
6
|
value?: string | number | null;
|
|
7
|
-
optional?: boolean;
|
|
8
7
|
error?: string | string[];
|
|
9
8
|
description?: string;
|
|
10
9
|
currency?: string;
|
|
11
10
|
disableValidationColor?: boolean;
|
|
12
|
-
infoTooltip?: string;
|
|
13
11
|
}
|
|
14
12
|
declare const Input: import("svelte").Component<InputProps, {}, "value">;
|
|
15
13
|
type Input = ReturnType<typeof Input>;
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Tooltip } from '../../index.js';
|
|
3
|
-
|
|
4
|
-
interface LabelProps {
|
|
5
|
-
label?: string;
|
|
6
|
-
id?: string | null;
|
|
7
|
-
required?: boolean | null;
|
|
8
|
-
optional?: boolean;
|
|
9
|
-
infoTooltip?: string;
|
|
10
|
-
}
|
|
3
|
+
import type { LabelProps } from './labelState.svelte.js';
|
|
11
4
|
|
|
12
5
|
let { label, id, required, optional, infoTooltip }: LabelProps = $props();
|
|
13
6
|
</script>
|
|
@@ -33,6 +26,8 @@
|
|
|
33
26
|
display: flex;
|
|
34
27
|
gap: 8px;
|
|
35
28
|
font-weight: 500;
|
|
29
|
+
font-size: 14px;
|
|
30
|
+
line-height: 20px;
|
|
36
31
|
}
|
|
37
32
|
|
|
38
33
|
.form-label-optional {
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
label?: string;
|
|
3
|
-
id?: string | null;
|
|
4
|
-
required?: boolean | null;
|
|
5
|
-
optional?: boolean;
|
|
6
|
-
infoTooltip?: string;
|
|
7
|
-
}
|
|
1
|
+
import type { LabelProps } from './labelState.svelte.js';
|
|
8
2
|
declare const Label: import("svelte").Component<LabelProps, {}, "">;
|
|
9
3
|
type Label = ReturnType<typeof Label>;
|
|
10
4
|
export default Label;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -4,15 +4,12 @@
|
|
|
4
4
|
import type { Attachment } from 'svelte/attachments';
|
|
5
5
|
|
|
6
6
|
import { Search, type SelectOption } from '../../index.js';
|
|
7
|
+
import type { LabelProps } from '../Label/labelState.svelte.js';
|
|
7
8
|
import Label from '../Label/Label.svelte';
|
|
8
9
|
|
|
9
|
-
interface SelectProps {
|
|
10
|
+
interface SelectProps extends LabelProps {
|
|
10
11
|
options: string[] | SelectOption[];
|
|
11
|
-
label?: string;
|
|
12
12
|
placeholder?: string;
|
|
13
|
-
infoTooltip?: string;
|
|
14
|
-
required?: boolean;
|
|
15
|
-
optional?: boolean;
|
|
16
13
|
searchable?: boolean;
|
|
17
14
|
hideClearButton?: boolean;
|
|
18
15
|
multiple?: boolean;
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { type Snippet } from 'svelte';
|
|
2
2
|
import { type SelectOption } from '../../index.js';
|
|
3
|
-
|
|
3
|
+
import type { LabelProps } from '../Label/labelState.svelte.js';
|
|
4
|
+
interface SelectProps extends LabelProps {
|
|
4
5
|
options: string[] | SelectOption[];
|
|
5
|
-
label?: string;
|
|
6
6
|
placeholder?: string;
|
|
7
|
-
infoTooltip?: string;
|
|
8
|
-
required?: boolean;
|
|
9
|
-
optional?: boolean;
|
|
10
7
|
searchable?: boolean;
|
|
11
8
|
hideClearButton?: boolean;
|
|
12
9
|
multiple?: boolean;
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
3
3
|
|
|
4
|
+
import type { LabelProps } from '../Label/labelState.svelte.js';
|
|
4
5
|
import Label from '../Label/Label.svelte';
|
|
5
6
|
|
|
6
|
-
interface TextAreaProps extends Omit<HTMLTextareaAttributes, 'value'
|
|
7
|
-
label?: string;
|
|
7
|
+
interface TextAreaProps extends Omit<HTMLTextareaAttributes, 'value'>, LabelProps {
|
|
8
8
|
value?: string | null;
|
|
9
|
-
optional?: boolean;
|
|
10
9
|
error?: string | string[];
|
|
11
10
|
description?: string;
|
|
12
11
|
maximumCharactersAllowed?: number;
|
|
13
12
|
resize?: boolean;
|
|
14
|
-
infoTooltip?: string;
|
|
15
13
|
disableValidationColor?: boolean;
|
|
16
14
|
}
|
|
17
15
|
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import type { LabelProps } from '../Label/labelState.svelte.js';
|
|
3
|
+
interface TextAreaProps extends Omit<HTMLTextareaAttributes, 'value'>, LabelProps {
|
|
4
4
|
value?: string | null;
|
|
5
|
-
optional?: boolean;
|
|
6
5
|
error?: string | string[];
|
|
7
6
|
description?: string;
|
|
8
7
|
maximumCharactersAllowed?: number;
|
|
9
8
|
resize?: boolean;
|
|
10
|
-
infoTooltip?: string;
|
|
11
9
|
disableValidationColor?: boolean;
|
|
12
10
|
}
|
|
13
11
|
declare const TextArea: import("svelte").Component<TextAreaProps, {}, "value">;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Label from '../Label/Label.svelte';
|
|
3
|
+
import type { LabelProps } from '../Label/labelState.svelte.js';
|
|
3
4
|
|
|
4
|
-
interface ToggleProps {
|
|
5
|
-
id?: string;
|
|
5
|
+
interface ToggleProps extends Pick<LabelProps, 'id' | 'label' | 'infoTooltip'> {
|
|
6
6
|
checked?: boolean;
|
|
7
7
|
disabled?: boolean;
|
|
8
|
-
label?: string;
|
|
9
|
-
infoTooltip?: string;
|
|
10
8
|
vertical?: boolean;
|
|
11
9
|
onchange?: (event: Event) => void;
|
|
12
10
|
}
|
|
@@ -43,7 +41,7 @@
|
|
|
43
41
|
</span>
|
|
44
42
|
</div>
|
|
45
43
|
</label>
|
|
46
|
-
<Label {label} {infoTooltip} />
|
|
44
|
+
<Label {id} {label} {infoTooltip} />
|
|
47
45
|
</div>
|
|
48
46
|
|
|
49
47
|
<style>
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { LabelProps } from '../Label/labelState.svelte.js';
|
|
2
|
+
interface ToggleProps extends Pick<LabelProps, 'id' | 'label' | 'infoTooltip'> {
|
|
3
3
|
checked?: boolean;
|
|
4
4
|
disabled?: boolean;
|
|
5
|
-
label?: string;
|
|
6
|
-
infoTooltip?: string;
|
|
7
5
|
vertical?: boolean;
|
|
8
6
|
onchange?: (event: Event) => void;
|
|
9
7
|
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
<script lang="ts" generics="T">
|
|
2
|
+
import { page } from '$app/state';
|
|
3
|
+
|
|
4
|
+
import { Button, Input, Select, type SelectOption } from '../index.js';
|
|
5
|
+
import type { Column } from './adapter/index.js';
|
|
6
|
+
import { Operation } from './consts.js';
|
|
7
|
+
import { type Filter } from './types.js';
|
|
8
|
+
import { sanitizeFilters } from './util.js';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
columns: Column<T, unknown>[];
|
|
12
|
+
onfilterschange?: (filters: Filter[]) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { columns, onfilterschange }: Props = $props();
|
|
16
|
+
|
|
17
|
+
let isOpen = $state(false);
|
|
18
|
+
let editingFilters = $state<Filter[]>([]);
|
|
19
|
+
const operationOptions: Operation[] = Object.values(Operation);
|
|
20
|
+
const filters = $derived(sanitizeFilters(page.url.searchParams.getAll('filter')));
|
|
21
|
+
const hasActiveFilters = $derived(filters.length > 0);
|
|
22
|
+
const columnOptions = $derived<SelectOption[]>(
|
|
23
|
+
columns
|
|
24
|
+
.filter((column) => !column.columnDef.meta?.hideColumnFilter)
|
|
25
|
+
.map((column) => ({
|
|
26
|
+
value: column.id,
|
|
27
|
+
label: (column.columnDef.header as string) || column.id
|
|
28
|
+
}))
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const addFilterRow = () => {
|
|
32
|
+
editingFilters = [...editingFilters, { column: '', value: '', operation: Operation.And }];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const removeFilterRow = (index: number) => {
|
|
36
|
+
const remainingFilters = editingFilters.filter((_, filterIndex) => filterIndex !== index);
|
|
37
|
+
editingFilters = normalizeFilters(remainingFilters);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const applyFilters = () => {
|
|
41
|
+
editingFilters = normalizeFilters(getValidFilters());
|
|
42
|
+
handleFilters();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const normalizeFilters = (filters: Filter[]) => {
|
|
46
|
+
return filters.length === 1 ? [{ ...filters[0], operation: undefined }] : filters;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const clearAllFilters = () => {
|
|
50
|
+
editingFilters = [];
|
|
51
|
+
handleFilters();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleFilters = () => {
|
|
55
|
+
onfilterschange?.(editingFilters);
|
|
56
|
+
isOpen = false;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const toggleModal = () => {
|
|
60
|
+
isOpen = !isOpen;
|
|
61
|
+
isOpen ? initializeEditingFilters() : clearFilters();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const initializeEditingFilters = () => {
|
|
65
|
+
if (!editingFilters.length) {
|
|
66
|
+
editingFilters = filters.length
|
|
67
|
+
? filters.map((filter) => ({ ...filter }))
|
|
68
|
+
: [{ column: '', value: '', operation: undefined }];
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const clearFilters = () => {
|
|
73
|
+
const validFilters = getValidFilters();
|
|
74
|
+
if (validFilters.length === 0 && filters.length > 0) {
|
|
75
|
+
clearAllFilters();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const getValidFilters = () => {
|
|
80
|
+
return editingFilters.filter((filter) => filter.column && filter.value.trim());
|
|
81
|
+
};
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<div class="filter-container">
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
class={['filter-button', hasActiveFilters && 'filter-button--active']}
|
|
88
|
+
onclick={toggleModal}
|
|
89
|
+
>
|
|
90
|
+
<span class="material-icons">filter_list</span>
|
|
91
|
+
{#if hasActiveFilters}
|
|
92
|
+
<strong>{filters.length} {filters.length === 1 ? 'Filter' : 'Filters'}</strong>
|
|
93
|
+
{:else}
|
|
94
|
+
<p>Filters</p>
|
|
95
|
+
{/if}
|
|
96
|
+
</button>
|
|
97
|
+
|
|
98
|
+
{#if isOpen}
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
class="filter-overlay"
|
|
102
|
+
onclick={toggleModal}
|
|
103
|
+
aria-label="Close filter modal"
|
|
104
|
+
></button>
|
|
105
|
+
|
|
106
|
+
<div
|
|
107
|
+
class="filter-modal"
|
|
108
|
+
role="dialog"
|
|
109
|
+
tabindex="-1"
|
|
110
|
+
aria-modal="true"
|
|
111
|
+
aria-labelledby="filter-dialog-title"
|
|
112
|
+
>
|
|
113
|
+
<div class="filter-header">
|
|
114
|
+
<h2 id="filter-dialog-title" class="filter-title">Filters</h2>
|
|
115
|
+
<button type="button" onclick={toggleModal} class="filter-close-button" aria-label="Close">
|
|
116
|
+
<span class="material-icons">close</span>
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="filter-body">
|
|
121
|
+
{#each editingFilters as filter, index (index)}
|
|
122
|
+
<div class="filter-row">
|
|
123
|
+
{#if index > 0}
|
|
124
|
+
<div class="filter-operation">
|
|
125
|
+
<Select
|
|
126
|
+
options={operationOptions}
|
|
127
|
+
disableValidationColor
|
|
128
|
+
bind:value={filter.operation}
|
|
129
|
+
hideClearButton
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
{:else}
|
|
133
|
+
<div class="filter-operation"></div>
|
|
134
|
+
{/if}
|
|
135
|
+
|
|
136
|
+
<div class="filter-column">
|
|
137
|
+
<Select
|
|
138
|
+
options={columnOptions}
|
|
139
|
+
disableValidationColor
|
|
140
|
+
bind:value={filter.column}
|
|
141
|
+
placeholder="Select column"
|
|
142
|
+
searchable
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div class="filter-value">
|
|
147
|
+
<Input
|
|
148
|
+
id="filter-value-{index}"
|
|
149
|
+
disableValidationColor
|
|
150
|
+
type="text"
|
|
151
|
+
bind:value={filter.value}
|
|
152
|
+
placeholder="Enter value"
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<button
|
|
157
|
+
type="button"
|
|
158
|
+
onclick={() => removeFilterRow(index)}
|
|
159
|
+
class="filter-delete-button"
|
|
160
|
+
aria-label="Remove filter"
|
|
161
|
+
>
|
|
162
|
+
<span class="material-icons-outlined">delete</span>
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
{/each}
|
|
166
|
+
|
|
167
|
+
<div class="filter-actions">
|
|
168
|
+
<Button variant="outline-none" type="button" onclick={addFilterRow}>Add condition</Button>
|
|
169
|
+
{#if editingFilters.length}
|
|
170
|
+
<Button variant="outline-none" type="button" onclick={clearAllFilters}>
|
|
171
|
+
Reset filters
|
|
172
|
+
</Button>
|
|
173
|
+
{/if}
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{#if editingFilters.length}
|
|
178
|
+
<div class="filter-footer">
|
|
179
|
+
<Button
|
|
180
|
+
onclick={applyFilters}
|
|
181
|
+
disabled={editingFilters.every((filter) => !filter.column || !filter.value.trim())}
|
|
182
|
+
>
|
|
183
|
+
Apply Filters
|
|
184
|
+
</Button>
|
|
185
|
+
</div>
|
|
186
|
+
{/if}
|
|
187
|
+
</div>
|
|
188
|
+
{/if}
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<style>
|
|
192
|
+
.filter-container {
|
|
193
|
+
--color-primary: #472aff;
|
|
194
|
+
--color-primary-light: #eaecff;
|
|
195
|
+
--color-white: #fff;
|
|
196
|
+
--color-text-primary: #25282d;
|
|
197
|
+
--color-text-dark: #111827;
|
|
198
|
+
--color-text-secondary: #6b7280;
|
|
199
|
+
--color-text-hover: #374151;
|
|
200
|
+
--color-bg-hover: #f4f6f8;
|
|
201
|
+
--color-border: #e5e7eb;
|
|
202
|
+
--spacing-xs: 4px;
|
|
203
|
+
--spacing-sm: 8px;
|
|
204
|
+
--spacing-md: 12px;
|
|
205
|
+
--spacing-lg: 16px;
|
|
206
|
+
--spacing-xl: 24px;
|
|
207
|
+
--font-size-sm: 14px;
|
|
208
|
+
--font-size-md: 18px;
|
|
209
|
+
--font-size-icon: 20px;
|
|
210
|
+
--border-radius-sm: 8px;
|
|
211
|
+
--border-radius-circle: 50%;
|
|
212
|
+
--button-size: 40px;
|
|
213
|
+
--delete-button-size: 48px;
|
|
214
|
+
--modal-width: 640px;
|
|
215
|
+
--operation-column-width: 80px;
|
|
216
|
+
--transition-speed: 0.2s;
|
|
217
|
+
|
|
218
|
+
position: relative;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.filter-button {
|
|
222
|
+
display: flex;
|
|
223
|
+
align-items: center;
|
|
224
|
+
gap: var(--spacing-sm);
|
|
225
|
+
padding: var(--spacing-sm) var(--spacing-lg);
|
|
226
|
+
border: none;
|
|
227
|
+
border-radius: var(--border-radius-sm);
|
|
228
|
+
background: var(--color-white);
|
|
229
|
+
font-size: var(--font-size-sm);
|
|
230
|
+
color: var(--color-text-primary);
|
|
231
|
+
cursor: pointer;
|
|
232
|
+
transition: background-color var(--transition-speed) ease-in-out;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.filter-button:hover {
|
|
236
|
+
background: var(--color-bg-hover);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.filter-button--active {
|
|
240
|
+
background: var(--color-primary-light);
|
|
241
|
+
color: var(--color-primary);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.filter-overlay {
|
|
245
|
+
position: fixed;
|
|
246
|
+
inset: 0;
|
|
247
|
+
z-index: 9;
|
|
248
|
+
background: transparent;
|
|
249
|
+
border: none;
|
|
250
|
+
cursor: default;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.filter-modal {
|
|
254
|
+
position: absolute;
|
|
255
|
+
top: calc(100% + var(--spacing-sm));
|
|
256
|
+
right: 0;
|
|
257
|
+
z-index: 10;
|
|
258
|
+
width: var(--modal-width);
|
|
259
|
+
padding: var(--spacing-xl);
|
|
260
|
+
border: 1px solid var(--color-border);
|
|
261
|
+
border-radius: var(--border-radius-sm);
|
|
262
|
+
background: var(--color-white);
|
|
263
|
+
box-shadow:
|
|
264
|
+
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
|
265
|
+
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.filter-header {
|
|
269
|
+
display: flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
justify-content: space-between;
|
|
272
|
+
margin-bottom: var(--spacing-xl);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.filter-title {
|
|
276
|
+
font-size: var(--font-size-md);
|
|
277
|
+
font-weight: 600;
|
|
278
|
+
color: var(--color-text-dark);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.filter-close-button {
|
|
282
|
+
display: flex;
|
|
283
|
+
align-items: center;
|
|
284
|
+
padding: var(--spacing-xs);
|
|
285
|
+
border: none;
|
|
286
|
+
background: transparent;
|
|
287
|
+
color: var(--color-text-secondary);
|
|
288
|
+
cursor: pointer;
|
|
289
|
+
transition: color var(--transition-speed) ease-in-out;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.filter-close-button:hover {
|
|
293
|
+
color: var(--color-text-hover);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.filter-body {
|
|
297
|
+
display: flex;
|
|
298
|
+
flex-direction: column;
|
|
299
|
+
gap: var(--spacing-lg);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.filter-row {
|
|
303
|
+
display: grid;
|
|
304
|
+
grid-template-columns: var(--operation-column-width) 1fr 1fr var(--delete-button-size);
|
|
305
|
+
align-items: end;
|
|
306
|
+
gap: var(--spacing-md);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.filter-operation,
|
|
310
|
+
.filter-column,
|
|
311
|
+
.filter-value {
|
|
312
|
+
display: flex;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.filter-delete-button {
|
|
316
|
+
display: flex;
|
|
317
|
+
align-items: center;
|
|
318
|
+
justify-content: center;
|
|
319
|
+
padding: var(--spacing-sm);
|
|
320
|
+
border: none;
|
|
321
|
+
background: transparent;
|
|
322
|
+
color: var(--color-text-primary);
|
|
323
|
+
border-radius: var(--border-radius-circle);
|
|
324
|
+
width: var(--button-size);
|
|
325
|
+
height: var(--button-size);
|
|
326
|
+
cursor: pointer;
|
|
327
|
+
transition: background-color var(--transition-speed) ease-in-out;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.filter-delete-button:hover {
|
|
331
|
+
background: var(--color-bg-hover);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.filter-actions {
|
|
335
|
+
display: flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
justify-content: space-between;
|
|
338
|
+
margin-top: var(--spacing-sm);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.filter-footer {
|
|
342
|
+
display: flex;
|
|
343
|
+
justify-content: flex-end;
|
|
344
|
+
margin-top: var(--spacing-lg);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.material-icons,
|
|
348
|
+
.material-icons-outlined {
|
|
349
|
+
font-size: var(--font-size-icon);
|
|
350
|
+
}
|
|
351
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Column } from './adapter/index.js';
|
|
2
|
+
import { type Filter } from './types.js';
|
|
3
|
+
declare function $$render<T>(): {
|
|
4
|
+
props: {
|
|
5
|
+
columns: Column<T, unknown>[];
|
|
6
|
+
onfilterschange?: (filters: Filter[]) => void;
|
|
7
|
+
};
|
|
8
|
+
exports: {};
|
|
9
|
+
bindings: "";
|
|
10
|
+
slots: {};
|
|
11
|
+
events: {};
|
|
12
|
+
};
|
|
13
|
+
declare class __sveltets_Render<T> {
|
|
14
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
15
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
16
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
17
|
+
bindings(): "";
|
|
18
|
+
exports(): {};
|
|
19
|
+
}
|
|
20
|
+
interface $$IsomorphicComponent {
|
|
21
|
+
new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
22
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
23
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
24
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
25
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
26
|
+
}
|
|
27
|
+
declare const AdvancedFilter: $$IsomorphicComponent;
|
|
28
|
+
type AdvancedFilter<T> = InstanceType<typeof AdvancedFilter<T>>;
|
|
29
|
+
export default AdvancedFilter;
|
package/dist/Table/Header.svelte
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends RowData">
|
|
2
|
+
import { page } from '$app/state';
|
|
3
|
+
|
|
2
4
|
import { Search } from '../index.js';
|
|
3
5
|
import { FlexRender, type HeaderGroup, type RowData } from './adapter/index.js';
|
|
6
|
+
import { sanitizeFilters } from './util.js';
|
|
4
7
|
|
|
5
8
|
interface Props {
|
|
6
9
|
headerGroups: HeaderGroup<T>[];
|
|
7
10
|
enableColumnSearch: boolean;
|
|
8
11
|
}
|
|
9
12
|
|
|
13
|
+
let { headerGroups, enableColumnSearch }: Props = $props();
|
|
14
|
+
|
|
10
15
|
const getSortIcon: Record<string, string> = {
|
|
11
16
|
asc: 'arrow_upward',
|
|
12
17
|
desc: 'arrow_downward',
|
|
13
18
|
false: 'swap_vert'
|
|
14
19
|
};
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
const filters = $derived(sanitizeFilters(page.url.searchParams.getAll('filter')));
|
|
22
|
+
|
|
23
|
+
const isColumnFiltered = (columnId: string): boolean => {
|
|
24
|
+
return filters.some((filter) => filter.column === columnId);
|
|
25
|
+
};
|
|
17
26
|
</script>
|
|
18
27
|
|
|
19
28
|
<thead class="container">
|
|
@@ -27,9 +36,11 @@
|
|
|
27
36
|
{@const hideColumnFilter = header.column.columnDef.meta?.hideColumnFilter}
|
|
28
37
|
{@const justifyContent = alignColumn ?? 'left'}
|
|
29
38
|
{@const isSorted = header.column.getIsSorted().toString() !== 'false'}
|
|
39
|
+
{@const isFiltered = isColumnFiltered(header.column.id)}
|
|
40
|
+
{@const isActive = isSorted || isFiltered}
|
|
30
41
|
<th
|
|
31
42
|
colSpan={header.colSpan}
|
|
32
|
-
class={['table-header-cell', className,
|
|
43
|
+
class={['table-header-cell', className, isActive && 'table-header-cell--active']}
|
|
33
44
|
style:width={columnWidth}
|
|
34
45
|
style={columnStyle}
|
|
35
46
|
>
|
|
@@ -61,6 +72,12 @@
|
|
|
61
72
|
>
|
|
62
73
|
</div>
|
|
63
74
|
{/if}
|
|
75
|
+
|
|
76
|
+
{#if isFiltered}
|
|
77
|
+
<div class="table-header-filtered-icon">
|
|
78
|
+
<span class="material-icons-outlined">filter_alt</span>
|
|
79
|
+
</div>
|
|
80
|
+
{/if}
|
|
64
81
|
</button>
|
|
65
82
|
|
|
66
83
|
{#if enableColumnSearch && !hideColumnFilter}
|
|
@@ -82,6 +99,7 @@
|
|
|
82
99
|
<style>
|
|
83
100
|
.container {
|
|
84
101
|
--color-primary: #472aff;
|
|
102
|
+
--color-primary-light: #eaecff;
|
|
85
103
|
--color-black: #000000;
|
|
86
104
|
--color-gray-100: #f3f4f6;
|
|
87
105
|
--color-gray-200: #e5e7eb;
|
|
@@ -112,8 +130,8 @@
|
|
|
112
130
|
background: var(--color-gray-100);
|
|
113
131
|
}
|
|
114
132
|
|
|
115
|
-
.table-header-cell--
|
|
116
|
-
background: var(--color-
|
|
133
|
+
.table-header-cell--active {
|
|
134
|
+
background: var(--color-primary-light);
|
|
117
135
|
color: var(--color-primary);
|
|
118
136
|
}
|
|
119
137
|
|
|
@@ -158,7 +176,18 @@
|
|
|
158
176
|
margin-top: 4px;
|
|
159
177
|
}
|
|
160
178
|
|
|
161
|
-
.table-header-sort-icon .
|
|
179
|
+
.table-header-sort-icon:has(+ .table-header-filtered-icon) {
|
|
180
|
+
right: 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.table-header-filtered-icon {
|
|
184
|
+
position: absolute;
|
|
185
|
+
right: -13px;
|
|
186
|
+
margin-top: 4px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.table-header-sort-icon .material-icons,
|
|
190
|
+
.material-icons-outlined {
|
|
162
191
|
font-size: var(--font-size-sm);
|
|
163
192
|
}
|
|
164
193
|
|
package/dist/Table/Table.svelte
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts" generics="T">
|
|
2
|
+
import { goto } from '$app/navigation';
|
|
3
|
+
import { page } from '$app/state';
|
|
2
4
|
import { type Snippet } from 'svelte';
|
|
3
5
|
|
|
4
6
|
import { Button, Search } from '../index.js';
|
|
@@ -18,17 +20,18 @@
|
|
|
18
20
|
type Updater,
|
|
19
21
|
type VisibilityState
|
|
20
22
|
} from './adapter/index.js';
|
|
23
|
+
import AdvancedFilter from './AdvancedFilter.svelte';
|
|
21
24
|
import Body from './Body.svelte';
|
|
22
25
|
import ColumnVisibilityDropdown from './ColumnVisibilityDropdown.svelte';
|
|
23
|
-
import { DEFAULT_MIN_PAGE_LIMIT, DEFAULT_PAGE_LIMIT } from './consts.js';
|
|
26
|
+
import { DEFAULT_MIN_PAGE_LIMIT, DEFAULT_PAGE_LIMIT, Operator } from './consts.js';
|
|
24
27
|
import { setPaginationTableContext } from './context.js';
|
|
25
28
|
import type { ExcelSetting } from './excel-setting.js';
|
|
26
29
|
import exportExcel from './excel.js';
|
|
27
30
|
import Footer from './Footer.svelte';
|
|
28
31
|
import Header from './Header.svelte';
|
|
29
32
|
import Skeleton from './Skeleton.svelte';
|
|
30
|
-
import type
|
|
31
|
-
import { createCheckedColumn } from './util.js';
|
|
33
|
+
import { type Filter, type Pagination } from './types.js';
|
|
34
|
+
import { createCheckedColumn, getPage, getPageLimit } from './util.js';
|
|
32
35
|
|
|
33
36
|
interface Props {
|
|
34
37
|
columns: ColumnDef<T, any>[];
|
|
@@ -40,6 +43,7 @@
|
|
|
40
43
|
enableGlobalSearch?: boolean;
|
|
41
44
|
enableColumnSearch?: boolean;
|
|
42
45
|
enableColumnVisibility?: boolean;
|
|
46
|
+
enableAdvancedFilter?: boolean;
|
|
43
47
|
manualPagination?: boolean;
|
|
44
48
|
pagination?: Pagination;
|
|
45
49
|
excelSetting?: ExcelSetting;
|
|
@@ -59,6 +63,7 @@
|
|
|
59
63
|
enableGlobalSearch = true,
|
|
60
64
|
enableColumnSearch = false,
|
|
61
65
|
enableColumnVisibility = true,
|
|
66
|
+
enableAdvancedFilter = false,
|
|
62
67
|
manualPagination = false,
|
|
63
68
|
pagination,
|
|
64
69
|
excelSetting = {
|
|
@@ -71,33 +76,6 @@
|
|
|
71
76
|
bulkActions
|
|
72
77
|
}: Props = $props();
|
|
73
78
|
|
|
74
|
-
const getInitialColumnVisibility = (): VisibilityState => {
|
|
75
|
-
const visibility: VisibilityState = {};
|
|
76
|
-
columns.forEach((column) => {
|
|
77
|
-
const columnId =
|
|
78
|
-
column.id ?? ('accessorKey' in column ? String(column.accessorKey) : undefined);
|
|
79
|
-
if (columnId && column.meta?.isVisible === false) {
|
|
80
|
-
visibility[columnId] = false;
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
return visibility;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const getInitialPaginationState = (): PaginationState => {
|
|
87
|
-
if (!manualPagination) {
|
|
88
|
-
return {
|
|
89
|
-
pageIndex: 0,
|
|
90
|
-
pageSize: minPageSize ? DEFAULT_MIN_PAGE_LIMIT : DEFAULT_PAGE_LIMIT
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const pageSize = DEFAULT_PAGE_LIMIT;
|
|
95
|
-
const pageIndex = 0;
|
|
96
|
-
|
|
97
|
-
return { pageIndex, pageSize };
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const tableManualPagination = manualPagination ? setPaginationTableContext() : undefined;
|
|
101
79
|
let globalFilter = $state<string>('');
|
|
102
80
|
let sorting = $state<SortingState>([]);
|
|
103
81
|
let columnFilters = $state<ColumnFiltersState>([]);
|
|
@@ -106,6 +84,8 @@
|
|
|
106
84
|
let columnVisibility = $state<VisibilityState>(getInitialColumnVisibility());
|
|
107
85
|
let paginationState = $state<PaginationState>(getInitialPaginationState());
|
|
108
86
|
let tableData = $derived(data);
|
|
87
|
+
const tableManualPagination = manualPagination ? setPaginationTableContext() : undefined;
|
|
88
|
+
const hasData = $derived(data.length > 0);
|
|
109
89
|
|
|
110
90
|
const table = createSvelteTable({
|
|
111
91
|
get data() {
|
|
@@ -172,7 +152,37 @@
|
|
|
172
152
|
sortDescFirst: false
|
|
173
153
|
});
|
|
174
154
|
|
|
175
|
-
const
|
|
155
|
+
const hasFilteredRows = $derived(table.getRowModel().rows.length > 0);
|
|
156
|
+
const selectedRows = $derived(table.getSelectedRowModel().rows);
|
|
157
|
+
|
|
158
|
+
function getInitialColumnVisibility(): VisibilityState {
|
|
159
|
+
const visibility: VisibilityState = {};
|
|
160
|
+
columns.forEach((column) => {
|
|
161
|
+
const columnId =
|
|
162
|
+
column.id ?? ('accessorKey' in column ? String(column.accessorKey) : undefined);
|
|
163
|
+
if (columnId && column.meta?.isVisible === false) {
|
|
164
|
+
visibility[columnId] = false;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
return visibility;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getInitialPaginationState(): PaginationState {
|
|
171
|
+
if (!manualPagination) {
|
|
172
|
+
return {
|
|
173
|
+
pageIndex: 0,
|
|
174
|
+
pageSize: minPageSize ? DEFAULT_MIN_PAGE_LIMIT : DEFAULT_PAGE_LIMIT
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const currentPage = getPage(page.url.searchParams);
|
|
179
|
+
const pageSize = getPageLimit(page.url.searchParams);
|
|
180
|
+
const pageIndex = Math.max(0, currentPage - 1);
|
|
181
|
+
|
|
182
|
+
return { pageIndex, pageSize };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function changeManualPagination(updatedPaginationState: PaginationState) {
|
|
176
186
|
const pageSizeChanged = updatedPaginationState.pageSize !== paginationState.pageSize;
|
|
177
187
|
|
|
178
188
|
if (pageSizeChanged) {
|
|
@@ -182,11 +192,30 @@
|
|
|
182
192
|
const currentPageNumber = updatedPaginationState.pageIndex + 1;
|
|
183
193
|
onpagechange?.(updatedPaginationState.pageSize, currentPageNumber);
|
|
184
194
|
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const changeFilters = (filters: Filter[]) => {
|
|
198
|
+
const searchParams = new URLSearchParams(page.url.searchParams);
|
|
199
|
+
searchParams.delete('filter');
|
|
200
|
+
|
|
201
|
+
filters.forEach((filter) => {
|
|
202
|
+
const filterValue = buildFilterValue(filter);
|
|
203
|
+
searchParams.append('filter', filterValue);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const url = buildUrl(searchParams);
|
|
207
|
+
goto(url);
|
|
185
208
|
};
|
|
186
209
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
210
|
+
const buildFilterValue = (filter: Filter) => {
|
|
211
|
+
const operation = filter.operation ? `${filter.operation}:` : '';
|
|
212
|
+
return `${operation}${filter.column}:${Operator.Eq}:${filter.value}`;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const buildUrl = (searchParams: URLSearchParams) => {
|
|
216
|
+
const queryString = searchParams.toString();
|
|
217
|
+
return queryString ? `${page.url.pathname}?${queryString}` : page.url.pathname;
|
|
218
|
+
};
|
|
190
219
|
|
|
191
220
|
$effect(() => {
|
|
192
221
|
if (manualPagination && pagination) {
|
|
@@ -216,8 +245,16 @@
|
|
|
216
245
|
]}
|
|
217
246
|
>
|
|
218
247
|
{#if enableGlobalSearch}
|
|
219
|
-
<div class="table-search">
|
|
220
|
-
<
|
|
248
|
+
<div class="table-search-container">
|
|
249
|
+
<div class="table-search">
|
|
250
|
+
<Search bind:value={globalFilter} />
|
|
251
|
+
</div>
|
|
252
|
+
{#if enableAdvancedFilter}
|
|
253
|
+
<AdvancedFilter
|
|
254
|
+
columns={table.getAllLeafColumns().filter((column) => column.getCanHide())}
|
|
255
|
+
onfilterschange={changeFilters}
|
|
256
|
+
/>
|
|
257
|
+
{/if}
|
|
221
258
|
</div>
|
|
222
259
|
{/if}
|
|
223
260
|
|
|
@@ -303,6 +340,12 @@
|
|
|
303
340
|
width: var(--search-width);
|
|
304
341
|
}
|
|
305
342
|
|
|
343
|
+
.table-search-container {
|
|
344
|
+
display: flex;
|
|
345
|
+
align-items: center;
|
|
346
|
+
gap: var(--spacing-md);
|
|
347
|
+
}
|
|
348
|
+
|
|
306
349
|
.table-actions {
|
|
307
350
|
display: flex;
|
|
308
351
|
gap: var(--spacing-md);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Snippet } from 'svelte';
|
|
2
2
|
import { type ColumnDef } from './adapter/index.js';
|
|
3
3
|
import type { ExcelSetting } from './excel-setting.js';
|
|
4
|
-
import type
|
|
4
|
+
import { type Pagination } from './types.js';
|
|
5
5
|
declare function $$render<T>(): {
|
|
6
6
|
props: {
|
|
7
7
|
columns: ColumnDef<T, any>[];
|
|
@@ -13,6 +13,7 @@ declare function $$render<T>(): {
|
|
|
13
13
|
enableGlobalSearch?: boolean;
|
|
14
14
|
enableColumnSearch?: boolean;
|
|
15
15
|
enableColumnVisibility?: boolean;
|
|
16
|
+
enableAdvancedFilter?: boolean;
|
|
16
17
|
manualPagination?: boolean;
|
|
17
18
|
pagination?: Pagination;
|
|
18
19
|
excelSetting?: ExcelSetting;
|
package/dist/Table/consts.d.ts
CHANGED
|
@@ -2,3 +2,13 @@ export declare const DEFAULT_PAGE = 1;
|
|
|
2
2
|
export declare const DEFAULT_PAGE_LIMIT = 10;
|
|
3
3
|
export declare const DEFAULT_MIN_PAGE_LIMIT = 5;
|
|
4
4
|
export declare const DEFAULT_ITEMS_PER_PAGE_OPTIONS: number[];
|
|
5
|
+
export declare const Operation: {
|
|
6
|
+
readonly And: "AND";
|
|
7
|
+
readonly Or: "OR";
|
|
8
|
+
readonly Not: "NOT";
|
|
9
|
+
};
|
|
10
|
+
export declare const Operator: {
|
|
11
|
+
readonly Eq: "eq";
|
|
12
|
+
};
|
|
13
|
+
export type Operation = typeof Operation[keyof typeof Operation];
|
|
14
|
+
export type Operator = typeof Operator[keyof typeof Operator];
|
package/dist/Table/consts.js
CHANGED
|
@@ -2,3 +2,11 @@ export const DEFAULT_PAGE = 1;
|
|
|
2
2
|
export const DEFAULT_PAGE_LIMIT = 10;
|
|
3
3
|
export const DEFAULT_MIN_PAGE_LIMIT = 5;
|
|
4
4
|
export const DEFAULT_ITEMS_PER_PAGE_OPTIONS = [10, 25, 50, 75, 100];
|
|
5
|
+
export const Operation = {
|
|
6
|
+
And: 'AND',
|
|
7
|
+
Or: 'OR',
|
|
8
|
+
Not: 'NOT'
|
|
9
|
+
};
|
|
10
|
+
export const Operator = {
|
|
11
|
+
Eq: 'eq'
|
|
12
|
+
};
|
package/dist/Table/index.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export * from './context.js';
|
|
|
4
4
|
export { ColumnFormat, type ExcelSetting } from './excel-setting.js';
|
|
5
5
|
export * from './excel.js';
|
|
6
6
|
export { default as Table } from './Table.svelte';
|
|
7
|
-
export
|
|
7
|
+
export * from './types.js';
|
|
8
|
+
export { createActionsColumn, createStaticTable, getPage, getPageLimit, sanitizeFilters } from './util.js';
|
package/dist/Table/index.js
CHANGED
|
@@ -4,4 +4,5 @@ export * from './context.js';
|
|
|
4
4
|
export { ColumnFormat } from './excel-setting.js';
|
|
5
5
|
export * from './excel.js';
|
|
6
6
|
export { default as Table } from './Table.svelte';
|
|
7
|
-
export
|
|
7
|
+
export * from './types.js';
|
|
8
|
+
export { createActionsColumn, createStaticTable, getPage, getPageLimit, sanitizeFilters } from './util.js';
|
package/dist/Table/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import { Operation } from "./consts.js";
|
package/dist/Table/util.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type ColumnDef, type Row } from './adapter/index.js';
|
|
2
|
-
import type { Action } from './types.js';
|
|
2
|
+
import type { Action, Filter } from './types.js';
|
|
3
3
|
export declare const createCheckedColumn: <T>() => import("./adapter/index.js").DisplayColumnDef<T, unknown>;
|
|
4
4
|
export declare const createActionsColumn: <T>(getActions: (row: Row<T>) => Action[]) => import("./adapter/index.js").DisplayColumnDef<T, unknown>;
|
|
5
5
|
export declare const createStaticTable: <TData>(columns: ColumnDef<TData, any>[], data: TData[]) => import("./adapter/index.js").Table<TData>;
|
|
6
|
+
export declare const getPageLimit: (urlSearchParams: URLSearchParams) => number;
|
|
7
|
+
export declare const getPage: (urlSearchParams: URLSearchParams) => number;
|
|
8
|
+
export declare const sanitizeFilters: (filterParams: string[]) => Filter[];
|
package/dist/Table/util.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import ActionsColumn from './ActionsColumn.svelte';
|
|
2
2
|
import RowCheckBox from './RowCheckBox.svelte';
|
|
3
3
|
import { createColumnHelper, createTable, getCoreRowModel, renderComponent } from './adapter/index.js';
|
|
4
|
+
import { DEFAULT_ITEMS_PER_PAGE_OPTIONS, DEFAULT_PAGE, DEFAULT_PAGE_LIMIT, Operation, Operator } from './consts.js';
|
|
4
5
|
export const createCheckedColumn = () => {
|
|
5
6
|
const columnHelper = createColumnHelper();
|
|
6
7
|
return columnHelper.display({
|
|
7
8
|
id: 'checked',
|
|
9
|
+
enableSorting: false,
|
|
10
|
+
enableColumnFilter: false,
|
|
11
|
+
enableGlobalFilter: false,
|
|
12
|
+
enableHiding: false,
|
|
8
13
|
header: ({ table }) => renderComponent(RowCheckBox, {
|
|
9
14
|
checked: table.getIsAllRowsSelected(),
|
|
10
15
|
indeterminate: table.getIsSomeRowsSelected(),
|
|
@@ -17,9 +22,10 @@ export const createCheckedColumn = () => {
|
|
|
17
22
|
onchange: row.getToggleSelectedHandler()
|
|
18
23
|
}),
|
|
19
24
|
meta: {
|
|
20
|
-
columnWidth: '40px'
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
columnWidth: '40px',
|
|
26
|
+
excludeFromExport: true,
|
|
27
|
+
hideColumnFilter: true
|
|
28
|
+
}
|
|
23
29
|
});
|
|
24
30
|
};
|
|
25
31
|
export const createActionsColumn = (getActions) => {
|
|
@@ -54,3 +60,45 @@ export const createStaticTable = (columns, data) => {
|
|
|
54
60
|
renderFallbackValue: null
|
|
55
61
|
});
|
|
56
62
|
};
|
|
63
|
+
export const getPageLimit = (urlSearchParams) => {
|
|
64
|
+
const limitParam = urlSearchParams.get('limit');
|
|
65
|
+
const limit = Number(limitParam);
|
|
66
|
+
const isLimitValid = !Number.isNaN(limit) && DEFAULT_ITEMS_PER_PAGE_OPTIONS.includes(limit);
|
|
67
|
+
return isLimitValid ? limit : DEFAULT_PAGE_LIMIT;
|
|
68
|
+
};
|
|
69
|
+
export const getPage = (urlSearchParams) => {
|
|
70
|
+
const pageParam = urlSearchParams.get('page');
|
|
71
|
+
const page = Number(pageParam);
|
|
72
|
+
const isPageValid = pageParam && !Number.isNaN(page) && page > 0;
|
|
73
|
+
return isPageValid ? page : DEFAULT_PAGE;
|
|
74
|
+
};
|
|
75
|
+
export const sanitizeFilters = (filterParams) => {
|
|
76
|
+
return filterParams
|
|
77
|
+
.filter((param) => param.trim())
|
|
78
|
+
.map(parseFilterParam)
|
|
79
|
+
.filter((filter) => filter !== null);
|
|
80
|
+
};
|
|
81
|
+
const parseFilterParam = (filterValue) => {
|
|
82
|
+
const parts = filterValue.split(':');
|
|
83
|
+
if (parts.length === 3)
|
|
84
|
+
return parseSimpleFilter(parts);
|
|
85
|
+
if (parts.length === 4)
|
|
86
|
+
return parseOperationFilter(parts);
|
|
87
|
+
return null;
|
|
88
|
+
};
|
|
89
|
+
const parseSimpleFilter = (parts) => {
|
|
90
|
+
const [column, operator, value] = parts;
|
|
91
|
+
return isValidFilter(column, operator, value) ? { column, value, operation: undefined } : null;
|
|
92
|
+
};
|
|
93
|
+
const parseOperationFilter = (parts) => {
|
|
94
|
+
const [operation, column, operator, value] = parts;
|
|
95
|
+
return isValidFilter(column, operator, value) && isValidOperation(operation)
|
|
96
|
+
? { column, value, operation }
|
|
97
|
+
: null;
|
|
98
|
+
};
|
|
99
|
+
const isValidFilter = (column, operator, value) => {
|
|
100
|
+
return operator === Operator.Eq && !!column && !!value;
|
|
101
|
+
};
|
|
102
|
+
const isValidOperation = (operation) => {
|
|
103
|
+
return Object.values(Operation).includes(operation);
|
|
104
|
+
};
|
package/dist/Tabs/Tabs.svelte
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
{#each tabs as tab}
|
|
43
43
|
{#if activeTab === tab.index}
|
|
44
44
|
<div
|
|
45
|
-
class=
|
|
45
|
+
class={['tabs-content', !tab.disablePadding && 'padding']}
|
|
46
46
|
id="panel-{tab.index}"
|
|
47
47
|
role="tabpanel"
|
|
48
48
|
aria-labelledby="tab-{tab.index}"
|
|
@@ -115,10 +115,13 @@
|
|
|
115
115
|
position: relative;
|
|
116
116
|
border-radius: 0px 0px 16px 16px;
|
|
117
117
|
overflow-wrap: break-word;
|
|
118
|
-
padding: 24px;
|
|
119
118
|
box-sizing: border-box;
|
|
120
119
|
min-height: 300px;
|
|
121
120
|
text-align: left;
|
|
122
121
|
font-size: 18px;
|
|
123
122
|
}
|
|
123
|
+
|
|
124
|
+
.padding {
|
|
125
|
+
padding: 24px;
|
|
126
|
+
}
|
|
124
127
|
</style>
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import type { Component } from 'svelte';
|
|
2
|
-
export type Tab<
|
|
1
|
+
import type { Component, ComponentProps } from 'svelte';
|
|
2
|
+
export type Tab<TComponent extends Component<any> = Component<any>> = BaseTab | TabComponent<TComponent>;
|
|
3
3
|
type BaseTab = {
|
|
4
4
|
index: number;
|
|
5
5
|
label: string;
|
|
6
6
|
hidden?: boolean;
|
|
7
|
+
disablePadding?: boolean;
|
|
7
8
|
};
|
|
8
|
-
type TabComponent<
|
|
9
|
-
component:
|
|
10
|
-
props
|
|
9
|
+
type TabComponent<TComponent extends Component<any>> = BaseTab & {
|
|
10
|
+
component: TComponent;
|
|
11
|
+
props: ComponentProps<TComponent>;
|
|
11
12
|
};
|
|
13
|
+
export declare const createTabComponent: <TComponent extends Component<any>>(tab: Tab<TComponent>) => Tab<TComponent>;
|
|
12
14
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -33,7 +33,8 @@ import Toaster from './Toast/Toast.svelte';
|
|
|
33
33
|
import Toggle from './Controls/Toggle/Toggle.svelte';
|
|
34
34
|
import Tooltip from './Tooltip/Tooltip.svelte';
|
|
35
35
|
import Waffle from './Waffle/Waffle.svelte';
|
|
36
|
-
import AttachFile from './AttachFile/AttachFile.svelte';
|
|
36
|
+
import AttachFile from './Controls/AttachFile/AttachFile.svelte';
|
|
37
|
+
import { createTabComponent } from './Tabs/tabsState.svelte.js';
|
|
37
38
|
import { addBreadcrumbsNameMap } from './Breadcrumbs/breadcrumbsState.svelte.js';
|
|
38
39
|
import { ChipType } from './Chips/chipsState.svelte.js';
|
|
39
40
|
import { ColumnType, ImageType } from './HighlightPanel/highlightPanelState.svelte.js';
|
|
@@ -53,4 +54,4 @@ import type { Tab } from './Tabs/tabsState.svelte.js';
|
|
|
53
54
|
import type { Toast } from './Toast/toastState.svelte';
|
|
54
55
|
import type { WaffleItem } from './Waffle/waffleState.svelte.js';
|
|
55
56
|
import type { NotificationProps } from './Notification/notificationState.svelte.js';
|
|
56
|
-
export { Accordion, AttachFile, Avatar, Breadcrumbs, Button, Card, Chips, DeleteConfirmationModal, ErrorPage, Footer, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, Input, Label, Link, Menu, Modal, Notification, Processing, ProgressPage, ProgressWizard, Search, Select, Sidebar, Spinner, Switcher, Tabs, TextArea, Toaster, Toggle, Tooltip, Waffle, addBreadcrumbsNameMap, addToast, getProgressWizardContext, setProgressWizardStepsContext, setStepValidity, getSubMenuItemsFromMenu, ChipType, ColumnType, ImageType, type BreadcrumbsNameMap, type HighlightPanelColumn, type HomeItem, type MainMenu, type MenuItem, type ModalProps, type ProgressWizardStep, type SelectOption, type SwitcherOption, type Tab, type Toast, type WaffleItem, type SubMenuItem, type NotificationProps };
|
|
57
|
+
export { Accordion, AttachFile, Avatar, Breadcrumbs, Button, Card, Chips, DeleteConfirmationModal, ErrorPage, Footer, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, Input, Label, Link, Menu, Modal, Notification, Processing, ProgressPage, ProgressWizard, Search, Select, Sidebar, Spinner, Switcher, Tabs, TextArea, Toaster, Toggle, Tooltip, Waffle, addBreadcrumbsNameMap, addToast, getProgressWizardContext, setProgressWizardStepsContext, setStepValidity, getSubMenuItemsFromMenu, createTabComponent, ChipType, ColumnType, ImageType, type BreadcrumbsNameMap, type HighlightPanelColumn, type HomeItem, type MainMenu, type MenuItem, type ModalProps, type ProgressWizardStep, type SelectOption, type SwitcherOption, type Tab, type Toast, type WaffleItem, type SubMenuItem, type NotificationProps };
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,8 @@ import Toaster from './Toast/Toast.svelte';
|
|
|
34
34
|
import Toggle from './Controls/Toggle/Toggle.svelte';
|
|
35
35
|
import Tooltip from './Tooltip/Tooltip.svelte';
|
|
36
36
|
import Waffle from './Waffle/Waffle.svelte';
|
|
37
|
-
import AttachFile from './AttachFile/AttachFile.svelte';
|
|
37
|
+
import AttachFile from './Controls/AttachFile/AttachFile.svelte';
|
|
38
|
+
import { createTabComponent } from './Tabs/tabsState.svelte.js';
|
|
38
39
|
// State, enums, and helpers
|
|
39
40
|
import { addBreadcrumbsNameMap } from './Breadcrumbs/breadcrumbsState.svelte.js';
|
|
40
41
|
import { ChipType } from './Chips/chipsState.svelte.js';
|
|
@@ -47,6 +48,6 @@ export {
|
|
|
47
48
|
// Components
|
|
48
49
|
Accordion, AttachFile, Avatar, Breadcrumbs, Button, Card, Chips, DeleteConfirmationModal, ErrorPage, Footer, Header, HeaderAccount, HeaderLoader, HeaderLogo, HighlightPanel, Home, Input, Label, Link, Menu, Modal, Notification, Processing, ProgressPage, ProgressWizard, Search, Select, Sidebar, Spinner, Switcher, Tabs, TextArea, Toaster, Toggle, Tooltip, Waffle,
|
|
49
50
|
// Functions and helpers
|
|
50
|
-
addBreadcrumbsNameMap, addToast, getProgressWizardContext, setProgressWizardStepsContext, setStepValidity, getSubMenuItemsFromMenu,
|
|
51
|
+
addBreadcrumbsNameMap, addToast, getProgressWizardContext, setProgressWizardStepsContext, setStepValidity, getSubMenuItemsFromMenu, createTabComponent,
|
|
51
52
|
// Enums
|
|
52
53
|
ChipType, ColumnType, ImageType };
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|