@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,215 @@
|
|
|
1
|
+
<script lang="ts" generics="T">
|
|
2
|
+
import type { MenuItemDefinition, MenuContext, MenuItemAction } from '../types.js';
|
|
3
|
+
import Menu from './Menu.svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Menu items */
|
|
7
|
+
items: MenuItemDefinition<T>[];
|
|
8
|
+
/** Context passed to menu actions */
|
|
9
|
+
context: MenuContext<T>;
|
|
10
|
+
/** CSS class */
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let { items, context, class: className = '' }: Props = $props();
|
|
15
|
+
|
|
16
|
+
// ============================================
|
|
17
|
+
// Submenu State
|
|
18
|
+
// ============================================
|
|
19
|
+
|
|
20
|
+
let activeSubmenuIndex = $state<number | null>(null);
|
|
21
|
+
let submenuRect = $state<DOMRect | null>(null);
|
|
22
|
+
let menuItemRefs: (HTMLDivElement | undefined)[] = $state([]);
|
|
23
|
+
|
|
24
|
+
// ============================================
|
|
25
|
+
// Event Handlers
|
|
26
|
+
// ============================================
|
|
27
|
+
|
|
28
|
+
// Type guard for action items
|
|
29
|
+
function isActionItem(item: MenuItemDefinition<T>): item is MenuItemAction<T> {
|
|
30
|
+
return !item.separator;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function handleItemClick(item: MenuItemAction<T>, event: MouseEvent) {
|
|
34
|
+
event.stopPropagation();
|
|
35
|
+
|
|
36
|
+
// Check if disabled
|
|
37
|
+
const disabled =
|
|
38
|
+
typeof item.disabled === 'function' ? item.disabled(context) : item.disabled;
|
|
39
|
+
|
|
40
|
+
if (disabled) return;
|
|
41
|
+
|
|
42
|
+
// If has submenu, toggle it
|
|
43
|
+
if (item.menu && item.menu.length > 0) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Execute action and close
|
|
48
|
+
item.action?.(context);
|
|
49
|
+
context.closeMenu();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function handleItemMouseEnter(index: number, item: MenuItemAction<T>) {
|
|
53
|
+
if (item.menu && item.menu.length > 0) {
|
|
54
|
+
activeSubmenuIndex = index;
|
|
55
|
+
const ref = menuItemRefs[index];
|
|
56
|
+
if (ref) {
|
|
57
|
+
submenuRect = ref.getBoundingClientRect();
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
activeSubmenuIndex = null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleItemMouseLeave() {
|
|
65
|
+
// Keep submenu open when hovering over it
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function handleKeydown(event: KeyboardEvent, item: MenuItemAction<T>, index: number) {
|
|
69
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
handleItemClick(item, event as unknown as MouseEvent);
|
|
72
|
+
} else if (event.key === 'ArrowRight' && item.menu) {
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
activeSubmenuIndex = index;
|
|
75
|
+
const ref = menuItemRefs[index];
|
|
76
|
+
if (ref) {
|
|
77
|
+
submenuRect = ref.getBoundingClientRect();
|
|
78
|
+
}
|
|
79
|
+
} else if (event.key === 'ArrowLeft' && activeSubmenuIndex !== null) {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
activeSubmenuIndex = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================
|
|
86
|
+
// Computed
|
|
87
|
+
// ============================================
|
|
88
|
+
|
|
89
|
+
function isDisabled(item: MenuItemAction<T>): boolean {
|
|
90
|
+
if (typeof item.disabled === 'function') {
|
|
91
|
+
return item.disabled(context);
|
|
92
|
+
}
|
|
93
|
+
return item.disabled === true;
|
|
94
|
+
}
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<div class="sg-menu {className}" role="menu">
|
|
98
|
+
{#each items as item, index (index)}
|
|
99
|
+
{#if item.separator}
|
|
100
|
+
<div class="sg-menu-separator" role="separator"></div>
|
|
101
|
+
{:else if isActionItem(item)}
|
|
102
|
+
{@const disabled = isDisabled(item)}
|
|
103
|
+
<div
|
|
104
|
+
bind:this={menuItemRefs[index]}
|
|
105
|
+
class="sg-menu-item {item.cssClass ?? ''}"
|
|
106
|
+
class:sg-menu-item-disabled={disabled}
|
|
107
|
+
class:sg-menu-item-has-submenu={item.menu && item.menu.length > 0}
|
|
108
|
+
class:sg-menu-item-active={activeSubmenuIndex === index}
|
|
109
|
+
role="menuitem"
|
|
110
|
+
tabindex={disabled ? -1 : 0}
|
|
111
|
+
aria-disabled={disabled}
|
|
112
|
+
onclick={(e) => handleItemClick(item, e)}
|
|
113
|
+
onmouseenter={() => handleItemMouseEnter(index, item)}
|
|
114
|
+
onmouseleave={handleItemMouseLeave}
|
|
115
|
+
onkeydown={(e) => handleKeydown(e, item, index)}
|
|
116
|
+
>
|
|
117
|
+
{#if item.icon}
|
|
118
|
+
<span class="sg-menu-item-icon">{@html item.icon}</span>
|
|
119
|
+
{/if}
|
|
120
|
+
<span class="sg-menu-item-label">{item.label}</span>
|
|
121
|
+
{#if item.menu && item.menu.length > 0}
|
|
122
|
+
<span class="sg-menu-item-arrow">›</span>
|
|
123
|
+
{/if}
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<!-- Submenu -->
|
|
127
|
+
{#if item.menu && item.menu.length > 0 && activeSubmenuIndex === index && submenuRect}
|
|
128
|
+
<div
|
|
129
|
+
class="sg-submenu"
|
|
130
|
+
role="menu"
|
|
131
|
+
tabindex="-1"
|
|
132
|
+
style="top: {submenuRect.top}px; left: {submenuRect.right}px;"
|
|
133
|
+
onmouseenter={() => (activeSubmenuIndex = index)}
|
|
134
|
+
onmouseleave={() => (activeSubmenuIndex = null)}
|
|
135
|
+
>
|
|
136
|
+
<Menu items={item.menu} {context} />
|
|
137
|
+
</div>
|
|
138
|
+
{/if}
|
|
139
|
+
{/if}
|
|
140
|
+
{/each}
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<style>
|
|
144
|
+
.sg-menu {
|
|
145
|
+
padding: 4px 0;
|
|
146
|
+
min-width: 160px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.sg-menu-item {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: 8px;
|
|
153
|
+
padding: 8px 12px;
|
|
154
|
+
cursor: pointer;
|
|
155
|
+
font-size: 14px;
|
|
156
|
+
color: var(--sg-menu-text, #1e293b);
|
|
157
|
+
transition: background-color 0.1s ease;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.sg-menu-item:hover:not(.sg-menu-item-disabled) {
|
|
161
|
+
background: var(--sg-menu-hover-bg, #f1f5f9);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.sg-menu-item:focus {
|
|
165
|
+
outline: none;
|
|
166
|
+
background: var(--sg-menu-focus-bg, #e2e8f0);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.sg-menu-item-active {
|
|
170
|
+
background: var(--sg-menu-hover-bg, #f1f5f9);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.sg-menu-item-disabled {
|
|
174
|
+
opacity: 0.5;
|
|
175
|
+
cursor: not-allowed;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.sg-menu-item-icon {
|
|
179
|
+
display: flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
justify-content: center;
|
|
182
|
+
width: 16px;
|
|
183
|
+
height: 16px;
|
|
184
|
+
flex-shrink: 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.sg-menu-item-label {
|
|
188
|
+
flex: 1;
|
|
189
|
+
overflow: hidden;
|
|
190
|
+
text-overflow: ellipsis;
|
|
191
|
+
white-space: nowrap;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.sg-menu-item-arrow {
|
|
195
|
+
color: var(--sg-menu-arrow, #94a3b8);
|
|
196
|
+
font-size: 16px;
|
|
197
|
+
margin-left: auto;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.sg-menu-separator {
|
|
201
|
+
height: 1px;
|
|
202
|
+
background: var(--sg-menu-separator, #e2e8f0);
|
|
203
|
+
margin: 4px 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.sg-submenu {
|
|
207
|
+
position: fixed;
|
|
208
|
+
z-index: 1001;
|
|
209
|
+
background: var(--sg-popup-bg, #ffffff);
|
|
210
|
+
border: 1px solid var(--sg-popup-border, #e2e8f0);
|
|
211
|
+
border-radius: 6px;
|
|
212
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
213
|
+
min-width: 160px;
|
|
214
|
+
}
|
|
215
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MenuItemDefinition, MenuContext } from '../types.js';
|
|
2
|
+
import Menu from './Menu.svelte';
|
|
3
|
+
declare function $$render<T>(): {
|
|
4
|
+
props: {
|
|
5
|
+
/** Menu items */
|
|
6
|
+
items: MenuItemDefinition<T>[];
|
|
7
|
+
/** Context passed to menu actions */
|
|
8
|
+
context: MenuContext<T>;
|
|
9
|
+
/** CSS class */
|
|
10
|
+
class?: string;
|
|
11
|
+
};
|
|
12
|
+
exports: {};
|
|
13
|
+
bindings: "";
|
|
14
|
+
slots: {};
|
|
15
|
+
events: {};
|
|
16
|
+
};
|
|
17
|
+
declare class __sveltets_Render<T> {
|
|
18
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
19
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
20
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
21
|
+
bindings(): "";
|
|
22
|
+
exports(): {};
|
|
23
|
+
}
|
|
24
|
+
interface $$IsomorphicComponent {
|
|
25
|
+
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']>> & {
|
|
26
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
27
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
28
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
29
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
30
|
+
}
|
|
31
|
+
declare const Menu: $$IsomorphicComponent;
|
|
32
|
+
type Menu<T> = InstanceType<typeof Menu<T>>;
|
|
33
|
+
export default Menu;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Whether the popup is visible */
|
|
7
|
+
open: boolean;
|
|
8
|
+
/** Target element to position relative to */
|
|
9
|
+
targetRect: DOMRect | null;
|
|
10
|
+
/** Preferred position relative to target */
|
|
11
|
+
position?: 'bottom' | 'top' | 'left' | 'right' | 'bottom-start' | 'bottom-end';
|
|
12
|
+
/** Close callback */
|
|
13
|
+
onclose?: () => void;
|
|
14
|
+
/** CSS class for the popup */
|
|
15
|
+
class?: string;
|
|
16
|
+
/** Popup content */
|
|
17
|
+
children: Snippet;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
open,
|
|
22
|
+
targetRect,
|
|
23
|
+
position = 'bottom-start',
|
|
24
|
+
onclose,
|
|
25
|
+
class: className = '',
|
|
26
|
+
children
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
let popupRef: HTMLDivElement | undefined = $state();
|
|
30
|
+
let popupStyle = $state('');
|
|
31
|
+
|
|
32
|
+
// ============================================
|
|
33
|
+
// Positioning
|
|
34
|
+
// ============================================
|
|
35
|
+
|
|
36
|
+
function updatePosition() {
|
|
37
|
+
if (!popupRef || !targetRect || !open) return;
|
|
38
|
+
|
|
39
|
+
const popupRect = popupRef.getBoundingClientRect();
|
|
40
|
+
const viewportWidth = window.innerWidth;
|
|
41
|
+
const viewportHeight = window.innerHeight;
|
|
42
|
+
const scrollX = window.scrollX;
|
|
43
|
+
const scrollY = window.scrollY;
|
|
44
|
+
|
|
45
|
+
let top = 0;
|
|
46
|
+
let left = 0;
|
|
47
|
+
|
|
48
|
+
// Calculate base position
|
|
49
|
+
switch (position) {
|
|
50
|
+
case 'bottom':
|
|
51
|
+
top = targetRect.bottom + scrollY;
|
|
52
|
+
left = targetRect.left + scrollX + (targetRect.width - popupRect.width) / 2;
|
|
53
|
+
break;
|
|
54
|
+
case 'bottom-start':
|
|
55
|
+
top = targetRect.bottom + scrollY;
|
|
56
|
+
left = targetRect.left + scrollX;
|
|
57
|
+
break;
|
|
58
|
+
case 'bottom-end':
|
|
59
|
+
top = targetRect.bottom + scrollY;
|
|
60
|
+
left = targetRect.right + scrollX - popupRect.width;
|
|
61
|
+
break;
|
|
62
|
+
case 'top':
|
|
63
|
+
top = targetRect.top + scrollY - popupRect.height;
|
|
64
|
+
left = targetRect.left + scrollX + (targetRect.width - popupRect.width) / 2;
|
|
65
|
+
break;
|
|
66
|
+
case 'left':
|
|
67
|
+
top = targetRect.top + scrollY + (targetRect.height - popupRect.height) / 2;
|
|
68
|
+
left = targetRect.left + scrollX - popupRect.width;
|
|
69
|
+
break;
|
|
70
|
+
case 'right':
|
|
71
|
+
top = targetRect.top + scrollY + (targetRect.height - popupRect.height) / 2;
|
|
72
|
+
left = targetRect.right + scrollX;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Viewport collision detection - flip if needed
|
|
77
|
+
if (left + popupRect.width > viewportWidth + scrollX) {
|
|
78
|
+
left = viewportWidth + scrollX - popupRect.width - 8;
|
|
79
|
+
}
|
|
80
|
+
if (left < scrollX) {
|
|
81
|
+
left = scrollX + 8;
|
|
82
|
+
}
|
|
83
|
+
if (top + popupRect.height > viewportHeight + scrollY) {
|
|
84
|
+
// Flip to top
|
|
85
|
+
top = targetRect.top + scrollY - popupRect.height;
|
|
86
|
+
}
|
|
87
|
+
if (top < scrollY) {
|
|
88
|
+
top = scrollY + 8;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
popupStyle = `top: ${top}px; left: ${left}px;`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================
|
|
95
|
+
// Effects
|
|
96
|
+
// ============================================
|
|
97
|
+
|
|
98
|
+
$effect(() => {
|
|
99
|
+
if (open && targetRect && popupRef) {
|
|
100
|
+
// Use requestAnimationFrame to ensure DOM is updated
|
|
101
|
+
requestAnimationFrame(updatePosition);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ============================================
|
|
106
|
+
// Event Handlers
|
|
107
|
+
// ============================================
|
|
108
|
+
|
|
109
|
+
function handleClickOutside(event: MouseEvent) {
|
|
110
|
+
if (!open || !popupRef) return;
|
|
111
|
+
|
|
112
|
+
const target = event.target as Node;
|
|
113
|
+
if (!popupRef.contains(target)) {
|
|
114
|
+
onclose?.();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
119
|
+
if (!open) return;
|
|
120
|
+
|
|
121
|
+
if (event.key === 'Escape') {
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
onclose?.();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function handleScroll() {
|
|
128
|
+
if (open) {
|
|
129
|
+
onclose?.();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================
|
|
134
|
+
// Lifecycle
|
|
135
|
+
// ============================================
|
|
136
|
+
|
|
137
|
+
onMount(() => {
|
|
138
|
+
// Delay to avoid immediate close from the click that opened it
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
141
|
+
}, 0);
|
|
142
|
+
document.addEventListener('keydown', handleKeydown);
|
|
143
|
+
window.addEventListener('scroll', handleScroll, true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
onDestroy(() => {
|
|
147
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
148
|
+
document.removeEventListener('keydown', handleKeydown);
|
|
149
|
+
window.removeEventListener('scroll', handleScroll, true);
|
|
150
|
+
});
|
|
151
|
+
</script>
|
|
152
|
+
|
|
153
|
+
{#if open}
|
|
154
|
+
<div
|
|
155
|
+
bind:this={popupRef}
|
|
156
|
+
class="sg-popup {className}"
|
|
157
|
+
style={popupStyle}
|
|
158
|
+
role="dialog"
|
|
159
|
+
aria-modal="true"
|
|
160
|
+
>
|
|
161
|
+
{@render children()}
|
|
162
|
+
</div>
|
|
163
|
+
{/if}
|
|
164
|
+
|
|
165
|
+
<style>
|
|
166
|
+
.sg-popup {
|
|
167
|
+
position: fixed;
|
|
168
|
+
z-index: 1000;
|
|
169
|
+
background: var(--sg-popup-bg, #ffffff);
|
|
170
|
+
border: 1px solid var(--sg-popup-border, #e2e8f0);
|
|
171
|
+
border-radius: 6px;
|
|
172
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
173
|
+
min-width: 120px;
|
|
174
|
+
max-width: 320px;
|
|
175
|
+
overflow: hidden;
|
|
176
|
+
animation: sg-popup-fade-in 0.15s ease-out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@keyframes sg-popup-fade-in {
|
|
180
|
+
from {
|
|
181
|
+
opacity: 0;
|
|
182
|
+
transform: translateY(-4px);
|
|
183
|
+
}
|
|
184
|
+
to {
|
|
185
|
+
opacity: 1;
|
|
186
|
+
transform: translateY(0);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Whether the popup is visible */
|
|
4
|
+
open: boolean;
|
|
5
|
+
/** Target element to position relative to */
|
|
6
|
+
targetRect: DOMRect | null;
|
|
7
|
+
/** Preferred position relative to target */
|
|
8
|
+
position?: 'bottom' | 'top' | 'left' | 'right' | 'bottom-start' | 'bottom-end';
|
|
9
|
+
/** Close callback */
|
|
10
|
+
onclose?: () => void;
|
|
11
|
+
/** CSS class for the popup */
|
|
12
|
+
class?: string;
|
|
13
|
+
/** Popup content */
|
|
14
|
+
children: Snippet;
|
|
15
|
+
}
|
|
16
|
+
declare const Popup: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type Popup = ReturnType<typeof Popup>;
|
|
18
|
+
export default Popup;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script lang="ts" generics="T">
|
|
2
|
+
import { getContext } from 'svelte';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { ColumnDefinition, CellContext, GridEvents } from '../types.js';
|
|
5
|
+
import { GRID_CONTEXT_KEY, type GridStateManager } from '../state/gridState.svelte.js';
|
|
6
|
+
import Cell from './Cell.svelte';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
row: T;
|
|
10
|
+
columns: ColumnDefinition<T>[];
|
|
11
|
+
rowIndex: number;
|
|
12
|
+
rowHeight: number;
|
|
13
|
+
onrowclick?: GridEvents<T>['rowclick'];
|
|
14
|
+
onrowdblclick?: GridEvents<T>['rowdblclick'];
|
|
15
|
+
oncellclick?: GridEvents<T>['cellclick'];
|
|
16
|
+
cell?: Snippet<[CellContext<T>]>;
|
|
17
|
+
onrowcontextmenu?: (row: T, rowIndex: number, event: MouseEvent) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
row,
|
|
22
|
+
columns,
|
|
23
|
+
rowIndex,
|
|
24
|
+
rowHeight,
|
|
25
|
+
onrowclick,
|
|
26
|
+
onrowdblclick,
|
|
27
|
+
oncellclick,
|
|
28
|
+
cell,
|
|
29
|
+
onrowcontextmenu
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
// Get grid state for column widths
|
|
33
|
+
const gridState = getContext<GridStateManager<T>>(GRID_CONTEXT_KEY);
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// Event Handlers
|
|
37
|
+
// ============================================
|
|
38
|
+
|
|
39
|
+
function handleClick(event: MouseEvent) {
|
|
40
|
+
onrowclick?.(row, rowIndex, event);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function handleDblClick(event: MouseEvent) {
|
|
44
|
+
onrowdblclick?.(row, rowIndex, event);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
48
|
+
if (event.key === 'Enter') {
|
|
49
|
+
onrowclick?.(row, rowIndex, event as unknown as MouseEvent);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function handleContextMenu(event: MouseEvent) {
|
|
54
|
+
onrowcontextmenu?.(row, rowIndex, event);
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<div
|
|
59
|
+
class="sg-row"
|
|
60
|
+
role="row"
|
|
61
|
+
aria-rowindex={rowIndex + 1}
|
|
62
|
+
tabindex="0"
|
|
63
|
+
style="height: {rowHeight}px;"
|
|
64
|
+
onclick={handleClick}
|
|
65
|
+
ondblclick={handleDblClick}
|
|
66
|
+
onkeydown={handleKeydown}
|
|
67
|
+
oncontextmenu={onrowcontextmenu ? handleContextMenu : undefined}
|
|
68
|
+
>
|
|
69
|
+
{#each columns as column, columnIndex (column.field)}
|
|
70
|
+
{@const frozenLeft = column.frozen === true || column.frozen === 'left'}
|
|
71
|
+
{@const frozenRight = column.frozen === 'right'}
|
|
72
|
+
{@const frozenPosition = frozenLeft
|
|
73
|
+
? { side: 'left' as const, offset: gridState?.getFrozenLeftPosition(column.field) ?? 0 }
|
|
74
|
+
: frozenRight
|
|
75
|
+
? { side: 'right' as const, offset: gridState?.getFrozenRightPosition(column.field) ?? 0 }
|
|
76
|
+
: undefined}
|
|
77
|
+
<Cell
|
|
78
|
+
value={(row as Record<string, unknown>)[column.field]}
|
|
79
|
+
{row}
|
|
80
|
+
{column}
|
|
81
|
+
{rowIndex}
|
|
82
|
+
{columnIndex}
|
|
83
|
+
columnWidth={gridState?.getColumnWidth(column.field)}
|
|
84
|
+
{oncellclick}
|
|
85
|
+
{cell}
|
|
86
|
+
{frozenPosition}
|
|
87
|
+
/>
|
|
88
|
+
{/each}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<style>
|
|
92
|
+
.sg-row {
|
|
93
|
+
display: flex;
|
|
94
|
+
min-width: 100%;
|
|
95
|
+
background: var(--sg-row-bg, #ffffff);
|
|
96
|
+
box-sizing: border-box;
|
|
97
|
+
transition: background-color var(--sg-transition-fast, 0.1s ease);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.sg-row:focus {
|
|
101
|
+
outline: 2px solid var(--sg-primary-color, #3b82f6);
|
|
102
|
+
outline-offset: -2px;
|
|
103
|
+
z-index: 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Striped rows (applied via parent) */
|
|
107
|
+
:global(.sg-striped) .sg-row:nth-child(even) {
|
|
108
|
+
background: var(--sg-row-alt-bg, #f8fafc);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Hover state (applied via parent) */
|
|
112
|
+
:global(.sg-row-hover) .sg-row:hover {
|
|
113
|
+
background: var(--sg-row-hover-bg, #f1f5f9);
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ColumnDefinition, CellContext, GridEvents } from '../types.js';
|
|
3
|
+
declare function $$render<T>(): {
|
|
4
|
+
props: {
|
|
5
|
+
row: T;
|
|
6
|
+
columns: ColumnDefinition<T>[];
|
|
7
|
+
rowIndex: number;
|
|
8
|
+
rowHeight: number;
|
|
9
|
+
onrowclick?: GridEvents<T>["rowclick"];
|
|
10
|
+
onrowdblclick?: GridEvents<T>["rowdblclick"];
|
|
11
|
+
oncellclick?: GridEvents<T>["cellclick"];
|
|
12
|
+
cell?: Snippet<[CellContext<T>]>;
|
|
13
|
+
onrowcontextmenu?: (row: T, rowIndex: number, event: MouseEvent) => void;
|
|
14
|
+
};
|
|
15
|
+
exports: {};
|
|
16
|
+
bindings: "";
|
|
17
|
+
slots: {};
|
|
18
|
+
events: {};
|
|
19
|
+
};
|
|
20
|
+
declare class __sveltets_Render<T> {
|
|
21
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
22
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
23
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
24
|
+
bindings(): "";
|
|
25
|
+
exports(): {};
|
|
26
|
+
}
|
|
27
|
+
interface $$IsomorphicComponent {
|
|
28
|
+
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']>> & {
|
|
29
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
30
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
31
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
32
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
33
|
+
}
|
|
34
|
+
declare const Row: $$IsomorphicComponent;
|
|
35
|
+
type Row<T> = InstanceType<typeof Row<T>>;
|
|
36
|
+
export default Row;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* svelte-grid - A Svelte 5 table/grid library
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export { default as Grid } from './components/Grid.svelte';
|
|
7
|
+
export { default as GridHeader } from './components/GridHeader.svelte';
|
|
8
|
+
export { default as GridBody } from './components/GridBody.svelte';
|
|
9
|
+
export { default as HeaderCell } from './components/HeaderCell.svelte';
|
|
10
|
+
export { default as Row } from './components/Row.svelte';
|
|
11
|
+
export { default as Cell } from './components/Cell.svelte';
|
|
12
|
+
export { default as Popup } from './components/Popup.svelte';
|
|
13
|
+
export { default as Menu } from './components/Menu.svelte';
|
|
14
|
+
export { default as GroupHeader } from './components/GroupHeader.svelte';
|
|
15
|
+
export { createGridState, GridStateManager, GRID_CONTEXT_KEY } from './state/gridState.svelte.js';
|
|
16
|
+
export { themes, defaultTheme, tailwindBlue, tailwindEmerald, tailwindRose, tailwindAmber, tailwindSlate, darkTheme, themeToStyle, createTheme } from './themes.js';
|
|
17
|
+
export type { GridTheme, ThemeName } from './themes.js';
|
|
18
|
+
export type { RowData, ColumnDefinition, FormatterType, FormatterFunction, CellContext, RowContext, GridOptions, GridState, TablePlugin, PluginContext, PluginInstance, GridEvents, GridSnippets, MenuItemDefinition, MenuItemAction, MenuItemSeparator, MenuAction, MenuContext, PopupContent, PopupContext, GroupBy, GroupConfig, GroupInfo, GroupAggregates, DisplayRow } from './types.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* svelte-grid - A Svelte 5 table/grid library
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
// Main Grid Component
|
|
7
|
+
export { default as Grid } from './components/Grid.svelte';
|
|
8
|
+
// Individual components (for advanced use cases)
|
|
9
|
+
export { default as GridHeader } from './components/GridHeader.svelte';
|
|
10
|
+
export { default as GridBody } from './components/GridBody.svelte';
|
|
11
|
+
export { default as HeaderCell } from './components/HeaderCell.svelte';
|
|
12
|
+
export { default as Row } from './components/Row.svelte';
|
|
13
|
+
export { default as Cell } from './components/Cell.svelte';
|
|
14
|
+
export { default as Popup } from './components/Popup.svelte';
|
|
15
|
+
export { default as Menu } from './components/Menu.svelte';
|
|
16
|
+
export { default as GroupHeader } from './components/GroupHeader.svelte';
|
|
17
|
+
// State Management
|
|
18
|
+
export { createGridState, GridStateManager, GRID_CONTEXT_KEY } from './state/gridState.svelte.js';
|
|
19
|
+
// Theming
|
|
20
|
+
export { themes, defaultTheme, tailwindBlue, tailwindEmerald, tailwindRose, tailwindAmber, tailwindSlate, darkTheme, themeToStyle, createTheme } from './themes.js';
|