@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.
Files changed (37) hide show
  1. package/dist/Card/Card.svelte +1 -1
  2. package/dist/{AttachFile → Controls/AttachFile}/AttachFile.svelte +39 -23
  3. package/dist/{AttachFile → Controls/AttachFile}/AttachFile.svelte.d.ts +2 -1
  4. package/dist/{AttachFile → Controls/AttachFile}/Warnings.svelte +1 -1
  5. package/dist/Controls/Input/Input.svelte +2 -4
  6. package/dist/Controls/Input/Input.svelte.d.ts +2 -4
  7. package/dist/Controls/Label/Label.svelte +3 -8
  8. package/dist/Controls/Label/Label.svelte.d.ts +1 -7
  9. package/dist/Controls/Label/labelState.svelte.d.ts +7 -0
  10. package/dist/Controls/Label/labelState.svelte.js +1 -0
  11. package/dist/Controls/Select/Select.svelte +2 -5
  12. package/dist/Controls/Select/Select.svelte.d.ts +2 -5
  13. package/dist/Controls/TextArea/TextArea.svelte +2 -4
  14. package/dist/Controls/TextArea/TextArea.svelte.d.ts +2 -4
  15. package/dist/Controls/Toggle/Toggle.svelte +3 -5
  16. package/dist/Controls/Toggle/Toggle.svelte.d.ts +2 -4
  17. package/dist/Table/AdvancedFilter.svelte +351 -0
  18. package/dist/Table/AdvancedFilter.svelte.d.ts +29 -0
  19. package/dist/Table/Header.svelte +34 -5
  20. package/dist/Table/Table.svelte +79 -36
  21. package/dist/Table/Table.svelte.d.ts +2 -1
  22. package/dist/Table/consts.d.ts +10 -0
  23. package/dist/Table/consts.js +8 -0
  24. package/dist/Table/index.d.ts +2 -1
  25. package/dist/Table/index.js +2 -1
  26. package/dist/Table/types.js +1 -1
  27. package/dist/Table/util.d.ts +4 -1
  28. package/dist/Table/util.js +51 -3
  29. package/dist/Tabs/Tabs.svelte +5 -2
  30. package/dist/Tabs/tabsState.svelte.d.ts +7 -5
  31. package/dist/Tabs/tabsState.svelte.js +3 -1
  32. package/dist/index.d.ts +3 -2
  33. package/dist/index.js +3 -2
  34. package/package.json +1 -1
  35. /package/dist/{AttachFile → Controls/AttachFile}/FileManager.svelte +0 -0
  36. /package/dist/{AttachFile → Controls/AttachFile}/FileManager.svelte.d.ts +0 -0
  37. /package/dist/{AttachFile → Controls/AttachFile}/Warnings.svelte.d.ts +0 -0
@@ -9,7 +9,7 @@
9
9
  let { children, disablePadding = false }: CardProps = $props();
10
10
  </script>
11
11
 
12
- <div class="card" class:padding={!disablePadding}>
12
+ <div class={['card', !disablePadding && 'padding']}>
13
13
  {@render children?.()}
14
14
  </div>
15
15
 
@@ -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 = accept
34
- ? `Files of the following type are allowed: ${accept.replaceAll(',', ' & ')}.`
35
- : 'All file types are allowed.';
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
- extensionFilesMessage,
39
- 'Duplicate file names are not allowed.',
40
- ...externalFileValidationMessages
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
- <aside class="container">
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
- <h2 class="title">Upload file</h2>
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
- </aside>
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: 16px;
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: 14px;
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,4 +1,5 @@
1
- interface Props {
1
+ import type { LabelProps } from '../Label/labelState.svelte.js';
2
+ interface Props extends LabelProps {
2
3
  accept?: string;
3
4
  multiple?: boolean;
4
5
  externalFileValidationMessages?: string[];
@@ -24,7 +24,7 @@
24
24
 
25
25
  <style>
26
26
  .container {
27
- margin-top: 16px;
27
+ margin-top: 8px;
28
28
  font-size: 12px;
29
29
  line-height: 16px;
30
30
  }
@@ -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
- interface LabelProps {
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,7 @@
1
+ export interface LabelProps {
2
+ label?: string;
3
+ id?: string | null;
4
+ required?: boolean | null;
5
+ optional?: boolean;
6
+ infoTooltip?: string;
7
+ }
@@ -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
- interface SelectProps {
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
- interface TextAreaProps extends Omit<HTMLTextareaAttributes, 'value'> {
3
- label?: string;
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
- interface ToggleProps {
2
- id?: string;
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;
@@ -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
- let { headerGroups, enableColumnSearch }: Props = $props();
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, isSorted && 'table-header-cell--sorted']}
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--sorted {
116
- background: var(--color-gray-200);
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 .material-icons {
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
 
@@ -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 { Pagination } from './types.js';
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 changeManualPagination = (updatedPaginationState: PaginationState) => {
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 hasData = $derived(data.length > 0);
188
- const hasFilteredRows = $derived(table.getRowModel().rows.length > 0);
189
- const selectedRows = $derived(table.getSelectedRowModel().rows);
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
- <Search bind:value={globalFilter} />
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 { Pagination } from './types.js';
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;
@@ -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];
@@ -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
+ };
@@ -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 { createActionsColumn, createStaticTable } from './util.js';
7
+ export * from './types.js';
8
+ export { createActionsColumn, createStaticTable, getPage, getPageLimit, sanitizeFilters } from './util.js';
@@ -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 { createActionsColumn, createStaticTable } from './util.js';
7
+ export * from './types.js';
8
+ export { createActionsColumn, createStaticTable, getPage, getPageLimit, sanitizeFilters } from './util.js';
@@ -1 +1 @@
1
- export {};
1
+ import { Operation } from "./consts.js";
@@ -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[];
@@ -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
- enableSorting: false
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
+ };
@@ -42,7 +42,7 @@
42
42
  {#each tabs as tab}
43
43
  {#if activeTab === tab.index}
44
44
  <div
45
- class="tabs-content"
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<TProps extends Record<string, any> = Record<string, never>> = BaseTab | TabComponent<TProps>;
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<TProps extends Record<string, any>> = BaseTab & {
9
- component: Component<TProps>;
10
- props?: TProps;
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 {};
@@ -1 +1,3 @@
1
- export {};
1
+ export const createTabComponent = (tab) => {
2
+ return tab;
3
+ };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwareone/spi-sv5-library",
3
- "version": "1.10.5",
3
+ "version": "1.11.1",
4
4
  "description": "Svelte components",
5
5
  "keywords": [
6
6
  "svelte",