@shotleybuilder/svelte-table-kit 0.2.0 → 0.5.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.
@@ -0,0 +1,271 @@
1
+ <script>import { createEventDispatcher, onDestroy, onMount } from "svelte";
2
+ import { browser } from "$app/environment";
3
+ export let column;
4
+ export let isOpen = false;
5
+ export let canSort = true;
6
+ export let canFilter = true;
7
+ export let canGroup = true;
8
+ const dispatch = createEventDispatcher();
9
+ let menuElement;
10
+ $: currentSort = column.getIsSorted();
11
+ $: canSortColumn = canSort && column.getCanSort();
12
+ $: canFilterColumn = canFilter;
13
+ $: canGroupColumn = canGroup;
14
+ function handleSortAsc() {
15
+ dispatch("sort", { direction: "asc" });
16
+ dispatch("close");
17
+ }
18
+ function handleSortDesc() {
19
+ dispatch("sort", { direction: "desc" });
20
+ dispatch("close");
21
+ }
22
+ function handleFilter() {
23
+ dispatch("filter");
24
+ dispatch("close");
25
+ }
26
+ function handleGroup() {
27
+ dispatch("group");
28
+ dispatch("close");
29
+ }
30
+ function handleHideColumn() {
31
+ dispatch("hide");
32
+ dispatch("close");
33
+ }
34
+ function handleClickOutside(event) {
35
+ if (menuElement && !menuElement.contains(event.target)) {
36
+ dispatch("close");
37
+ }
38
+ }
39
+ function handleKeydown(event) {
40
+ if (event.key === "Escape") {
41
+ dispatch("close");
42
+ }
43
+ }
44
+ $: {
45
+ if (browser) {
46
+ if (isOpen) {
47
+ setTimeout(() => {
48
+ document.addEventListener("mousedown", handleClickOutside);
49
+ document.addEventListener("keydown", handleKeydown);
50
+ }, 0);
51
+ } else {
52
+ document.removeEventListener("mousedown", handleClickOutside);
53
+ document.removeEventListener("keydown", handleKeydown);
54
+ }
55
+ }
56
+ }
57
+ onDestroy(() => {
58
+ if (browser) {
59
+ document.removeEventListener("mousedown", handleClickOutside);
60
+ document.removeEventListener("keydown", handleKeydown);
61
+ }
62
+ });
63
+ </script>
64
+
65
+ {#if isOpen}
66
+ <div class="column-menu" bind:this={menuElement}>
67
+ <!-- Sort Actions -->
68
+ {#if canSortColumn}
69
+ <button
70
+ class="menu-item"
71
+ class:active={currentSort === 'asc'}
72
+ on:click={handleSortAsc}
73
+ >
74
+ <svg
75
+ class="menu-icon"
76
+ width="16"
77
+ height="16"
78
+ viewBox="0 0 16 16"
79
+ fill="none"
80
+ xmlns="http://www.w3.org/2000/svg"
81
+ >
82
+ <path
83
+ d="M8 12V4M8 4L5 7M8 4L11 7"
84
+ stroke="currentColor"
85
+ stroke-width="1.5"
86
+ stroke-linecap="round"
87
+ stroke-linejoin="round"
88
+ />
89
+ </svg>
90
+ <span>Sort A → Z</span>
91
+ {#if currentSort === 'asc'}
92
+ <span class="check-icon">✓</span>
93
+ {/if}
94
+ </button>
95
+
96
+ <button
97
+ class="menu-item"
98
+ class:active={currentSort === 'desc'}
99
+ on:click={handleSortDesc}
100
+ >
101
+ <svg
102
+ class="menu-icon"
103
+ width="16"
104
+ height="16"
105
+ viewBox="0 0 16 16"
106
+ fill="none"
107
+ xmlns="http://www.w3.org/2000/svg"
108
+ >
109
+ <path
110
+ d="M8 4V12M8 12L11 9M8 12L5 9"
111
+ stroke="currentColor"
112
+ stroke-width="1.5"
113
+ stroke-linecap="round"
114
+ stroke-linejoin="round"
115
+ />
116
+ </svg>
117
+ <span>Sort Z → A</span>
118
+ {#if currentSort === 'desc'}
119
+ <span class="check-icon">✓</span>
120
+ {/if}
121
+ </button>
122
+
123
+ <div class="menu-divider"></div>
124
+ {/if}
125
+
126
+ <!-- Filter by this field -->
127
+ {#if canFilterColumn}
128
+ <button class="menu-item" on:click={handleFilter}>
129
+ <svg
130
+ class="menu-icon"
131
+ width="16"
132
+ height="16"
133
+ viewBox="0 0 16 16"
134
+ fill="none"
135
+ xmlns="http://www.w3.org/2000/svg"
136
+ >
137
+ <path
138
+ d="M2 3h12M4 6h8M6 9h4M7 12h2"
139
+ stroke="currentColor"
140
+ stroke-width="1.5"
141
+ stroke-linecap="round"
142
+ stroke-linejoin="round"
143
+ />
144
+ </svg>
145
+ <span>Filter by this field</span>
146
+ </button>
147
+
148
+ <div class="menu-divider"></div>
149
+ {/if}
150
+
151
+ <!-- Group by this field -->
152
+ {#if canGroupColumn}
153
+ <button class="menu-item" on:click={handleGroup}>
154
+ <svg
155
+ class="menu-icon"
156
+ width="16"
157
+ height="16"
158
+ viewBox="0 0 16 16"
159
+ fill="none"
160
+ xmlns="http://www.w3.org/2000/svg"
161
+ >
162
+ <path
163
+ d="M2 4h12M4 8h8M6 12h4"
164
+ stroke="currentColor"
165
+ stroke-width="1.5"
166
+ stroke-linecap="round"
167
+ stroke-linejoin="round"
168
+ />
169
+ </svg>
170
+ <span>Group by this field</span>
171
+ </button>
172
+
173
+ <div class="menu-divider"></div>
174
+ {/if}
175
+
176
+ <!-- Hide Field -->
177
+ <button class="menu-item" on:click={handleHideColumn}>
178
+ <svg
179
+ class="menu-icon"
180
+ width="16"
181
+ height="16"
182
+ viewBox="0 0 16 16"
183
+ fill="none"
184
+ xmlns="http://www.w3.org/2000/svg"
185
+ >
186
+ <path
187
+ d="M2 8C2 8 4.5 3 8 3C11.5 3 14 8 14 8C14 8 11.5 13 8 13C4.5 13 2 8 2 8Z"
188
+ stroke="currentColor"
189
+ stroke-width="1.5"
190
+ stroke-linecap="round"
191
+ stroke-linejoin="round"
192
+ />
193
+ <circle cx="8" cy="8" r="2" stroke="currentColor" stroke-width="1.5" />
194
+ <line x1="2" y1="2" x2="14" y2="14" stroke="currentColor" stroke-width="1.5" />
195
+ </svg>
196
+ <span>Hide field</span>
197
+ </button>
198
+ </div>
199
+ {/if}
200
+
201
+ <style>
202
+ .column-menu {
203
+ position: absolute;
204
+ top: 100%;
205
+ right: 0;
206
+ margin-top: 0.25rem;
207
+ min-width: 12rem;
208
+ background: white;
209
+ border: 1px solid rgba(0, 0, 0, 0.1);
210
+ border-radius: 0.375rem;
211
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
212
+ padding: 0.25rem;
213
+ z-index: 50;
214
+ }
215
+
216
+ .menu-item {
217
+ display: flex;
218
+ align-items: center;
219
+ gap: 0.75rem;
220
+ width: 100%;
221
+ padding: 0.5rem 0.75rem;
222
+ border: none;
223
+ background: transparent;
224
+ text-align: left;
225
+ font-size: 0.875rem;
226
+ cursor: pointer;
227
+ border-radius: 0.25rem;
228
+ transition: background-color 0.15s;
229
+ color: #374151;
230
+ position: relative;
231
+ }
232
+
233
+ .menu-item:hover {
234
+ background-color: #f3f4f6;
235
+ }
236
+
237
+ .menu-item.active {
238
+ background-color: #eff6ff;
239
+ color: #1e40af;
240
+ }
241
+
242
+ .menu-item.active:hover {
243
+ background-color: #dbeafe;
244
+ }
245
+
246
+ .menu-icon {
247
+ flex-shrink: 0;
248
+ color: #6b7280;
249
+ }
250
+
251
+ .menu-item:hover .menu-icon {
252
+ color: #374151;
253
+ }
254
+
255
+ .menu-item.active .menu-icon {
256
+ color: #3b82f6;
257
+ }
258
+
259
+ .check-icon {
260
+ margin-left: auto;
261
+ color: #3b82f6;
262
+ font-weight: bold;
263
+ font-size: 1rem;
264
+ }
265
+
266
+ .menu-divider {
267
+ height: 1px;
268
+ background-color: #e5e7eb;
269
+ margin: 0.25rem 0;
270
+ }
271
+ </style>
@@ -0,0 +1,29 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { Column } from '@tanstack/svelte-table';
3
+ declare const __propDef: {
4
+ props: {
5
+ column: Column<any>;
6
+ isOpen?: boolean;
7
+ canSort?: boolean;
8
+ canFilter?: boolean;
9
+ canGroup?: boolean;
10
+ };
11
+ events: {
12
+ sort: CustomEvent<any>;
13
+ close: CustomEvent<any>;
14
+ filter: CustomEvent<any>;
15
+ group: CustomEvent<any>;
16
+ hide: CustomEvent<any>;
17
+ } & {
18
+ [evt: string]: CustomEvent<any>;
19
+ };
20
+ slots: {};
21
+ exports?: {} | undefined;
22
+ bindings?: string | undefined;
23
+ };
24
+ export type ColumnMenuProps = typeof __propDef.props;
25
+ export type ColumnMenuEvents = typeof __propDef.events;
26
+ export type ColumnMenuSlots = typeof __propDef.slots;
27
+ export default class ColumnMenu extends SvelteComponent<ColumnMenuProps, ColumnMenuEvents, ColumnMenuSlots> {
28
+ }
29
+ export {};
@@ -4,7 +4,8 @@ export let conditions = [];
4
4
  export let onConditionsChange;
5
5
  export let logic = "and";
6
6
  export let onLogicChange;
7
- let isExpanded = false;
7
+ export let isExpanded = false;
8
+ export let onExpandedChange = void 0;
8
9
  function generateId() {
9
10
  return `filter-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
10
11
  }
@@ -16,7 +17,13 @@ function addCondition() {
16
17
  value: ""
17
18
  };
18
19
  onConditionsChange([...conditions, newCondition]);
19
- isExpanded = true;
20
+ setExpanded(true);
21
+ }
22
+ function setExpanded(value) {
23
+ isExpanded = value;
24
+ if (onExpandedChange) {
25
+ onExpandedChange(value);
26
+ }
20
27
  }
21
28
  function updateCondition(index, updated) {
22
29
  const newConditions = [...conditions];
@@ -27,12 +34,12 @@ function removeCondition(index) {
27
34
  const newConditions = conditions.filter((_, i) => i !== index);
28
35
  onConditionsChange(newConditions);
29
36
  if (newConditions.length === 0) {
30
- isExpanded = false;
37
+ setExpanded(false);
31
38
  }
32
39
  }
33
40
  function clearAllConditions() {
34
41
  onConditionsChange([]);
35
- isExpanded = false;
42
+ setExpanded(false);
36
43
  }
37
44
  $: hasConditions = conditions.length > 0;
38
45
  $: filterCount = conditions.filter(
@@ -42,7 +49,7 @@ $: filterCount = conditions.filter(
42
49
 
43
50
  <div class="filter-bar">
44
51
  <!-- Compact Filter Button -->
45
- <button class="filter-toggle-btn" on:click={() => (isExpanded = !isExpanded)}>
52
+ <button class="filter-toggle-btn" on:click={() => setExpanded(!isExpanded)}>
46
53
  <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
47
54
  <path
48
55
  stroke-linecap="round"
@@ -8,6 +8,8 @@ declare const __propDef: {
8
8
  onConditionsChange: (conditions: FilterCondition[]) => void;
9
9
  logic?: FilterLogic;
10
10
  onLogicChange: (logic: FilterLogic) => void;
11
+ isExpanded?: boolean;
12
+ onExpandedChange?: ((expanded: boolean) => void) | undefined;
11
13
  };
12
14
  events: {
13
15
  [evt: string]: CustomEvent<any>;
@@ -1,12 +1,19 @@
1
1
  <script>export let columns;
2
2
  export let grouping = [];
3
3
  export let onGroupingChange;
4
+ export let isExpanded = false;
5
+ export let onExpandedChange = void 0;
4
6
  const MAX_LEVELS = 3;
5
- let isExpanded = false;
7
+ function setExpanded(value) {
8
+ isExpanded = value;
9
+ if (onExpandedChange) {
10
+ onExpandedChange(value);
11
+ }
12
+ }
6
13
  function addGroup() {
7
14
  if (grouping.length >= MAX_LEVELS) return;
8
15
  onGroupingChange([...grouping, ""]);
9
- isExpanded = true;
16
+ setExpanded(true);
10
17
  }
11
18
  function updateGroup(index, columnId) {
12
19
  const newGrouping = [...grouping];
@@ -17,12 +24,12 @@ function removeGroup(index) {
17
24
  const newGrouping = grouping.filter((_, i) => i !== index);
18
25
  onGroupingChange(newGrouping);
19
26
  if (newGrouping.length === 0) {
20
- isExpanded = false;
27
+ setExpanded(false);
21
28
  }
22
29
  }
23
30
  function clearAllGroups() {
24
31
  onGroupingChange([]);
25
- isExpanded = false;
32
+ setExpanded(false);
26
33
  }
27
34
  $: availableColumns = columns.filter((col) => {
28
35
  const columnId = col.accessorKey || col.id;
@@ -35,7 +42,7 @@ $: canAddMore = grouping.length < MAX_LEVELS;
35
42
 
36
43
  <div class="group-bar">
37
44
  <!-- Compact Group Button -->
38
- <button class="group-toggle-btn" on:click={() => (isExpanded = !isExpanded)}>
45
+ <button class="group-toggle-btn" on:click={() => setExpanded(!isExpanded)}>
39
46
  <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
40
47
  <path
41
48
  stroke-linecap="round"
@@ -5,6 +5,8 @@ declare const __propDef: {
5
5
  columns: ColumnDef<any>[];
6
6
  grouping?: string[];
7
7
  onGroupingChange: (grouping: string[]) => void;
8
+ isExpanded?: boolean;
9
+ onExpandedChange?: ((expanded: boolean) => void) | undefined;
8
10
  };
9
11
  events: {
10
12
  [evt: string]: CustomEvent<any>;
@@ -0,0 +1,239 @@
1
+ <script>import SortConditionComponent from "./SortCondition.svelte";
2
+ export let columns;
3
+ export let sorting = [];
4
+ export let onSortingChange;
5
+ let isExpanded = false;
6
+ function addSort() {
7
+ const newSort = { id: "", desc: false };
8
+ onSortingChange([...sorting, newSort]);
9
+ isExpanded = true;
10
+ }
11
+ function updateSort(index, columnId, desc) {
12
+ const newSorting = [...sorting];
13
+ newSorting[index] = { id: columnId, desc };
14
+ onSortingChange(newSorting);
15
+ }
16
+ function removeSort(index) {
17
+ const newSorting = sorting.filter((_, i) => i !== index);
18
+ onSortingChange(newSorting);
19
+ if (newSorting.length === 0) {
20
+ isExpanded = false;
21
+ }
22
+ }
23
+ function clearAllSorts() {
24
+ onSortingChange([]);
25
+ isExpanded = false;
26
+ }
27
+ function createUpdateHandler(index) {
28
+ return (columnId, desc) => updateSort(index, columnId, desc);
29
+ }
30
+ function createRemoveHandler(index) {
31
+ return () => removeSort(index);
32
+ }
33
+ $: availableColumns = columns.filter((col) => {
34
+ const columnId = col.accessorKey || col.id;
35
+ return columnId && col.enableSorting !== false;
36
+ });
37
+ $: availableColumnsForNew = availableColumns.filter((col) => {
38
+ const columnId = col.accessorKey || col.id;
39
+ return !sorting.some((s) => s.id === columnId);
40
+ });
41
+ $: hasSorts = sorting.length > 0;
42
+ $: validSortCount = sorting.filter((s) => s.id !== "").length;
43
+ $: canAddMore = availableColumnsForNew.length > 0;
44
+ </script>
45
+
46
+ <div class="sort-bar">
47
+ <!-- Compact Sort Button -->
48
+ <button class="sort-toggle-btn" on:click={() => (isExpanded = !isExpanded)}>
49
+ <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
50
+ <path
51
+ stroke-linecap="round"
52
+ stroke-linejoin="round"
53
+ stroke-width="2"
54
+ d="M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12"
55
+ />
56
+ </svg>
57
+ Sort
58
+ {#if validSortCount > 0}
59
+ <span class="sort-badge">{validSortCount}</span>
60
+ {/if}
61
+ <svg
62
+ class="chevron"
63
+ class:expanded={isExpanded}
64
+ fill="none"
65
+ stroke="currentColor"
66
+ viewBox="0 0 24 24"
67
+ >
68
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
69
+ </svg>
70
+ </button>
71
+
72
+ <!-- Expandable Sort Panel -->
73
+ {#if isExpanded}
74
+ <div class="sort-panel">
75
+ {#if hasSorts}
76
+ <div class="sort-header">
77
+ <span class="sort-label">Sort by</span>
78
+ <button class="clear-all-btn" on:click={clearAllSorts}> Clear all </button>
79
+ </div>
80
+
81
+ <div class="sort-levels">
82
+ {#each sorting as sort, index (index)}
83
+ <SortConditionComponent
84
+ columnId={sort.id}
85
+ desc={sort.desc}
86
+ {columns}
87
+ {sorting}
88
+ onUpdate={createUpdateHandler(index)}
89
+ onRemove={createRemoveHandler(index)}
90
+ />
91
+ {/each}
92
+ </div>
93
+ {/if}
94
+
95
+ <button class="add-sort-btn" on:click={addSort} disabled={!canAddMore}>
96
+ <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
97
+ <path
98
+ stroke-linecap="round"
99
+ stroke-linejoin="round"
100
+ stroke-width="2"
101
+ d="M12 6v6m0 0v6m0-6h6m-6 0H6"
102
+ />
103
+ </svg>
104
+ {hasSorts ? 'Add another sort' : 'Add a sort'}
105
+ </button>
106
+ </div>
107
+ {/if}
108
+ </div>
109
+
110
+ <style>
111
+ .sort-bar {
112
+ position: relative;
113
+ }
114
+
115
+ /* Compact Sort Toggle Button */
116
+ .sort-toggle-btn {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ gap: 0.5rem;
120
+ padding: 0.5rem 1rem;
121
+ font-size: 0.875rem;
122
+ font-weight: 500;
123
+ color: #374151;
124
+ background: white;
125
+ border: 1px solid #d1d5db;
126
+ border-radius: 0.375rem;
127
+ cursor: pointer;
128
+ transition: all 0.2s;
129
+ }
130
+
131
+ .sort-toggle-btn:hover {
132
+ background: #f9fafb;
133
+ border-color: #9ca3af;
134
+ }
135
+
136
+ .sort-badge {
137
+ display: inline-flex;
138
+ align-items: center;
139
+ justify-content: center;
140
+ min-width: 1.25rem;
141
+ height: 1.25rem;
142
+ padding: 0 0.375rem;
143
+ font-size: 0.75rem;
144
+ font-weight: 600;
145
+ color: white;
146
+ background: #f59e0b;
147
+ border-radius: 0.75rem;
148
+ }
149
+
150
+ .chevron {
151
+ width: 1rem;
152
+ height: 1rem;
153
+ transition: transform 0.2s;
154
+ }
155
+
156
+ .chevron.expanded {
157
+ transform: rotate(180deg);
158
+ }
159
+
160
+ /* Expandable Sort Panel */
161
+ .sort-panel {
162
+ position: absolute;
163
+ top: calc(100% + 0.5rem);
164
+ left: 0;
165
+ z-index: 20;
166
+ min-width: 400px;
167
+ padding: 1rem;
168
+ background: white;
169
+ border: 1px solid #e5e7eb;
170
+ border-radius: 0.5rem;
171
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
172
+ }
173
+
174
+ .sort-header {
175
+ display: flex;
176
+ justify-content: space-between;
177
+ align-items: center;
178
+ margin-bottom: 0.75rem;
179
+ }
180
+
181
+ .sort-label {
182
+ font-size: 0.875rem;
183
+ font-weight: 600;
184
+ color: #374151;
185
+ }
186
+
187
+ .clear-all-btn {
188
+ font-size: 0.75rem;
189
+ color: #6b7280;
190
+ background: none;
191
+ border: none;
192
+ cursor: pointer;
193
+ padding: 0.25rem 0.5rem;
194
+ border-radius: 0.25rem;
195
+ transition: all 0.2s;
196
+ }
197
+
198
+ .clear-all-btn:hover {
199
+ color: #dc2626;
200
+ background: #fee2e2;
201
+ }
202
+
203
+ .sort-levels {
204
+ display: flex;
205
+ flex-direction: column;
206
+ gap: 0.5rem;
207
+ margin-bottom: 0.75rem;
208
+ }
209
+
210
+ .add-sort-btn {
211
+ display: inline-flex;
212
+ align-items: center;
213
+ gap: 0.5rem;
214
+ padding: 0.5rem 0.75rem;
215
+ font-size: 0.875rem;
216
+ font-weight: 500;
217
+ color: #f59e0b;
218
+ background: white;
219
+ border: 1px dashed #f59e0b;
220
+ border-radius: 0.375rem;
221
+ cursor: pointer;
222
+ transition: all 0.2s;
223
+ }
224
+
225
+ .add-sort-btn:hover:not(:disabled) {
226
+ background: #fef3c7;
227
+ border-style: solid;
228
+ }
229
+
230
+ .add-sort-btn:disabled {
231
+ opacity: 0.5;
232
+ cursor: not-allowed;
233
+ }
234
+
235
+ .icon {
236
+ width: 1rem;
237
+ height: 1rem;
238
+ }
239
+ </style>
@@ -0,0 +1,22 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { ColumnDef } from '@tanstack/svelte-table';
3
+ import type { SortingState } from '@tanstack/svelte-table';
4
+ declare const __propDef: {
5
+ props: {
6
+ columns: ColumnDef<any>[];
7
+ sorting?: SortingState;
8
+ onSortingChange: (sorting: SortingState) => void;
9
+ };
10
+ events: {
11
+ [evt: string]: CustomEvent<any>;
12
+ };
13
+ slots: {};
14
+ exports?: {} | undefined;
15
+ bindings?: string | undefined;
16
+ };
17
+ export type SortBarProps = typeof __propDef.props;
18
+ export type SortBarEvents = typeof __propDef.events;
19
+ export type SortBarSlots = typeof __propDef.slots;
20
+ export default class SortBar extends SvelteComponent<SortBarProps, SortBarEvents, SortBarSlots> {
21
+ }
22
+ export {};