@shotleybuilder/svelte-gridlite-kit 0.1.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 (43) hide show
  1. package/README.md +260 -0
  2. package/dist/GridLite.svelte +1361 -0
  3. package/dist/GridLite.svelte.d.ts +42 -0
  4. package/dist/components/CellContextMenu.svelte +209 -0
  5. package/dist/components/CellContextMenu.svelte.d.ts +28 -0
  6. package/dist/components/ColumnMenu.svelte +234 -0
  7. package/dist/components/ColumnMenu.svelte.d.ts +29 -0
  8. package/dist/components/ColumnPicker.svelte +403 -0
  9. package/dist/components/ColumnPicker.svelte.d.ts +29 -0
  10. package/dist/components/FilterBar.svelte +390 -0
  11. package/dist/components/FilterBar.svelte.d.ts +38 -0
  12. package/dist/components/FilterCondition.svelte +643 -0
  13. package/dist/components/FilterCondition.svelte.d.ts +35 -0
  14. package/dist/components/GroupBar.svelte +463 -0
  15. package/dist/components/GroupBar.svelte.d.ts +33 -0
  16. package/dist/components/RowDetailModal.svelte +213 -0
  17. package/dist/components/RowDetailModal.svelte.d.ts +25 -0
  18. package/dist/components/SortBar.svelte +232 -0
  19. package/dist/components/SortBar.svelte.d.ts +30 -0
  20. package/dist/components/SortCondition.svelte +129 -0
  21. package/dist/components/SortCondition.svelte.d.ts +30 -0
  22. package/dist/index.d.ts +23 -0
  23. package/dist/index.js +29 -0
  24. package/dist/query/builder.d.ts +160 -0
  25. package/dist/query/builder.js +432 -0
  26. package/dist/query/live.d.ts +50 -0
  27. package/dist/query/live.js +118 -0
  28. package/dist/query/schema.d.ts +30 -0
  29. package/dist/query/schema.js +75 -0
  30. package/dist/state/migrations.d.ts +29 -0
  31. package/dist/state/migrations.js +113 -0
  32. package/dist/state/views.d.ts +54 -0
  33. package/dist/state/views.js +130 -0
  34. package/dist/styles/gridlite.css +966 -0
  35. package/dist/types.d.ts +164 -0
  36. package/dist/types.js +2 -0
  37. package/dist/utils/filters.d.ts +14 -0
  38. package/dist/utils/filters.js +49 -0
  39. package/dist/utils/formatters.d.ts +16 -0
  40. package/dist/utils/formatters.js +39 -0
  41. package/dist/utils/fuzzy.d.ts +47 -0
  42. package/dist/utils/fuzzy.js +142 -0
  43. package/package.json +76 -0
@@ -0,0 +1,42 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { GridConfig, GridFeatures, GridState, FilterCondition, FilterLogic, SortConfig, GroupConfig, ClassNameMap, RowHeight, ColumnSpacing, ToolbarLayout } from './types.js';
3
+ import { type PGliteWithLive } from './query/live.js';
4
+ declare const __propDef: {
5
+ props: {
6
+ /** PGLite instance with the live extension loaded */ db: PGliteWithLive;
7
+ /** Table name to query (mutually exclusive with `query`) */ table?: string | undefined;
8
+ /** Raw SQL query (mutually exclusive with `table`) */ query?: string | undefined;
9
+ /** Grid configuration */ config?: GridConfig | undefined;
10
+ /** Feature flags */ features?: GridFeatures;
11
+ /** Custom CSS class names */ classNames?: Partial<ClassNameMap>;
12
+ /** Row height variant */ rowHeight?: RowHeight;
13
+ /** Column spacing variant */ columnSpacing?: ColumnSpacing;
14
+ /** Toolbar layout preset */ toolbarLayout?: ToolbarLayout;
15
+ /** Row click callback */ onRowClick?: ((row: Record<string, unknown>) => void) | undefined;
16
+ /** State change callback */ onStateChange?: ((state: GridState) => void) | undefined;
17
+ setFilters?: (newFilters: FilterCondition[], logic?: FilterLogic) => void;
18
+ setSorting?: (newSorting: SortConfig[]) => void;
19
+ setGrouping?: (newGrouping: GroupConfig[]) => void;
20
+ setPage?: (newPage: number) => void;
21
+ setPageSize?: (newPageSize: number) => void;
22
+ setGlobalFilter?: (search: string) => void;
23
+ };
24
+ events: {
25
+ [evt: string]: CustomEvent<any>;
26
+ };
27
+ slots: {};
28
+ exports?: {} | undefined;
29
+ bindings?: string | undefined;
30
+ };
31
+ export type GridLiteProps = typeof __propDef.props;
32
+ export type GridLiteEvents = typeof __propDef.events;
33
+ export type GridLiteSlots = typeof __propDef.slots;
34
+ export default class GridLite extends SvelteComponent<GridLiteProps, GridLiteEvents, GridLiteSlots> {
35
+ get setFilters(): (newFilters: FilterCondition[], logic?: FilterLogic) => void;
36
+ get setSorting(): (newSorting: SortConfig[]) => void;
37
+ get setGrouping(): (newGrouping: GroupConfig[]) => void;
38
+ get setPage(): (newPage: number) => void;
39
+ get setPageSize(): (newPageSize: number) => void;
40
+ get setGlobalFilter(): (search: string) => void;
41
+ }
42
+ export {};
@@ -0,0 +1,209 @@
1
+ <script>import { onMount } from "svelte";
2
+ export let x;
3
+ export let y;
4
+ export let value;
5
+ export let columnName;
6
+ export let columnLabel;
7
+ export let isNumeric = false;
8
+ export let onFilterEquals;
9
+ export let onFilterNotEquals;
10
+ export let onFilterGreaterThan = void 0;
11
+ export let onFilterLessThan = void 0;
12
+ export let onClose;
13
+ let menuRef;
14
+ $: displayValue = (() => {
15
+ const str = String(value ?? "");
16
+ return str.length > 30 ? str.substring(0, 27) + "..." : str;
17
+ })();
18
+ $: adjustedPosition = (() => {
19
+ let adjustedX = x;
20
+ let adjustedY = y;
21
+ if (typeof window !== "undefined") {
22
+ const menuWidth = 220;
23
+ const menuHeight = isNumeric ? 180 : 100;
24
+ if (x + menuWidth > window.innerWidth) {
25
+ adjustedX = window.innerWidth - menuWidth - 10;
26
+ }
27
+ if (y + menuHeight > window.innerHeight) {
28
+ adjustedY = window.innerHeight - menuHeight - 10;
29
+ }
30
+ }
31
+ return { x: adjustedX, y: adjustedY };
32
+ })();
33
+ function handleFilterEquals() {
34
+ onFilterEquals(columnName, value);
35
+ onClose();
36
+ }
37
+ function handleFilterNotEquals() {
38
+ onFilterNotEquals(columnName, value);
39
+ onClose();
40
+ }
41
+ function handleFilterGreaterThan() {
42
+ onFilterGreaterThan?.(columnName, value);
43
+ onClose();
44
+ }
45
+ function handleFilterLessThan() {
46
+ onFilterLessThan?.(columnName, value);
47
+ onClose();
48
+ }
49
+ function handleKeydown(event) {
50
+ if (event.key === "Escape") {
51
+ onClose();
52
+ }
53
+ }
54
+ function handleClickOutside(event) {
55
+ if (menuRef && !menuRef.contains(event.target)) {
56
+ onClose();
57
+ }
58
+ }
59
+ onMount(() => {
60
+ document.addEventListener("click", handleClickOutside);
61
+ document.addEventListener("keydown", handleKeydown);
62
+ menuRef?.focus();
63
+ return () => {
64
+ document.removeEventListener("click", handleClickOutside);
65
+ document.removeEventListener("keydown", handleKeydown);
66
+ };
67
+ });
68
+ </script>
69
+
70
+ <div
71
+ bind:this={menuRef}
72
+ class="cell-context-menu"
73
+ style="top: {adjustedPosition.y}px; left: {adjustedPosition.x}px;"
74
+ role="menu"
75
+ tabindex="-1"
76
+ >
77
+ <div class="menu-header">
78
+ <span class="column-name">{columnLabel}</span>
79
+ </div>
80
+
81
+ <button class="menu-item" on:click={handleFilterEquals} role="menuitem">
82
+ <svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
83
+ <path
84
+ stroke-linecap="round"
85
+ stroke-linejoin="round"
86
+ stroke-width="2"
87
+ d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
88
+ />
89
+ </svg>
90
+ <span>Filter by "<strong>{displayValue}</strong>"</span>
91
+ </button>
92
+
93
+ <button class="menu-item" on:click={handleFilterNotEquals} role="menuitem">
94
+ <svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
95
+ <path
96
+ stroke-linecap="round"
97
+ stroke-linejoin="round"
98
+ stroke-width="2"
99
+ d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
100
+ />
101
+ </svg>
102
+ <span>Exclude "<strong>{displayValue}</strong>"</span>
103
+ </button>
104
+
105
+ {#if isNumeric}
106
+ <div class="menu-divider"></div>
107
+
108
+ <button class="menu-item" on:click={handleFilterGreaterThan} role="menuitem">
109
+ <svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
110
+ <path
111
+ stroke-linecap="round"
112
+ stroke-linejoin="round"
113
+ stroke-width="2"
114
+ d="M9 5l7 7-7 7"
115
+ />
116
+ </svg>
117
+ <span>Greater than <strong>{displayValue}</strong></span>
118
+ </button>
119
+
120
+ <button class="menu-item" on:click={handleFilterLessThan} role="menuitem">
121
+ <svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
122
+ <path
123
+ stroke-linecap="round"
124
+ stroke-linejoin="round"
125
+ stroke-width="2"
126
+ d="M15 19l-7-7 7-7"
127
+ />
128
+ </svg>
129
+ <span>Less than <strong>{displayValue}</strong></span>
130
+ </button>
131
+ {/if}
132
+ </div>
133
+
134
+ <style>
135
+ .cell-context-menu {
136
+ position: fixed;
137
+ z-index: 1000;
138
+ min-width: 200px;
139
+ background: white;
140
+ border: 1px solid #e5e7eb;
141
+ border-radius: 0.5rem;
142
+ box-shadow:
143
+ 0 10px 15px -3px rgba(0, 0, 0, 0.1),
144
+ 0 4px 6px -2px rgba(0, 0, 0, 0.05);
145
+ padding: 0.25rem 0;
146
+ outline: none;
147
+ }
148
+
149
+ .menu-header {
150
+ padding: 0.5rem 0.75rem;
151
+ border-bottom: 1px solid #e5e7eb;
152
+ margin-bottom: 0.25rem;
153
+ }
154
+
155
+ .column-name {
156
+ font-size: 0.75rem;
157
+ font-weight: 600;
158
+ color: #6b7280;
159
+ text-transform: uppercase;
160
+ letter-spacing: 0.025em;
161
+ }
162
+
163
+ .menu-item {
164
+ display: flex;
165
+ align-items: center;
166
+ gap: 0.5rem;
167
+ width: 100%;
168
+ padding: 0.5rem 0.75rem;
169
+ font-size: 0.875rem;
170
+ text-align: left;
171
+ background: none;
172
+ border: none;
173
+ cursor: pointer;
174
+ transition: background-color 0.1s;
175
+ }
176
+
177
+ .menu-item:hover {
178
+ background: #f3f4f6;
179
+ }
180
+
181
+ .menu-item:focus {
182
+ outline: none;
183
+ background: #e5e7eb;
184
+ }
185
+
186
+ .menu-item span {
187
+ flex: 1;
188
+ overflow: hidden;
189
+ text-overflow: ellipsis;
190
+ white-space: nowrap;
191
+ }
192
+
193
+ .menu-item strong {
194
+ color: #1f2937;
195
+ }
196
+
197
+ .menu-icon {
198
+ width: 1rem;
199
+ height: 1rem;
200
+ color: #6b7280;
201
+ flex-shrink: 0;
202
+ }
203
+
204
+ .menu-divider {
205
+ height: 1px;
206
+ background: #e5e7eb;
207
+ margin: 0.25rem 0;
208
+ }
209
+ </style>
@@ -0,0 +1,28 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ x: number;
5
+ y: number;
6
+ value: unknown;
7
+ columnName: string;
8
+ columnLabel: string;
9
+ isNumeric?: boolean;
10
+ onFilterEquals: (columnName: string, value: unknown) => void;
11
+ onFilterNotEquals: (columnName: string, value: unknown) => void;
12
+ onFilterGreaterThan?: ((columnName: string, value: unknown) => void) | undefined;
13
+ onFilterLessThan?: ((columnName: string, value: unknown) => void) | undefined;
14
+ onClose: () => void;
15
+ };
16
+ events: {
17
+ [evt: string]: CustomEvent<any>;
18
+ };
19
+ slots: {};
20
+ exports?: {} | undefined;
21
+ bindings?: string | undefined;
22
+ };
23
+ export type CellContextMenuProps = typeof __propDef.props;
24
+ export type CellContextMenuEvents = typeof __propDef.events;
25
+ export type CellContextMenuSlots = typeof __propDef.slots;
26
+ export default class CellContextMenu extends SvelteComponent<CellContextMenuProps, CellContextMenuEvents, CellContextMenuSlots> {
27
+ }
28
+ export {};
@@ -0,0 +1,234 @@
1
+ <script>import { onDestroy } from "svelte";
2
+ export let columnName;
3
+ export let isOpen = false;
4
+ export let sorting = [];
5
+ export let canSort = true;
6
+ export let canFilter = true;
7
+ export let canGroup = true;
8
+ export let onSort;
9
+ export let onFilter;
10
+ export let onGroup;
11
+ export let onHide;
12
+ export let onClose;
13
+ let menuElement;
14
+ $: currentSort = (() => {
15
+ const s = sorting.find((s2) => s2.column === columnName);
16
+ return s?.direction ?? null;
17
+ })();
18
+ function handleSortAsc() {
19
+ onSort(columnName, "asc");
20
+ onClose();
21
+ }
22
+ function handleSortDesc() {
23
+ onSort(columnName, "desc");
24
+ onClose();
25
+ }
26
+ function handleFilter() {
27
+ onFilter(columnName);
28
+ onClose();
29
+ }
30
+ function handleGroup() {
31
+ onGroup(columnName);
32
+ onClose();
33
+ }
34
+ function handleHideColumn() {
35
+ onHide(columnName);
36
+ onClose();
37
+ }
38
+ function handleClickOutside(event) {
39
+ if (menuElement && !menuElement.contains(event.target)) {
40
+ onClose();
41
+ }
42
+ }
43
+ function handleKeydown(event) {
44
+ if (event.key === "Escape") {
45
+ onClose();
46
+ }
47
+ }
48
+ $: if (typeof document !== "undefined") {
49
+ if (isOpen) {
50
+ setTimeout(() => {
51
+ document.addEventListener("mousedown", handleClickOutside);
52
+ document.addEventListener("keydown", handleKeydown);
53
+ }, 0);
54
+ } else {
55
+ document.removeEventListener("mousedown", handleClickOutside);
56
+ document.removeEventListener("keydown", handleKeydown);
57
+ }
58
+ }
59
+ onDestroy(() => {
60
+ if (typeof document !== "undefined") {
61
+ document.removeEventListener("mousedown", handleClickOutside);
62
+ document.removeEventListener("keydown", handleKeydown);
63
+ }
64
+ });
65
+ </script>
66
+
67
+ {#if isOpen}
68
+ <div class="column-menu" bind:this={menuElement}>
69
+ {#if canSort}
70
+ <button
71
+ class="menu-item"
72
+ class:active={currentSort === 'asc'}
73
+ on:click={handleSortAsc}
74
+ >
75
+ <svg class="menu-icon" width="16" height="16" viewBox="0 0 16 16" fill="none">
76
+ <path
77
+ d="M8 12V4M8 4L5 7M8 4L11 7"
78
+ stroke="currentColor"
79
+ stroke-width="1.5"
80
+ stroke-linecap="round"
81
+ stroke-linejoin="round"
82
+ />
83
+ </svg>
84
+ <span>Sort A → Z</span>
85
+ {#if currentSort === 'asc'}
86
+ <span class="check-icon">✓</span>
87
+ {/if}
88
+ </button>
89
+
90
+ <button
91
+ class="menu-item"
92
+ class:active={currentSort === 'desc'}
93
+ on:click={handleSortDesc}
94
+ >
95
+ <svg class="menu-icon" width="16" height="16" viewBox="0 0 16 16" fill="none">
96
+ <path
97
+ d="M8 4V12M8 12L11 9M8 12L5 9"
98
+ stroke="currentColor"
99
+ stroke-width="1.5"
100
+ stroke-linecap="round"
101
+ stroke-linejoin="round"
102
+ />
103
+ </svg>
104
+ <span>Sort Z → A</span>
105
+ {#if currentSort === 'desc'}
106
+ <span class="check-icon">✓</span>
107
+ {/if}
108
+ </button>
109
+
110
+ <div class="menu-divider"></div>
111
+ {/if}
112
+
113
+ {#if canFilter}
114
+ <button class="menu-item" on:click={handleFilter}>
115
+ <svg class="menu-icon" width="16" height="16" viewBox="0 0 16 16" fill="none">
116
+ <path
117
+ d="M2 3h12M4 6h8M6 9h4M7 12h2"
118
+ stroke="currentColor"
119
+ stroke-width="1.5"
120
+ stroke-linecap="round"
121
+ stroke-linejoin="round"
122
+ />
123
+ </svg>
124
+ <span>Filter by this field</span>
125
+ </button>
126
+
127
+ <div class="menu-divider"></div>
128
+ {/if}
129
+
130
+ {#if canGroup}
131
+ <button class="menu-item" on:click={handleGroup}>
132
+ <svg class="menu-icon" width="16" height="16" viewBox="0 0 16 16" fill="none">
133
+ <path
134
+ d="M2 4h12M4 8h8M6 12h4"
135
+ stroke="currentColor"
136
+ stroke-width="1.5"
137
+ stroke-linecap="round"
138
+ stroke-linejoin="round"
139
+ />
140
+ </svg>
141
+ <span>Group by this field</span>
142
+ </button>
143
+
144
+ <div class="menu-divider"></div>
145
+ {/if}
146
+
147
+ <button class="menu-item" on:click={handleHideColumn}>
148
+ <svg class="menu-icon" width="16" height="16" viewBox="0 0 16 16" fill="none">
149
+ <path
150
+ 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"
151
+ stroke="currentColor"
152
+ stroke-width="1.5"
153
+ stroke-linecap="round"
154
+ stroke-linejoin="round"
155
+ />
156
+ <circle cx="8" cy="8" r="2" stroke="currentColor" stroke-width="1.5" />
157
+ <line x1="2" y1="2" x2="14" y2="14" stroke="currentColor" stroke-width="1.5" />
158
+ </svg>
159
+ <span>Hide field</span>
160
+ </button>
161
+ </div>
162
+ {/if}
163
+
164
+ <style>
165
+ .column-menu {
166
+ position: absolute;
167
+ top: 100%;
168
+ right: 0;
169
+ margin-top: 0.25rem;
170
+ min-width: 12rem;
171
+ background: white;
172
+ border: 1px solid rgba(0, 0, 0, 0.1);
173
+ border-radius: 0.375rem;
174
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
175
+ padding: 0.25rem;
176
+ z-index: 50;
177
+ }
178
+
179
+ .menu-item {
180
+ display: flex;
181
+ align-items: center;
182
+ gap: 0.75rem;
183
+ width: 100%;
184
+ padding: 0.5rem 0.75rem;
185
+ border: none;
186
+ background: transparent;
187
+ text-align: left;
188
+ font-size: 0.875rem;
189
+ cursor: pointer;
190
+ border-radius: 0.25rem;
191
+ transition: background-color 0.15s;
192
+ color: #374151;
193
+ position: relative;
194
+ }
195
+
196
+ .menu-item:hover {
197
+ background-color: #f3f4f6;
198
+ }
199
+
200
+ .menu-item.active {
201
+ background-color: #eff6ff;
202
+ color: #1e40af;
203
+ }
204
+
205
+ .menu-item.active:hover {
206
+ background-color: #dbeafe;
207
+ }
208
+
209
+ .menu-icon {
210
+ flex-shrink: 0;
211
+ color: #6b7280;
212
+ }
213
+
214
+ .menu-item:hover .menu-icon {
215
+ color: #374151;
216
+ }
217
+
218
+ .menu-item.active .menu-icon {
219
+ color: #3b82f6;
220
+ }
221
+
222
+ .check-icon {
223
+ margin-left: auto;
224
+ color: #3b82f6;
225
+ font-weight: bold;
226
+ font-size: 1rem;
227
+ }
228
+
229
+ .menu-divider {
230
+ height: 1px;
231
+ background-color: #e5e7eb;
232
+ margin: 0.25rem 0;
233
+ }
234
+ </style>
@@ -0,0 +1,29 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { SortConfig } from '../types.js';
3
+ declare const __propDef: {
4
+ props: {
5
+ columnName: string;
6
+ isOpen?: boolean;
7
+ sorting?: SortConfig[];
8
+ canSort?: boolean;
9
+ canFilter?: boolean;
10
+ canGroup?: boolean;
11
+ onSort: (columnName: string, direction: "asc" | "desc") => void;
12
+ onFilter: (columnName: string) => void;
13
+ onGroup: (columnName: string) => void;
14
+ onHide: (columnName: string) => void;
15
+ onClose: () => void;
16
+ };
17
+ events: {
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 {};