@softwareone/spi-sv5-library 1.10.5 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/Card/Card.svelte +1 -1
  2. package/dist/{AttachFile → Controls/AttachFile}/AttachFile.svelte +36 -21
  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 +385 -0
  18. package/dist/Table/AdvancedFilter.svelte.d.ts +29 -0
  19. package/dist/Table/Header.svelte +38 -5
  20. package/dist/Table/Table.svelte +78 -35
  21. package/dist/Table/Table.svelte.d.ts +2 -1
  22. package/dist/Table/consts.d.ts +6 -0
  23. package/dist/Table/consts.js +5 -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 +2 -0
  28. package/dist/Table/util.js +21 -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,12 +221,6 @@
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
226
  font-size: 16px;
@@ -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'] {
@@ -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,385 @@
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 { Operator } from './consts.js';
7
+ import { type Filter } from './types.js';
8
+
9
+ interface Props {
10
+ columns: Column<T, unknown>[];
11
+ onfilterschange?: (filters: Filter[]) => void;
12
+ }
13
+
14
+ let { columns, onfilterschange }: Props = $props();
15
+
16
+ let isOpen = $state(false);
17
+ let editingFilters = $state<Filter[]>([]);
18
+ const operatorOptions: Operator[] = Object.values(Operator);
19
+
20
+ const filters = $derived.by<Filter[]>(() => {
21
+ const filterParams = page.url.searchParams.getAll('filter');
22
+ return filterParams
23
+ .filter((param) => param.trim())
24
+ .map(parseFilterParam)
25
+ .filter((filter): filter is Filter => filter !== null);
26
+ });
27
+
28
+ const hasActiveFilters = $derived(filters.length > 0);
29
+ const columnOptions = $derived<SelectOption[]>(
30
+ columns
31
+ .filter((column) => !column.columnDef.meta?.hideColumnFilter)
32
+ .map((column) => ({
33
+ value: column.id,
34
+ label: (column.columnDef.header as string) || column.id
35
+ }))
36
+ );
37
+
38
+ const addFilterRow = () => {
39
+ editingFilters = [...editingFilters, { column: '', value: '', operator: Operator.And }];
40
+ };
41
+
42
+ const removeFilterRow = (index: number) => {
43
+ const remainingFilters = editingFilters.filter((_, filterIndex) => filterIndex !== index);
44
+
45
+ editingFilters =
46
+ remainingFilters.length === 1
47
+ ? [{ ...remainingFilters[0], operator: undefined }]
48
+ : remainingFilters;
49
+ };
50
+
51
+ const applyFilters = () => {
52
+ const validFilters = getValidFilters();
53
+ handleFilters(validFilters);
54
+ };
55
+
56
+ const clearAllFilters = () => {
57
+ editingFilters = [];
58
+ handleFilters(editingFilters);
59
+ };
60
+
61
+ const handleFilters = (filters: Filter[]) => {
62
+ onfilterschange?.(filters);
63
+ isOpen = false;
64
+ };
65
+
66
+ const toggleModal = () => {
67
+ isOpen = !isOpen;
68
+ isOpen ? initializeEditingFilters() : clearFilters();
69
+ };
70
+
71
+ const initializeEditingFilters = () => {
72
+ if (!editingFilters.length) {
73
+ editingFilters = filters.length
74
+ ? filters.map((filter) => ({ ...filter }))
75
+ : [{ column: '', value: '', operator: undefined }];
76
+ }
77
+ };
78
+
79
+ const clearFilters = () => {
80
+ const validFilters = getValidFilters();
81
+ if (validFilters.length === 0 && filters.length > 0) {
82
+ clearAllFilters();
83
+ }
84
+ };
85
+
86
+ const getValidFilters = () => {
87
+ return editingFilters.filter((filter) => filter.column && filter.value.trim());
88
+ };
89
+
90
+ const parseFilterParam = (filterValue: string) => {
91
+ const parts = filterValue.split(':');
92
+ if (parts.length === 3) return parseSimpleFilter(parts);
93
+ if (parts.length === 4) return parseOperatorFilter(parts);
94
+ return null;
95
+ };
96
+
97
+ const parseSimpleFilter = (parts: string[]): Filter | null => {
98
+ const [column, operator, value] = parts;
99
+ return isValidFilter(column, operator, value) ? { column, value, operator: undefined } : null;
100
+ };
101
+
102
+ const parseOperatorFilter = (parts: string[]): Filter | null => {
103
+ const [operator, column, eq, value] = parts;
104
+ return isValidFilter(column, eq, value) && isValidOperator(operator)
105
+ ? { column, value, operator }
106
+ : null;
107
+ };
108
+
109
+ const isValidFilter = (column: string, operator: string, value: string) => {
110
+ return operator === 'eq' && !!column && !!value;
111
+ };
112
+
113
+ const isValidOperator = (operator: string): operator is Operator => {
114
+ return operatorOptions.includes(operator as Operator);
115
+ };
116
+ </script>
117
+
118
+ <div class="filter-container">
119
+ <button
120
+ type="button"
121
+ class={['filter-button', hasActiveFilters && 'filter-button--active']}
122
+ onclick={toggleModal}
123
+ >
124
+ <span class="material-icons">filter_list</span>
125
+ {#if hasActiveFilters}
126
+ <strong>{filters.length} {filters.length === 1 ? 'Filter' : 'Filters'}</strong>
127
+ {:else}
128
+ <p>Filters</p>
129
+ {/if}
130
+ </button>
131
+
132
+ {#if isOpen}
133
+ <button
134
+ type="button"
135
+ class="filter-overlay"
136
+ onclick={toggleModal}
137
+ aria-label="Close filter modal"
138
+ ></button>
139
+
140
+ <div
141
+ class="filter-modal"
142
+ role="dialog"
143
+ tabindex="-1"
144
+ aria-modal="true"
145
+ aria-labelledby="filter-dialog-title"
146
+ >
147
+ <div class="filter-header">
148
+ <h2 id="filter-dialog-title" class="filter-title">Filters</h2>
149
+ <button type="button" onclick={toggleModal} class="filter-close-button" aria-label="Close">
150
+ <span class="material-icons">close</span>
151
+ </button>
152
+ </div>
153
+
154
+ <div class="filter-body">
155
+ {#each editingFilters as filter, index (index)}
156
+ <div class="filter-row">
157
+ {#if index > 0}
158
+ <div class="filter-operator">
159
+ <Select
160
+ options={operatorOptions}
161
+ disableValidationColor
162
+ bind:value={filter.operator}
163
+ hideClearButton
164
+ />
165
+ </div>
166
+ {:else}
167
+ <div class="filter-operator"></div>
168
+ {/if}
169
+
170
+ <div class="filter-column">
171
+ <Select
172
+ options={columnOptions}
173
+ disableValidationColor
174
+ bind:value={filter.column}
175
+ placeholder="Select column"
176
+ searchable
177
+ />
178
+ </div>
179
+
180
+ <div class="filter-value">
181
+ <Input
182
+ id="filter-value-{index}"
183
+ disableValidationColor
184
+ type="text"
185
+ bind:value={filter.value}
186
+ placeholder="Enter value"
187
+ />
188
+ </div>
189
+
190
+ <button
191
+ type="button"
192
+ onclick={() => removeFilterRow(index)}
193
+ class="filter-delete-button"
194
+ aria-label="Remove filter"
195
+ >
196
+ <span class="material-icons-outlined">delete</span>
197
+ </button>
198
+ </div>
199
+ {/each}
200
+
201
+ <div class="filter-actions">
202
+ <Button variant="outline-none" type="button" onclick={addFilterRow}>Add condition</Button>
203
+ {#if editingFilters.length}
204
+ <Button variant="outline-none" type="button" onclick={clearAllFilters}>
205
+ Reset filters
206
+ </Button>
207
+ {/if}
208
+ </div>
209
+ </div>
210
+
211
+ {#if editingFilters.length}
212
+ <div class="filter-footer">
213
+ <Button
214
+ onclick={applyFilters}
215
+ disabled={editingFilters.every((filter) => !filter.column || !filter.value.trim())}
216
+ >
217
+ Apply Filters
218
+ </Button>
219
+ </div>
220
+ {/if}
221
+ </div>
222
+ {/if}
223
+ </div>
224
+
225
+ <style>
226
+ .filter-container {
227
+ --color-primary: #472aff;
228
+ --color-primary-light: #eaecff;
229
+ --color-white: #fff;
230
+ --color-text-primary: #25282d;
231
+ --color-text-dark: #111827;
232
+ --color-text-secondary: #6b7280;
233
+ --color-text-hover: #374151;
234
+ --color-bg-hover: #f4f6f8;
235
+ --color-border: #e5e7eb;
236
+ --spacing-xs: 4px;
237
+ --spacing-sm: 8px;
238
+ --spacing-md: 12px;
239
+ --spacing-lg: 16px;
240
+ --spacing-xl: 24px;
241
+ --font-size-sm: 14px;
242
+ --font-size-md: 18px;
243
+ --font-size-icon: 20px;
244
+ --border-radius-sm: 8px;
245
+ --border-radius-circle: 50%;
246
+ --button-size: 40px;
247
+ --delete-button-size: 48px;
248
+ --modal-width: 640px;
249
+ --operator-column-width: 80px;
250
+ --transition-speed: 0.2s;
251
+
252
+ position: relative;
253
+ }
254
+
255
+ .filter-button {
256
+ display: flex;
257
+ align-items: center;
258
+ gap: var(--spacing-sm);
259
+ padding: var(--spacing-sm) var(--spacing-lg);
260
+ border: none;
261
+ border-radius: var(--border-radius-sm);
262
+ background: var(--color-white);
263
+ font-size: var(--font-size-sm);
264
+ color: var(--color-text-primary);
265
+ cursor: pointer;
266
+ transition: background-color var(--transition-speed) ease-in-out;
267
+ }
268
+
269
+ .filter-button:hover {
270
+ background: var(--color-bg-hover);
271
+ }
272
+
273
+ .filter-button--active {
274
+ background: var(--color-primary-light);
275
+ color: var(--color-primary);
276
+ }
277
+
278
+ .filter-overlay {
279
+ position: fixed;
280
+ inset: 0;
281
+ z-index: 9;
282
+ background: transparent;
283
+ border: none;
284
+ cursor: default;
285
+ }
286
+
287
+ .filter-modal {
288
+ position: absolute;
289
+ top: calc(100% + var(--spacing-sm));
290
+ right: 0;
291
+ z-index: 10;
292
+ width: var(--modal-width);
293
+ padding: var(--spacing-xl);
294
+ border: 1px solid var(--color-border);
295
+ border-radius: var(--border-radius-sm);
296
+ background: var(--color-white);
297
+ box-shadow:
298
+ 0 10px 15px -3px rgba(0, 0, 0, 0.1),
299
+ 0 4px 6px -2px rgba(0, 0, 0, 0.05);
300
+ }
301
+
302
+ .filter-header {
303
+ display: flex;
304
+ align-items: center;
305
+ justify-content: space-between;
306
+ margin-bottom: var(--spacing-xl);
307
+ }
308
+
309
+ .filter-title {
310
+ font-size: var(--font-size-md);
311
+ font-weight: 600;
312
+ color: var(--color-text-dark);
313
+ }
314
+
315
+ .filter-close-button {
316
+ display: flex;
317
+ align-items: center;
318
+ padding: var(--spacing-xs);
319
+ border: none;
320
+ background: transparent;
321
+ color: var(--color-text-secondary);
322
+ cursor: pointer;
323
+ transition: color var(--transition-speed) ease-in-out;
324
+ }
325
+
326
+ .filter-close-button:hover {
327
+ color: var(--color-text-hover);
328
+ }
329
+
330
+ .filter-body {
331
+ display: flex;
332
+ flex-direction: column;
333
+ gap: var(--spacing-lg);
334
+ }
335
+
336
+ .filter-row {
337
+ display: grid;
338
+ grid-template-columns: var(--operator-column-width) 1fr 1fr var(--delete-button-size);
339
+ align-items: end;
340
+ gap: var(--spacing-md);
341
+ }
342
+
343
+ .filter-operator,
344
+ .filter-column,
345
+ .filter-value {
346
+ display: flex;
347
+ }
348
+
349
+ .filter-delete-button {
350
+ display: flex;
351
+ align-items: center;
352
+ justify-content: center;
353
+ padding: var(--spacing-sm);
354
+ border: none;
355
+ background: transparent;
356
+ color: var(--color-text-primary);
357
+ border-radius: var(--border-radius-circle);
358
+ width: var(--button-size);
359
+ height: var(--button-size);
360
+ cursor: pointer;
361
+ transition: background-color var(--transition-speed) ease-in-out;
362
+ }
363
+
364
+ .filter-delete-button:hover {
365
+ background: var(--color-bg-hover);
366
+ }
367
+
368
+ .filter-actions {
369
+ display: flex;
370
+ align-items: center;
371
+ justify-content: space-between;
372
+ margin-top: var(--spacing-sm);
373
+ }
374
+
375
+ .filter-footer {
376
+ display: flex;
377
+ justify-content: flex-end;
378
+ margin-top: var(--spacing-lg);
379
+ }
380
+
381
+ .material-icons,
382
+ .material-icons-outlined {
383
+ font-size: var(--font-size-icon);
384
+ }
385
+ </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,4 +1,6 @@
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';
4
6
 
@@ -7,13 +9,24 @@
7
9
  enableColumnSearch: boolean;
8
10
  }
9
11
 
12
+ let { headerGroups, enableColumnSearch }: Props = $props();
13
+
10
14
  const getSortIcon: Record<string, string> = {
11
15
  asc: 'arrow_upward',
12
16
  desc: 'arrow_downward',
13
17
  false: 'swap_vert'
14
18
  };
15
19
 
16
- let { headerGroups, enableColumnSearch }: Props = $props();
20
+ const isColumnFiltered = (columnId: string): boolean => {
21
+ const filterParams = page.url.searchParams.getAll('filter');
22
+ return filterParams.some((filterValue) => {
23
+ const parts = filterValue.split(':');
24
+ return (
25
+ (parts.length === 3 && parts[0] === columnId) ||
26
+ (parts.length === 4 && parts[1] === columnId)
27
+ );
28
+ });
29
+ };
17
30
  </script>
18
31
 
19
32
  <thead class="container">
@@ -27,9 +40,11 @@
27
40
  {@const hideColumnFilter = header.column.columnDef.meta?.hideColumnFilter}
28
41
  {@const justifyContent = alignColumn ?? 'left'}
29
42
  {@const isSorted = header.column.getIsSorted().toString() !== 'false'}
43
+ {@const isFiltered = isColumnFiltered(header.column.id)}
44
+ {@const isActive = isSorted || isFiltered}
30
45
  <th
31
46
  colSpan={header.colSpan}
32
- class={['table-header-cell', className, isSorted && 'table-header-cell--sorted']}
47
+ class={['table-header-cell', className, isActive && 'table-header-cell--active']}
33
48
  style:width={columnWidth}
34
49
  style={columnStyle}
35
50
  >
@@ -61,6 +76,12 @@
61
76
  >
62
77
  </div>
63
78
  {/if}
79
+
80
+ {#if isFiltered}
81
+ <div class="table-header-filtered-icon">
82
+ <span class="material-icons-outlined">filter_alt</span>
83
+ </div>
84
+ {/if}
64
85
  </button>
65
86
 
66
87
  {#if enableColumnSearch && !hideColumnFilter}
@@ -82,6 +103,7 @@
82
103
  <style>
83
104
  .container {
84
105
  --color-primary: #472aff;
106
+ --color-primary-light: #eaecff;
85
107
  --color-black: #000000;
86
108
  --color-gray-100: #f3f4f6;
87
109
  --color-gray-200: #e5e7eb;
@@ -112,8 +134,8 @@
112
134
  background: var(--color-gray-100);
113
135
  }
114
136
 
115
- .table-header-cell--sorted {
116
- background: var(--color-gray-200);
137
+ .table-header-cell--active {
138
+ background: var(--color-primary-light);
117
139
  color: var(--color-primary);
118
140
  }
119
141
 
@@ -158,7 +180,18 @@
158
180
  margin-top: 4px;
159
181
  }
160
182
 
161
- .table-header-sort-icon .material-icons {
183
+ .table-header-sort-icon:has(+ .table-header-filtered-icon) {
184
+ right: 0;
185
+ }
186
+
187
+ .table-header-filtered-icon {
188
+ position: absolute;
189
+ right: -13px;
190
+ margin-top: 4px;
191
+ }
192
+
193
+ .table-header-sort-icon .material-icons,
194
+ .material-icons-outlined {
162
195
  font-size: var(--font-size-sm);
163
196
  }
164
197
 
@@ -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,6 +20,7 @@
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
26
  import { DEFAULT_MIN_PAGE_LIMIT, DEFAULT_PAGE_LIMIT } from './consts.js';
@@ -27,8 +30,8 @@
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 operator = filter.operator ? `${filter.operator}:` : '';
212
+ return `${operator}${filter.column}: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,9 @@ 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 Operator: {
6
+ readonly And: "AND";
7
+ readonly Or: "OR";
8
+ readonly Not: "NOT";
9
+ };
10
+ export type Operator = typeof Operator[keyof typeof Operator];
@@ -2,3 +2,8 @@ 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 Operator = {
6
+ And: 'AND',
7
+ Or: 'OR',
8
+ Not: 'NOT'
9
+ };
@@ -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 } 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 } from './util.js';
@@ -1 +1 @@
1
- export {};
1
+ import { Operator } from "./consts.js";
@@ -3,3 +3,5 @@ import type { Action } 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;
@@ -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 } 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,15 @@ 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
+ };
@@ -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 createTab: <TComponent extends Component<any>>(tab: Tab<TComponent>) => Tab<TComponent>;
12
14
  export {};
@@ -1 +1,3 @@
1
- export {};
1
+ export const createTab = (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 { createTab } 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, createTab, 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 { createTab } 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, createTab,
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.0",
4
4
  "description": "Svelte components",
5
5
  "keywords": [
6
6
  "svelte",