@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.
@@ -0,0 +1,249 @@
1
+ <script lang="ts" generics="T">
2
+ import type { Snippet } from 'svelte';
3
+ import type { ColumnDefinition, CellContext, GridEvents } from '../types.js';
4
+
5
+ interface Props {
6
+ value: unknown;
7
+ row: T;
8
+ column: ColumnDefinition<T>;
9
+ rowIndex: number;
10
+ columnIndex: number;
11
+ columnWidth?: number;
12
+ oncellclick?: GridEvents<T>['cellclick'];
13
+ cell?: Snippet<[CellContext<T>]>;
14
+ frozenPosition?: { side: 'left' | 'right'; offset: number };
15
+ }
16
+
17
+ let { value, row, column, rowIndex, columnIndex, columnWidth, oncellclick, cell, frozenPosition }: Props =
18
+ $props();
19
+
20
+ // ============================================
21
+ // Cell Context for custom renderers
22
+ // ============================================
23
+
24
+ const cellContext = $derived<CellContext<T>>({
25
+ value,
26
+ row,
27
+ column,
28
+ rowIndex,
29
+ columnIndex
30
+ });
31
+
32
+ // ============================================
33
+ // Computed Styles
34
+ // ============================================
35
+
36
+ const cellStyle = $derived.by(() => {
37
+ const styles: string[] = [];
38
+
39
+ // Use columnWidth (from resize) if available, else column.width
40
+ const width = columnWidth ?? column.width;
41
+
42
+ if (width) {
43
+ const w = typeof width === 'number' ? `${width}px` : width;
44
+ styles.push(`width: ${w}`);
45
+ styles.push(`min-width: ${w}`);
46
+ } else {
47
+ styles.push('flex: 1');
48
+ styles.push('min-width: 100px');
49
+ }
50
+
51
+ if (column.minWidth && !columnWidth) {
52
+ styles.push(`min-width: ${column.minWidth}px`);
53
+ }
54
+
55
+ if (column.maxWidth) {
56
+ styles.push(`max-width: ${column.maxWidth}px`);
57
+ }
58
+
59
+ // Frozen column positioning
60
+ if (frozenPosition) {
61
+ styles.push('position: sticky');
62
+ styles.push(`${frozenPosition.side}: ${frozenPosition.offset}px`);
63
+ styles.push('z-index: 1');
64
+ }
65
+
66
+ return styles.join('; ');
67
+ });
68
+
69
+ const isFrozen = $derived(!!frozenPosition);
70
+
71
+ const alignClass = $derived.by(() => {
72
+ if (column.hAlign === 'center') return 'sg-align-center';
73
+ if (column.hAlign === 'right') return 'sg-align-right';
74
+ return 'sg-align-left';
75
+ });
76
+
77
+ const vAlignClass = $derived.by(() => {
78
+ if (column.vAlign === 'top') return 'sg-valign-top';
79
+ if (column.vAlign === 'bottom') return 'sg-valign-bottom';
80
+ return 'sg-valign-middle';
81
+ });
82
+
83
+ // ============================================
84
+ // Formatting
85
+ // ============================================
86
+
87
+ const formattedValue = $derived.by(() => {
88
+ if (value === null || value === undefined) {
89
+ return '';
90
+ }
91
+
92
+ // Custom formatter function
93
+ if (typeof column.formatter === 'function') {
94
+ return column.formatter(cellContext);
95
+ }
96
+
97
+ // Built-in formatters
98
+ switch (column.formatter) {
99
+ case 'number':
100
+ return typeof value === 'number' ? value.toLocaleString() : String(value);
101
+
102
+ case 'money':
103
+ return typeof value === 'number'
104
+ ? value.toLocaleString(undefined, { style: 'currency', currency: 'USD' })
105
+ : String(value);
106
+
107
+ case 'date':
108
+ if (value instanceof Date) {
109
+ return value.toLocaleDateString();
110
+ }
111
+ if (typeof value === 'string' || typeof value === 'number') {
112
+ return new Date(value).toLocaleDateString();
113
+ }
114
+ return String(value);
115
+
116
+ case 'datetime':
117
+ if (value instanceof Date) {
118
+ return value.toLocaleString();
119
+ }
120
+ if (typeof value === 'string' || typeof value === 'number') {
121
+ return new Date(value).toLocaleString();
122
+ }
123
+ return String(value);
124
+
125
+ case 'boolean':
126
+ return value ? '✓' : '✗';
127
+
128
+ case 'text':
129
+ default:
130
+ return String(value);
131
+ }
132
+ });
133
+
134
+ // ============================================
135
+ // Tooltip
136
+ // ============================================
137
+
138
+ const tooltipText = $derived.by(() => {
139
+ if (!column.tooltip) return undefined;
140
+
141
+ if (typeof column.tooltip === 'function') {
142
+ return column.tooltip(cellContext);
143
+ }
144
+
145
+ return column.tooltip;
146
+ });
147
+
148
+ // ============================================
149
+ // Event Handlers
150
+ // ============================================
151
+
152
+ function handleClick(event: MouseEvent) {
153
+ event.stopPropagation(); // Prevent row click
154
+ oncellclick?.(cellContext, event);
155
+ }
156
+
157
+ function handleKeydown(event: KeyboardEvent) {
158
+ if (event.key === 'Enter' || event.key === ' ') {
159
+ event.preventDefault();
160
+ oncellclick?.(cellContext, event as unknown as MouseEvent);
161
+ }
162
+ }
163
+ </script>
164
+
165
+ <div
166
+ class="sg-cell {alignClass} {vAlignClass} {column.cssClass ?? ''}"
167
+ class:sg-frozen={isFrozen}
168
+ class:sg-frozen-left={frozenPosition?.side === 'left'}
169
+ class:sg-frozen-right={frozenPosition?.side === 'right'}
170
+ style={cellStyle}
171
+ role="gridcell"
172
+ aria-colindex={columnIndex + 1}
173
+ tabindex="-1"
174
+ title={tooltipText}
175
+ onclick={handleClick}
176
+ onkeydown={handleKeydown}
177
+ >
178
+ {#if cell}
179
+ {@render cell(cellContext)}
180
+ {:else}
181
+ <span class="sg-cell-content">
182
+ {formattedValue}
183
+ </span>
184
+ {/if}
185
+ </div>
186
+
187
+ <style>
188
+ .sg-cell {
189
+ display: flex;
190
+ padding: var(--sg-cell-padding-y) var(--sg-cell-padding-x);
191
+ overflow: hidden;
192
+ box-sizing: border-box;
193
+ border-bottom: 1px solid var(--sg-border-color, #f1f5f9);
194
+ font-feature-settings: 'tnum' 1; /* Tabular numbers for data */
195
+ transition: background-color var(--sg-transition-fast, 0.1s ease);
196
+ }
197
+
198
+ .sg-cell-content {
199
+ overflow: hidden;
200
+ text-overflow: ellipsis;
201
+ white-space: nowrap;
202
+ width: 100%;
203
+ color: #334155;
204
+ }
205
+
206
+ /* Horizontal Alignment */
207
+ .sg-align-left {
208
+ justify-content: flex-start;
209
+ text-align: left;
210
+ }
211
+
212
+ .sg-align-center {
213
+ justify-content: center;
214
+ text-align: center;
215
+ }
216
+
217
+ .sg-align-right {
218
+ justify-content: flex-end;
219
+ text-align: right;
220
+ }
221
+
222
+ /* Vertical Alignment */
223
+ .sg-valign-top {
224
+ align-items: flex-start;
225
+ }
226
+
227
+ .sg-valign-middle {
228
+ align-items: center;
229
+ }
230
+
231
+ .sg-valign-bottom {
232
+ align-items: flex-end;
233
+ }
234
+
235
+ /* Frozen column styles */
236
+ .sg-frozen {
237
+ background: inherit;
238
+ }
239
+
240
+ .sg-frozen-left {
241
+ border-right: 2px solid var(--sg-frozen-border-color, #cbd5e1);
242
+ box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
243
+ }
244
+
245
+ .sg-frozen-right {
246
+ border-left: 2px solid var(--sg-frozen-border-color, #cbd5e1);
247
+ box-shadow: -2px 0 4px rgba(0, 0, 0, 0.05);
248
+ }
249
+ </style>
@@ -0,0 +1,39 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { ColumnDefinition, CellContext, GridEvents } from '../types.js';
3
+ declare function $$render<T>(): {
4
+ props: {
5
+ value: unknown;
6
+ row: T;
7
+ column: ColumnDefinition<T>;
8
+ rowIndex: number;
9
+ columnIndex: number;
10
+ columnWidth?: number;
11
+ oncellclick?: GridEvents<T>["cellclick"];
12
+ cell?: Snippet<[CellContext<T>]>;
13
+ frozenPosition?: {
14
+ side: "left" | "right";
15
+ offset: number;
16
+ };
17
+ };
18
+ exports: {};
19
+ bindings: "";
20
+ slots: {};
21
+ events: {};
22
+ };
23
+ declare class __sveltets_Render<T> {
24
+ props(): ReturnType<typeof $$render<T>>['props'];
25
+ events(): ReturnType<typeof $$render<T>>['events'];
26
+ slots(): ReturnType<typeof $$render<T>>['slots'];
27
+ bindings(): "";
28
+ exports(): {};
29
+ }
30
+ interface $$IsomorphicComponent {
31
+ 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']>> & {
32
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
33
+ } & ReturnType<__sveltets_Render<T>['exports']>;
34
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
35
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
36
+ }
37
+ declare const Cell: $$IsomorphicComponent;
38
+ type Cell<T> = InstanceType<typeof Cell<T>>;
39
+ export default Cell;