@smartnet360/svelte-grid 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.
- package/dist/components/Cell.svelte +249 -0
- package/dist/components/Cell.svelte.d.ts +39 -0
- package/dist/components/Grid.svelte +504 -0
- package/dist/components/Grid.svelte.d.ts +80 -0
- package/dist/components/GridBody.svelte +194 -0
- package/dist/components/GridBody.svelte.d.ts +49 -0
- package/dist/components/GridHeader.svelte +99 -0
- package/dist/components/GridHeader.svelte.d.ts +31 -0
- package/dist/components/GroupHeader.svelte +192 -0
- package/dist/components/GroupHeader.svelte.d.ts +35 -0
- package/dist/components/HeaderCell.svelte +623 -0
- package/dist/components/HeaderCell.svelte.d.ts +40 -0
- package/dist/components/Menu.svelte +215 -0
- package/dist/components/Menu.svelte.d.ts +33 -0
- package/dist/components/Popup.svelte +189 -0
- package/dist/components/Popup.svelte.d.ts +18 -0
- package/dist/components/Row.svelte +115 -0
- package/dist/components/Row.svelte.d.ts +36 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +20 -0
- package/dist/state/gridState.svelte.d.ts +299 -0
- package/dist/state/gridState.svelte.js +1025 -0
- package/dist/themes.d.ts +61 -0
- package/dist/themes.js +192 -0
- package/dist/types.d.ts +291 -0
- package/dist/types.js +4 -0
- package/package.json +66 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
<script lang="ts" generics="T">
|
|
2
|
+
import { getContext } from 'svelte';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { ColumnDefinition, GridEvents, SortDirection, MenuItemDefinition, MenuContext, PopupContext } from '../types.js';
|
|
5
|
+
import { GRID_CONTEXT_KEY, type GridStateManager } from '../state/gridState.svelte.js';
|
|
6
|
+
import Popup from './Popup.svelte';
|
|
7
|
+
import Menu from './Menu.svelte';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
column: ColumnDefinition<T>;
|
|
11
|
+
index: number;
|
|
12
|
+
columnWidth?: number;
|
|
13
|
+
onheaderclick?: GridEvents<T>['headerclick'];
|
|
14
|
+
headerCell?: Snippet<[ColumnDefinition<T>]>;
|
|
15
|
+
onsort?: (field: string, direction: SortDirection, multiSort: boolean) => void;
|
|
16
|
+
sortDirection?: SortDirection;
|
|
17
|
+
sortIndex?: number;
|
|
18
|
+
onresizestart?: (field: string, startX: number, startWidth: number) => void;
|
|
19
|
+
frozenPosition?: { side: 'left' | 'right'; offset: number };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
column,
|
|
24
|
+
index,
|
|
25
|
+
columnWidth,
|
|
26
|
+
onheaderclick,
|
|
27
|
+
headerCell,
|
|
28
|
+
onsort,
|
|
29
|
+
sortDirection = 'none',
|
|
30
|
+
sortIndex = -1,
|
|
31
|
+
onresizestart,
|
|
32
|
+
frozenPosition
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
// Get grid state for filtering
|
|
36
|
+
const gridState = getContext<GridStateManager<T>>(GRID_CONTEXT_KEY);
|
|
37
|
+
|
|
38
|
+
// ============================================
|
|
39
|
+
// Menu & Popup State
|
|
40
|
+
// ============================================
|
|
41
|
+
|
|
42
|
+
let menuOpen = $state(false);
|
|
43
|
+
let menuTargetRect = $state<DOMRect | null>(null);
|
|
44
|
+
let menuButtonRef: HTMLButtonElement | undefined = $state();
|
|
45
|
+
|
|
46
|
+
let popupOpen = $state(false);
|
|
47
|
+
let popupTargetRect = $state<DOMRect | null>(null);
|
|
48
|
+
let popupButtonRef: HTMLButtonElement | undefined = $state();
|
|
49
|
+
|
|
50
|
+
// Computed: Does this column have a header menu?
|
|
51
|
+
const hasHeaderMenu = $derived(
|
|
52
|
+
column.headerMenu !== undefined &&
|
|
53
|
+
(Array.isArray(column.headerMenu) ? column.headerMenu.length > 0 : true)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Computed: Does this column have a header popup?
|
|
57
|
+
const hasHeaderPopup = $derived(column.headerPopup !== undefined);
|
|
58
|
+
const showPopupIcon = $derived(column.headerPopupIcon !== undefined && column.headerPopupIcon !== false);
|
|
59
|
+
|
|
60
|
+
// Get menu items (resolve function if needed)
|
|
61
|
+
const menuItems = $derived.by((): MenuItemDefinition<T>[] => {
|
|
62
|
+
if (!column.headerMenu) return [];
|
|
63
|
+
if (typeof column.headerMenu === 'function') {
|
|
64
|
+
return column.headerMenu(column);
|
|
65
|
+
}
|
|
66
|
+
return column.headerMenu;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Menu context
|
|
70
|
+
const menuContext = $derived<MenuContext<T>>({
|
|
71
|
+
column,
|
|
72
|
+
closeMenu: () => {
|
|
73
|
+
menuOpen = false;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Popup context
|
|
78
|
+
const popupContext = $derived<PopupContext<T>>({
|
|
79
|
+
column,
|
|
80
|
+
closePopup: () => {
|
|
81
|
+
popupOpen = false;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Get popup icon
|
|
86
|
+
const popupIcon = $derived.by(() => {
|
|
87
|
+
if (typeof column.headerPopupIcon === 'string') {
|
|
88
|
+
return column.headerPopupIcon;
|
|
89
|
+
}
|
|
90
|
+
// Default filter icon
|
|
91
|
+
return '⚙';
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ============================================
|
|
95
|
+
// Menu/Popup Handlers
|
|
96
|
+
// ============================================
|
|
97
|
+
|
|
98
|
+
function handleMenuClick(event: MouseEvent) {
|
|
99
|
+
event.stopPropagation();
|
|
100
|
+
if (menuButtonRef) {
|
|
101
|
+
menuTargetRect = menuButtonRef.getBoundingClientRect();
|
|
102
|
+
}
|
|
103
|
+
menuOpen = !menuOpen;
|
|
104
|
+
popupOpen = false; // Close popup if open
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function handlePopupClick(event: MouseEvent) {
|
|
108
|
+
event.stopPropagation();
|
|
109
|
+
if (popupButtonRef) {
|
|
110
|
+
popupTargetRect = popupButtonRef.getBoundingClientRect();
|
|
111
|
+
}
|
|
112
|
+
popupOpen = !popupOpen;
|
|
113
|
+
menuOpen = false; // Close menu if open
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function closeMenu() {
|
|
117
|
+
menuOpen = false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function closePopup() {
|
|
121
|
+
popupOpen = false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================
|
|
125
|
+
// Popup Content Rendering
|
|
126
|
+
// ============================================
|
|
127
|
+
|
|
128
|
+
function getPopupContent(): string | null {
|
|
129
|
+
if (!column.headerPopup) return null;
|
|
130
|
+
if (typeof column.headerPopup === 'string') {
|
|
131
|
+
return column.headerPopup;
|
|
132
|
+
}
|
|
133
|
+
if (typeof column.headerPopup === 'function') {
|
|
134
|
+
const result = column.headerPopup(popupContext);
|
|
135
|
+
return typeof result === 'string' ? result : null;
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const isSnippetPopup = $derived(
|
|
141
|
+
column.headerPopup !== undefined &&
|
|
142
|
+
typeof column.headerPopup !== 'string' &&
|
|
143
|
+
typeof column.headerPopup !== 'function'
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// ============================================
|
|
147
|
+
// Computed
|
|
148
|
+
// ============================================
|
|
149
|
+
|
|
150
|
+
const cellStyle = $derived.by(() => {
|
|
151
|
+
const styles: string[] = [];
|
|
152
|
+
|
|
153
|
+
// Use columnWidth (from resize) if available, else column.width
|
|
154
|
+
const width = columnWidth ?? column.width;
|
|
155
|
+
|
|
156
|
+
if (width) {
|
|
157
|
+
const w = typeof width === 'number' ? `${width}px` : width;
|
|
158
|
+
styles.push(`width: ${w}`);
|
|
159
|
+
styles.push(`min-width: ${w}`);
|
|
160
|
+
} else {
|
|
161
|
+
styles.push('flex: 1');
|
|
162
|
+
styles.push('min-width: 100px');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (column.minWidth && !columnWidth) {
|
|
166
|
+
styles.push(`min-width: ${column.minWidth}px`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (column.maxWidth) {
|
|
170
|
+
styles.push(`max-width: ${column.maxWidth}px`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Frozen column positioning
|
|
174
|
+
if (frozenPosition) {
|
|
175
|
+
styles.push('position: sticky');
|
|
176
|
+
styles.push(`${frozenPosition.side}: ${frozenPosition.offset}px`);
|
|
177
|
+
styles.push('z-index: 2');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return styles.join('; ');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const isFrozen = $derived(!!frozenPosition);
|
|
184
|
+
|
|
185
|
+
const alignClass = $derived.by(() => {
|
|
186
|
+
if (column.hAlign === 'center') return 'sg-align-center';
|
|
187
|
+
if (column.hAlign === 'right') return 'sg-align-right';
|
|
188
|
+
return 'sg-align-left';
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const sortable = $derived(column.sortable === true);
|
|
192
|
+
const filterable = $derived(column.headerFilter === true);
|
|
193
|
+
|
|
194
|
+
const ariaSort = $derived.by(() => {
|
|
195
|
+
if (sortDirection === 'asc') return 'ascending';
|
|
196
|
+
if (sortDirection === 'desc') return 'descending';
|
|
197
|
+
return 'none';
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ============================================
|
|
201
|
+
// Event Handlers
|
|
202
|
+
// ============================================
|
|
203
|
+
|
|
204
|
+
function handleClick(event: MouseEvent) {
|
|
205
|
+
// Don't trigger sort if clicking on filter input
|
|
206
|
+
if ((event.target as HTMLElement).closest('.sg-header-filter')) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Don't trigger sort if clicking on menu/popup buttons
|
|
211
|
+
if ((event.target as HTMLElement).closest('.sg-header-menu-btn, .sg-header-popup-btn')) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Don't trigger sort if clicking near the resize handle area (right 12px)
|
|
216
|
+
if (onresizestart) {
|
|
217
|
+
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
218
|
+
const clickX = event.clientX - rect.left;
|
|
219
|
+
const rightEdgeZone = rect.width - 12; // 12px dead zone for resize
|
|
220
|
+
if (clickX > rightEdgeZone) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (sortable) {
|
|
226
|
+
const multiSort = event.shiftKey;
|
|
227
|
+
onsort?.(column.field, sortDirection, multiSort);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
onheaderclick?.(column, event);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
234
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
if (sortable) {
|
|
237
|
+
const multiSort = event.shiftKey;
|
|
238
|
+
onsort?.(column.field, sortDirection, multiSort);
|
|
239
|
+
}
|
|
240
|
+
onheaderclick?.(column, event as unknown as MouseEvent);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function handleFilterInput(event: Event) {
|
|
245
|
+
const value = (event.target as HTMLInputElement).value;
|
|
246
|
+
gridState?.setHeaderFilter(column.field, value);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function handleResizeMouseDown(event: MouseEvent) {
|
|
250
|
+
event.preventDefault();
|
|
251
|
+
event.stopPropagation();
|
|
252
|
+
const currentWidth = columnWidth ?? (typeof column.width === 'number' ? column.width : 150);
|
|
253
|
+
onresizestart?.(column.field, event.clientX, currentWidth);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function handleResizeDblClick(event: MouseEvent) {
|
|
257
|
+
event.preventDefault();
|
|
258
|
+
event.stopPropagation();
|
|
259
|
+
// Auto-fit column width on double-click
|
|
260
|
+
gridState?.autoFitColumn(column.field);
|
|
261
|
+
}
|
|
262
|
+
</script>
|
|
263
|
+
|
|
264
|
+
<div
|
|
265
|
+
class="sg-header-cell {alignClass} {column.cssClass ?? ''}"
|
|
266
|
+
class:sg-sortable={sortable}
|
|
267
|
+
class:sg-sorted={sortDirection !== 'none'}
|
|
268
|
+
class:sg-frozen={isFrozen}
|
|
269
|
+
class:sg-frozen-left={frozenPosition?.side === 'left'}
|
|
270
|
+
class:sg-frozen-right={frozenPosition?.side === 'right'}
|
|
271
|
+
style={cellStyle}
|
|
272
|
+
role="columnheader"
|
|
273
|
+
aria-colindex={index + 1}
|
|
274
|
+
aria-sort={ariaSort}
|
|
275
|
+
tabindex={sortable ? 0 : -1}
|
|
276
|
+
title={column.headerTooltip ?? column.title}
|
|
277
|
+
onclick={handleClick}
|
|
278
|
+
onkeydown={handleKeydown}
|
|
279
|
+
>
|
|
280
|
+
<div class="sg-header-cell-main">
|
|
281
|
+
{#if headerCell}
|
|
282
|
+
{@render headerCell(column)}
|
|
283
|
+
{:else}
|
|
284
|
+
<span class="sg-header-cell-content">
|
|
285
|
+
{column.title}
|
|
286
|
+
</span>
|
|
287
|
+
{/if}
|
|
288
|
+
|
|
289
|
+
<!-- Sort indicator -->
|
|
290
|
+
{#if sortable}
|
|
291
|
+
<span class="sg-sort-indicator" class:sg-sort-active={sortDirection !== 'none'}>
|
|
292
|
+
{#if sortDirection === 'asc'}
|
|
293
|
+
<svg class="sg-sort-icon" viewBox="0 0 24 24" fill="currentColor">
|
|
294
|
+
<path d="M7 14l5-5 5 5H7z" />
|
|
295
|
+
</svg>
|
|
296
|
+
{:else if sortDirection === 'desc'}
|
|
297
|
+
<svg class="sg-sort-icon" viewBox="0 0 24 24" fill="currentColor">
|
|
298
|
+
<path d="M7 10l5 5 5-5H7z" />
|
|
299
|
+
</svg>
|
|
300
|
+
{:else}
|
|
301
|
+
<svg class="sg-sort-icon sg-sort-icon-inactive" viewBox="0 0 24 24" fill="currentColor">
|
|
302
|
+
<path d="M7 10l5-5 5 5H7z M7 14l5 5 5-5H7z" />
|
|
303
|
+
</svg>
|
|
304
|
+
{/if}
|
|
305
|
+
{#if sortIndex >= 0}
|
|
306
|
+
<span class="sg-sort-index">{sortIndex + 1}</span>
|
|
307
|
+
{/if}
|
|
308
|
+
</span>
|
|
309
|
+
{/if}
|
|
310
|
+
|
|
311
|
+
<!-- Header popup icon -->
|
|
312
|
+
{#if showPopupIcon && hasHeaderPopup}
|
|
313
|
+
<button
|
|
314
|
+
bind:this={popupButtonRef}
|
|
315
|
+
class="sg-header-popup-btn"
|
|
316
|
+
class:sg-popup-open={popupOpen}
|
|
317
|
+
onclick={handlePopupClick}
|
|
318
|
+
title="Open popup"
|
|
319
|
+
aria-label="Open column popup"
|
|
320
|
+
aria-expanded={popupOpen}
|
|
321
|
+
>
|
|
322
|
+
{popupIcon}
|
|
323
|
+
</button>
|
|
324
|
+
{/if}
|
|
325
|
+
|
|
326
|
+
<!-- Header menu button -->
|
|
327
|
+
{#if hasHeaderMenu}
|
|
328
|
+
<button
|
|
329
|
+
bind:this={menuButtonRef}
|
|
330
|
+
class="sg-header-menu-btn"
|
|
331
|
+
class:sg-menu-open={menuOpen}
|
|
332
|
+
onclick={handleMenuClick}
|
|
333
|
+
title="Open menu"
|
|
334
|
+
aria-label="Open column menu"
|
|
335
|
+
aria-haspopup="menu"
|
|
336
|
+
aria-expanded={menuOpen}
|
|
337
|
+
>
|
|
338
|
+
<svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14">
|
|
339
|
+
<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" />
|
|
340
|
+
</svg>
|
|
341
|
+
</button>
|
|
342
|
+
{/if}
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<!-- Header menu popup -->
|
|
346
|
+
{#if hasHeaderMenu && menuOpen}
|
|
347
|
+
<Popup open={menuOpen} targetRect={menuTargetRect} position="bottom-end" onclose={closeMenu}>
|
|
348
|
+
<Menu items={menuItems} context={menuContext} />
|
|
349
|
+
</Popup>
|
|
350
|
+
{/if}
|
|
351
|
+
|
|
352
|
+
<!-- Header popup -->
|
|
353
|
+
{#if hasHeaderPopup && popupOpen}
|
|
354
|
+
<Popup open={popupOpen} targetRect={popupTargetRect} position="bottom-start" onclose={closePopup}>
|
|
355
|
+
<div class="sg-header-popup-content">
|
|
356
|
+
{#if isSnippetPopup && column.headerPopup}
|
|
357
|
+
{@render (column.headerPopup as import('svelte').Snippet<[PopupContext<T>]>)(popupContext)}
|
|
358
|
+
{:else}
|
|
359
|
+
{@html getPopupContent() ?? ''}
|
|
360
|
+
{/if}
|
|
361
|
+
</div>
|
|
362
|
+
</Popup>
|
|
363
|
+
{/if}
|
|
364
|
+
|
|
365
|
+
<!-- Header filter input -->
|
|
366
|
+
{#if filterable}
|
|
367
|
+
<div class="sg-header-filter">
|
|
368
|
+
<input
|
|
369
|
+
type="text"
|
|
370
|
+
class="sg-header-filter-input"
|
|
371
|
+
placeholder="Filter..."
|
|
372
|
+
value={gridState?.getHeaderFilter(column.field) ?? ''}
|
|
373
|
+
oninput={handleFilterInput}
|
|
374
|
+
onclick={(e) => e.stopPropagation()}
|
|
375
|
+
/>
|
|
376
|
+
</div>
|
|
377
|
+
{/if}
|
|
378
|
+
|
|
379
|
+
<!-- Resize handle -->
|
|
380
|
+
{#if onresizestart}
|
|
381
|
+
<div
|
|
382
|
+
class="sg-resize-handle"
|
|
383
|
+
onmousedown={handleResizeMouseDown}
|
|
384
|
+
ondblclick={handleResizeDblClick}
|
|
385
|
+
role="separator"
|
|
386
|
+
aria-orientation="vertical"
|
|
387
|
+
title="Drag to resize, double-click to auto-fit"
|
|
388
|
+
></div>
|
|
389
|
+
{/if}
|
|
390
|
+
</div>
|
|
391
|
+
|
|
392
|
+
<style>
|
|
393
|
+
.sg-header-cell {
|
|
394
|
+
display: flex;
|
|
395
|
+
flex-direction: column;
|
|
396
|
+
justify-content: center;
|
|
397
|
+
padding: var(--sg-cell-padding-y) var(--sg-cell-padding-x);
|
|
398
|
+
font-weight: var(--sg-header-font-weight, 600);
|
|
399
|
+
font-size: var(--sg-header-font-size, 12px);
|
|
400
|
+
letter-spacing: var(--sg-header-letter-spacing, 0.025em);
|
|
401
|
+
text-transform: uppercase;
|
|
402
|
+
color: var(--sg-header-color, #475569);
|
|
403
|
+
user-select: none;
|
|
404
|
+
overflow: hidden;
|
|
405
|
+
box-sizing: border-box;
|
|
406
|
+
position: relative;
|
|
407
|
+
transition: background-color var(--sg-transition-fast, 0.1s ease);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.sg-header-cell.sg-sortable {
|
|
411
|
+
cursor: pointer;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.sg-header-cell.sg-sortable:hover {
|
|
415
|
+
background-color: var(--sg-header-hover-bg, rgba(0, 0, 0, 0.04));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.sg-header-cell.sg-sorted {
|
|
419
|
+
background-color: var(--sg-header-sorted-bg, rgba(59, 130, 246, 0.08));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.sg-header-cell:focus {
|
|
423
|
+
outline: 2px solid var(--sg-primary-color, #3b82f6);
|
|
424
|
+
outline-offset: -2px;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.sg-header-cell-main {
|
|
428
|
+
display: flex;
|
|
429
|
+
align-items: center;
|
|
430
|
+
gap: 6px;
|
|
431
|
+
min-height: 24px;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.sg-header-cell-content {
|
|
435
|
+
overflow: hidden;
|
|
436
|
+
text-overflow: ellipsis;
|
|
437
|
+
white-space: nowrap;
|
|
438
|
+
flex: 1;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* Alignment */
|
|
442
|
+
.sg-align-left .sg-header-cell-main {
|
|
443
|
+
justify-content: flex-start;
|
|
444
|
+
text-align: left;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.sg-align-center .sg-header-cell-main {
|
|
448
|
+
justify-content: center;
|
|
449
|
+
text-align: center;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.sg-align-right .sg-header-cell-main {
|
|
453
|
+
justify-content: flex-end;
|
|
454
|
+
text-align: right;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/* Sort indicator */
|
|
458
|
+
.sg-sort-indicator {
|
|
459
|
+
display: flex;
|
|
460
|
+
align-items: center;
|
|
461
|
+
gap: 2px;
|
|
462
|
+
flex-shrink: 0;
|
|
463
|
+
opacity: 0.4;
|
|
464
|
+
transition: opacity 0.15s ease;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.sg-sortable:hover .sg-sort-indicator {
|
|
468
|
+
opacity: 0.7;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.sg-sort-indicator.sg-sort-active {
|
|
472
|
+
opacity: 1;
|
|
473
|
+
color: var(--sg-primary-color, #3b82f6);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.sg-sort-icon {
|
|
477
|
+
width: 16px;
|
|
478
|
+
height: 16px;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.sg-sort-icon-inactive {
|
|
482
|
+
opacity: 0.5;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.sg-sort-index {
|
|
486
|
+
font-size: 10px;
|
|
487
|
+
font-weight: 700;
|
|
488
|
+
color: var(--sg-primary-color, #3b82f6);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/* Header filter */
|
|
492
|
+
.sg-header-filter {
|
|
493
|
+
margin-top: 4px;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.sg-header-filter-input {
|
|
497
|
+
width: 100%;
|
|
498
|
+
padding: 4px 8px;
|
|
499
|
+
font-size: 12px;
|
|
500
|
+
border: 1px solid var(--sg-border-color, #e2e8f0);
|
|
501
|
+
border-radius: 4px;
|
|
502
|
+
background: var(--sg-filter-input-bg, #fff);
|
|
503
|
+
color: var(--sg-text-color, #1e293b);
|
|
504
|
+
box-sizing: border-box;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.sg-header-filter-input:focus {
|
|
508
|
+
outline: none;
|
|
509
|
+
border-color: var(--sg-primary-color, #3b82f6);
|
|
510
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.sg-header-filter-input::placeholder {
|
|
514
|
+
color: var(--sg-placeholder-color, #94a3b8);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/* Resize handle */
|
|
518
|
+
.sg-resize-handle {
|
|
519
|
+
position: absolute;
|
|
520
|
+
top: 0;
|
|
521
|
+
right: 0;
|
|
522
|
+
width: 10px;
|
|
523
|
+
height: 100%;
|
|
524
|
+
cursor: col-resize;
|
|
525
|
+
background: transparent;
|
|
526
|
+
transition: background-color 0.15s ease;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.sg-resize-handle:hover {
|
|
530
|
+
background-color: var(--sg-primary-color, #3b82f6);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.sg-resize-handle:active {
|
|
534
|
+
background-color: var(--sg-primary-color, #3b82f6);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/* Frozen column styles */
|
|
538
|
+
.sg-frozen {
|
|
539
|
+
background: var(--sg-header-bg, #f8fafc);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.sg-frozen-left {
|
|
543
|
+
border-right: 2px solid var(--sg-frozen-border-color, #cbd5e1);
|
|
544
|
+
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.sg-frozen-right {
|
|
548
|
+
border-left: 2px solid var(--sg-frozen-border-color, #cbd5e1);
|
|
549
|
+
box-shadow: -2px 0 4px rgba(0, 0, 0, 0.1);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/* Header menu button */
|
|
553
|
+
.sg-header-menu-btn {
|
|
554
|
+
display: flex;
|
|
555
|
+
align-items: center;
|
|
556
|
+
justify-content: center;
|
|
557
|
+
width: 20px;
|
|
558
|
+
height: 20px;
|
|
559
|
+
padding: 0;
|
|
560
|
+
border: none;
|
|
561
|
+
background: transparent;
|
|
562
|
+
color: var(--sg-header-icon-color, #64748b);
|
|
563
|
+
cursor: pointer;
|
|
564
|
+
border-radius: 4px;
|
|
565
|
+
flex-shrink: 0;
|
|
566
|
+
opacity: 0;
|
|
567
|
+
transition: opacity 0.15s ease, background-color 0.15s ease;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.sg-header-cell:hover .sg-header-menu-btn,
|
|
571
|
+
.sg-header-menu-btn.sg-menu-open {
|
|
572
|
+
opacity: 1;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.sg-header-menu-btn:hover {
|
|
576
|
+
background: var(--sg-header-icon-hover-bg, rgba(0, 0, 0, 0.1));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.sg-header-menu-btn.sg-menu-open {
|
|
580
|
+
background: var(--sg-header-icon-active-bg, rgba(59, 130, 246, 0.2));
|
|
581
|
+
color: var(--sg-primary-color, #3b82f6);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/* Header popup button */
|
|
585
|
+
.sg-header-popup-btn {
|
|
586
|
+
display: flex;
|
|
587
|
+
align-items: center;
|
|
588
|
+
justify-content: center;
|
|
589
|
+
width: 20px;
|
|
590
|
+
height: 20px;
|
|
591
|
+
padding: 0;
|
|
592
|
+
border: none;
|
|
593
|
+
background: transparent;
|
|
594
|
+
color: var(--sg-header-icon-color, #64748b);
|
|
595
|
+
cursor: pointer;
|
|
596
|
+
border-radius: 4px;
|
|
597
|
+
flex-shrink: 0;
|
|
598
|
+
font-size: 12px;
|
|
599
|
+
opacity: 0;
|
|
600
|
+
transition: opacity 0.15s ease, background-color 0.15s ease;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.sg-header-cell:hover .sg-header-popup-btn,
|
|
604
|
+
.sg-header-popup-btn.sg-popup-open {
|
|
605
|
+
opacity: 1;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.sg-header-popup-btn:hover {
|
|
609
|
+
background: var(--sg-header-icon-hover-bg, rgba(0, 0, 0, 0.1));
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.sg-header-popup-btn.sg-popup-open {
|
|
613
|
+
background: var(--sg-header-icon-active-bg, rgba(59, 130, 246, 0.2));
|
|
614
|
+
color: var(--sg-primary-color, #3b82f6);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/* Header popup content */
|
|
618
|
+
.sg-header-popup-content {
|
|
619
|
+
padding: 12px;
|
|
620
|
+
font-size: 13px;
|
|
621
|
+
font-weight: normal;
|
|
622
|
+
}
|
|
623
|
+
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ColumnDefinition, GridEvents, SortDirection } from '../types.js';
|
|
3
|
+
declare function $$render<T>(): {
|
|
4
|
+
props: {
|
|
5
|
+
column: ColumnDefinition<T>;
|
|
6
|
+
index: number;
|
|
7
|
+
columnWidth?: number;
|
|
8
|
+
onheaderclick?: GridEvents<T>["headerclick"];
|
|
9
|
+
headerCell?: Snippet<[ColumnDefinition<T>]>;
|
|
10
|
+
onsort?: (field: string, direction: SortDirection, multiSort: boolean) => void;
|
|
11
|
+
sortDirection?: SortDirection;
|
|
12
|
+
sortIndex?: number;
|
|
13
|
+
onresizestart?: (field: string, startX: number, startWidth: number) => void;
|
|
14
|
+
frozenPosition?: {
|
|
15
|
+
side: "left" | "right";
|
|
16
|
+
offset: number;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
exports: {};
|
|
20
|
+
bindings: "";
|
|
21
|
+
slots: {};
|
|
22
|
+
events: {};
|
|
23
|
+
};
|
|
24
|
+
declare class __sveltets_Render<T> {
|
|
25
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
26
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
27
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
28
|
+
bindings(): "";
|
|
29
|
+
exports(): {};
|
|
30
|
+
}
|
|
31
|
+
interface $$IsomorphicComponent {
|
|
32
|
+
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']>> & {
|
|
33
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
34
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
35
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
36
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
37
|
+
}
|
|
38
|
+
declare const HeaderCell: $$IsomorphicComponent;
|
|
39
|
+
type HeaderCell<T> = InstanceType<typeof HeaderCell<T>>;
|
|
40
|
+
export default HeaderCell;
|