@warkypublic/svelix 0.1.43 → 0.1.46
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/CardGrid/CardGrid.svelte +1312 -0
- package/dist/components/CardGrid/CardGrid.svelte.d.ts +61 -0
- package/dist/components/CardGrid/CardGridFilterPanel.svelte +299 -0
- package/dist/components/CardGrid/CardGridFilterPanel.svelte.d.ts +11 -0
- package/dist/components/CardGrid/DefaultCard.svelte +303 -0
- package/dist/components/CardGrid/DefaultCard.svelte.d.ts +10 -0
- package/dist/components/CardGrid/ImageCardStory.svelte +257 -0
- package/dist/components/CardGrid/ImageCardStory.svelte.d.ts +18 -0
- package/dist/components/CardGrid/cardGrid.d.ts +33 -0
- package/dist/components/CardGrid/cardGrid.js +21 -0
- package/dist/components/CardGrid/index.d.ts +4 -0
- package/dist/components/CardGrid/index.js +4 -0
- package/dist/components/Gridler/components/GridlerCanvas.svelte +1 -0
- package/dist/components/Gridler/components/GridlerFull.svelte +4 -1
- package/dist/components/Gridler/components/GridlerSearch.svelte +3 -3
- package/dist/components/Gridler/components/GridlerSearch.svelte.d.ts +3 -1
- package/dist/components/Gridler/components/GridlerSearchToggle.svelte +7 -0
- package/dist/components/Gridler/types.d.ts +2 -12
- package/dist/components/Gridler/utils/cellContent.js +2 -1
- package/dist/components/Gridler/utils/filters.js +2 -2
- package/dist/components/Gridler/utils/sort.js +2 -1
- package/dist/components/Types/generic_grid.d.ts +73 -5
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/llm/COMPONENT_GUIDE.md +240 -0
- package/llm/plans/card_grid.plan.md +305 -0
- package/package.json +19 -18
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import CardGrid from './CardGrid.svelte';
|
|
3
|
+
import type { CardGridColumn, CardSortOption } from './cardGrid.js';
|
|
4
|
+
|
|
5
|
+
const columns: CardGridColumn[] = [
|
|
6
|
+
{ id: 'id', title: 'ID', disableSearch: true, disableFilter: true },
|
|
7
|
+
{ id: 'title', title: 'Title' },
|
|
8
|
+
{ id: 'photographer', title: 'Photographer' },
|
|
9
|
+
{ id: 'category', title: 'Category' },
|
|
10
|
+
{ id: 'likes', title: 'Likes', format: 'number' },
|
|
11
|
+
{ id: 'views', title: 'Views', format: 'number' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const sortOptions: CardSortOption[] = [
|
|
15
|
+
{ columnId: 'title', label: 'Title' },
|
|
16
|
+
{ columnId: 'photographer', label: 'Photographer' },
|
|
17
|
+
{ columnId: 'likes', label: 'Likes' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const CATEGORIES = ['Architecture', 'Nature', 'People', 'Travel', 'Abstract', 'Animals'];
|
|
21
|
+
const PHOTOGRAPHERS = ['Alice Monroe', 'Ben Carter', 'Cara Lee', 'Daniel Park', 'Emma Rios', 'Felix Braun'];
|
|
22
|
+
const TITLES = [
|
|
23
|
+
'Golden Hour', 'Morning Mist', 'Urban Jungle', 'Still Waters', 'First Light',
|
|
24
|
+
'Hidden Path', 'City Pulse', 'Wild Plains', 'Quiet Corner', 'Storm Front',
|
|
25
|
+
'Desert Bloom', 'Arctic Echo', 'Neon Drift', 'Forest Floor', 'Tidal Wave',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const data = Array.from({ length: 60 }, (_, i) => ({
|
|
29
|
+
id: i + 1,
|
|
30
|
+
title: `${TITLES[i % TITLES.length]} ${Math.floor(i / TITLES.length) || ''}`.trim(),
|
|
31
|
+
photographer: PHOTOGRAPHERS[i % PHOTOGRAPHERS.length],
|
|
32
|
+
category: CATEGORIES[i % CATEGORIES.length],
|
|
33
|
+
likes: Math.floor(Math.random() * 4800 + 200),
|
|
34
|
+
views: Math.floor(Math.random() * 48000 + 2000),
|
|
35
|
+
imageUrl: `https://picsum.photos/seed/${i + 1}/400/260`,
|
|
36
|
+
}));
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<!-- cardMaxFrontColumns=3 splits: front=[id,title,photographer], back=[category,likes,views] -->
|
|
40
|
+
<CardGrid
|
|
41
|
+
{columns}
|
|
42
|
+
{sortOptions}
|
|
43
|
+
{data}
|
|
44
|
+
cardMinWidth={260}
|
|
45
|
+
cardMaxFrontColumns={3}
|
|
46
|
+
pageSize={20}
|
|
47
|
+
searchPlaceholder="Search photos…"
|
|
48
|
+
>
|
|
49
|
+
{#snippet card(record, _i, selected, _focused)}
|
|
50
|
+
<article class="ic-card" class:ic-selected={selected}>
|
|
51
|
+
<div class="ic-image-wrap">
|
|
52
|
+
<img
|
|
53
|
+
src={record.imageUrl as string}
|
|
54
|
+
alt={record.title as string}
|
|
55
|
+
class="ic-image"
|
|
56
|
+
loading="lazy"
|
|
57
|
+
/>
|
|
58
|
+
<span class="ic-category">{record.category as string}</span>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="ic-body">
|
|
61
|
+
<p class="ic-title">{record.title as string}</p>
|
|
62
|
+
<p class="ic-photographer">by {record.photographer as string}</p>
|
|
63
|
+
</div>
|
|
64
|
+
</article>
|
|
65
|
+
{/snippet}
|
|
66
|
+
|
|
67
|
+
{#snippet cardBack(record, _i, selected, _focused)}
|
|
68
|
+
<article class="ic-card ic-back" class:ic-selected={selected}>
|
|
69
|
+
<div class="ic-back-image-wrap">
|
|
70
|
+
<img
|
|
71
|
+
src={record.imageUrl as string}
|
|
72
|
+
alt={record.title as string}
|
|
73
|
+
class="ic-back-image"
|
|
74
|
+
loading="lazy"
|
|
75
|
+
/>
|
|
76
|
+
<div class="ic-back-overlay">
|
|
77
|
+
<p class="ic-back-title">{record.title as string}</p>
|
|
78
|
+
<p class="ic-back-photographer">by {record.photographer as string}</p>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="ic-body ic-back-body">
|
|
82
|
+
<div class="ic-back-stat">
|
|
83
|
+
<svg width="14" height="14" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
84
|
+
<path d="M3 7h14M3 10h10M3 13h6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
|
|
85
|
+
</svg>
|
|
86
|
+
<span class="ic-back-label">Category</span>
|
|
87
|
+
<span class="ic-back-value">{record.category as string}</span>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="ic-back-stat">
|
|
90
|
+
<svg width="14" height="14" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
91
|
+
<path d="M10 4C5 4 1 10 1 10s4 6 9 6 9-6 9-6-4-6-9-6Z" stroke="currentColor" stroke-width="1.6"/>
|
|
92
|
+
<circle cx="10" cy="10" r="3" stroke="currentColor" stroke-width="1.6"/>
|
|
93
|
+
</svg>
|
|
94
|
+
<span class="ic-back-label">Views</span>
|
|
95
|
+
<span class="ic-back-value">{(record.views as number).toLocaleString()}</span>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="ic-back-stat">
|
|
98
|
+
<svg width="14" height="14" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
|
99
|
+
<path d="M10 17s-8-5-8-10a5 5 0 0 1 8-4 5 5 0 0 1 8 4c0 5-8 10-8 10Z" stroke="currentColor" stroke-width="1.6"/>
|
|
100
|
+
</svg>
|
|
101
|
+
<span class="ic-back-label">Likes</span>
|
|
102
|
+
<span class="ic-back-value">{(record.likes as number).toLocaleString()}</span>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</article>
|
|
106
|
+
{/snippet}
|
|
107
|
+
</CardGrid>
|
|
108
|
+
|
|
109
|
+
<style>
|
|
110
|
+
/* ── Shared ───────────────────────────────────────────────────────────── */
|
|
111
|
+
|
|
112
|
+
.ic-card {
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
height: 100%;
|
|
116
|
+
overflow: hidden;
|
|
117
|
+
border-radius: 10px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.ic-body {
|
|
121
|
+
padding: 12px 14px 14px;
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
gap: 3px;
|
|
125
|
+
flex: 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* ── Front face ───────────────────────────────────────────────────────── */
|
|
129
|
+
|
|
130
|
+
.ic-image-wrap {
|
|
131
|
+
position: relative;
|
|
132
|
+
overflow: hidden;
|
|
133
|
+
aspect-ratio: 16 / 10;
|
|
134
|
+
background: var(--cg-border-subtle, #f4f6f9);
|
|
135
|
+
flex-shrink: 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.ic-image {
|
|
139
|
+
width: 100%;
|
|
140
|
+
height: 100%;
|
|
141
|
+
object-fit: cover;
|
|
142
|
+
display: block;
|
|
143
|
+
transition: transform 300ms ease;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.ic-card:hover .ic-image {
|
|
147
|
+
transform: scale(1.04);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.ic-category {
|
|
151
|
+
position: absolute;
|
|
152
|
+
top: 10px;
|
|
153
|
+
left: 10px;
|
|
154
|
+
background: rgba(0, 0, 0, 0.48);
|
|
155
|
+
color: #fff;
|
|
156
|
+
font-size: 0.7rem;
|
|
157
|
+
font-weight: 600;
|
|
158
|
+
letter-spacing: 0.04em;
|
|
159
|
+
text-transform: uppercase;
|
|
160
|
+
padding: 3px 8px;
|
|
161
|
+
border-radius: 999px;
|
|
162
|
+
backdrop-filter: blur(4px);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.ic-title {
|
|
166
|
+
font-size: 0.9rem;
|
|
167
|
+
font-weight: 600;
|
|
168
|
+
color: var(--cg-text, #0f172a);
|
|
169
|
+
margin: 0;
|
|
170
|
+
white-space: nowrap;
|
|
171
|
+
overflow: hidden;
|
|
172
|
+
text-overflow: ellipsis;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.ic-photographer {
|
|
176
|
+
font-size: 0.78rem;
|
|
177
|
+
color: var(--cg-text-muted, #64748b);
|
|
178
|
+
margin: 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.ic-selected .ic-image-wrap::after {
|
|
182
|
+
content: '';
|
|
183
|
+
position: absolute;
|
|
184
|
+
inset: 0;
|
|
185
|
+
box-shadow: inset 0 0 0 3px var(--cg-accent, #6366f1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* ── Back face ────────────────────────────────────────────────────────── */
|
|
189
|
+
|
|
190
|
+
.ic-back-image-wrap {
|
|
191
|
+
position: relative;
|
|
192
|
+
overflow: hidden;
|
|
193
|
+
aspect-ratio: 16 / 10;
|
|
194
|
+
flex-shrink: 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.ic-back-image {
|
|
198
|
+
width: 100%;
|
|
199
|
+
height: 100%;
|
|
200
|
+
object-fit: cover;
|
|
201
|
+
display: block;
|
|
202
|
+
filter: brightness(0.45) saturate(0.7);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.ic-back-overlay {
|
|
206
|
+
position: absolute;
|
|
207
|
+
inset: 0;
|
|
208
|
+
display: flex;
|
|
209
|
+
flex-direction: column;
|
|
210
|
+
justify-content: flex-end;
|
|
211
|
+
padding: 12px 14px;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.ic-back-title {
|
|
215
|
+
font-size: 0.95rem;
|
|
216
|
+
font-weight: 700;
|
|
217
|
+
color: #fff;
|
|
218
|
+
margin: 0;
|
|
219
|
+
line-height: 1.3;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.ic-back-photographer {
|
|
223
|
+
font-size: 0.75rem;
|
|
224
|
+
color: rgba(255,255,255,.75);
|
|
225
|
+
margin: 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.ic-back-body {
|
|
229
|
+
gap: 10px;
|
|
230
|
+
justify-content: center;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.ic-back-stat {
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: center;
|
|
236
|
+
gap: 8px;
|
|
237
|
+
color: var(--cg-text-muted, #64748b);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.ic-back-label {
|
|
241
|
+
font-size: 0.78rem;
|
|
242
|
+
flex: 1;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.ic-back-value {
|
|
246
|
+
font-size: 0.82rem;
|
|
247
|
+
font-weight: 600;
|
|
248
|
+
color: var(--cg-text, #0f172a);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.ic-selected .ic-back-image-wrap::after {
|
|
252
|
+
content: '';
|
|
253
|
+
position: absolute;
|
|
254
|
+
inset: 0;
|
|
255
|
+
box-shadow: inset 0 0 0 3px var(--cg-accent, #6366f1);
|
|
256
|
+
}
|
|
257
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const ImageCardStory: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type ImageCardStory = InstanceType<typeof ImageCardStory>;
|
|
18
|
+
export default ImageCardStory;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { GridColumnFormat } from '../Types/generic_grid.js';
|
|
2
|
+
export type { GridColumnSortOrder, GridColumnFilters } from '../Types/generic_grid.js';
|
|
3
|
+
export type { GridlerAdapterConfig, GridlerPageResult } from '../Gridler/types.js';
|
|
4
|
+
export interface CardGridColumn {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
/** Field path on the record; defaults to id. Supports dot notation. */
|
|
8
|
+
dataKey?: string;
|
|
9
|
+
format?: GridColumnFormat;
|
|
10
|
+
disableSort?: boolean;
|
|
11
|
+
disableFilter?: boolean;
|
|
12
|
+
disableSearch?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface CardSortOption {
|
|
15
|
+
columnId: string;
|
|
16
|
+
label: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CardGridContextMenuItem {
|
|
19
|
+
id: string;
|
|
20
|
+
label: string;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
icon?: string;
|
|
23
|
+
kind?: 'item' | 'separator';
|
|
24
|
+
onselect?: (rowData?: Record<string, unknown>) => void;
|
|
25
|
+
}
|
|
26
|
+
/** Add or remove an item from the selection by uniqueID match. */
|
|
27
|
+
export declare function toggleItemInSelection(items: Record<string, unknown>[], item: Record<string, unknown>, uniqueID: string): Record<string, unknown>[];
|
|
28
|
+
/** Return all items between anchor and target indices, inclusive. */
|
|
29
|
+
export declare function rangeSelect(allItems: Record<string, unknown>[], anchorIndex: number, targetIndex: number): Record<string, unknown>[];
|
|
30
|
+
/** Returns true if item is in selectedItems by uniqueID. */
|
|
31
|
+
export declare function isItemSelected(selectedItems: Record<string, unknown>[], item: Record<string, unknown>, uniqueID: string): boolean;
|
|
32
|
+
/** Compute how many cards fit in a row given container width, min card width, and gap. */
|
|
33
|
+
export declare function computeColumnsInRow(containerWidth: number, cardMinWidth: number, gap: number): number;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Add or remove an item from the selection by uniqueID match. */
|
|
2
|
+
export function toggleItemInSelection(items, item, uniqueID) {
|
|
3
|
+
const idx = items.findIndex((i) => i[uniqueID] === item[uniqueID]);
|
|
4
|
+
if (idx === -1)
|
|
5
|
+
return [...items, item];
|
|
6
|
+
return items.filter((_, i) => i !== idx);
|
|
7
|
+
}
|
|
8
|
+
/** Return all items between anchor and target indices, inclusive. */
|
|
9
|
+
export function rangeSelect(allItems, anchorIndex, targetIndex) {
|
|
10
|
+
const start = Math.min(anchorIndex, targetIndex);
|
|
11
|
+
const end = Math.max(anchorIndex, targetIndex);
|
|
12
|
+
return allItems.slice(start, end + 1);
|
|
13
|
+
}
|
|
14
|
+
/** Returns true if item is in selectedItems by uniqueID. */
|
|
15
|
+
export function isItemSelected(selectedItems, item, uniqueID) {
|
|
16
|
+
return selectedItems.some((s) => s[uniqueID] === item[uniqueID]);
|
|
17
|
+
}
|
|
18
|
+
/** Compute how many cards fit in a row given container width, min card width, and gap. */
|
|
19
|
+
export function computeColumnsInRow(containerWidth, cardMinWidth, gap) {
|
|
20
|
+
return Math.max(1, Math.floor((containerWidth + gap) / (cardMinWidth + gap)));
|
|
21
|
+
}
|
|
@@ -1137,6 +1137,7 @@
|
|
|
1137
1137
|
style:--gridler-bg-header-focus={mergedTheme.bgHeaderHasFocus}
|
|
1138
1138
|
style:--gridler-bg-search-result={mergedTheme.bgSearchResult}
|
|
1139
1139
|
style:--gridler-header-height={`${headerHeight}px`}
|
|
1140
|
+
style:--gridler-row-marker-width={`${rowMarkerWidth}px`}
|
|
1140
1141
|
onfocusin={handleFocusIn}
|
|
1141
1142
|
onfocusout={handleFocusOut}
|
|
1142
1143
|
onmouseenter={onGridEnter}
|
|
@@ -970,7 +970,10 @@
|
|
|
970
970
|
.gf-loading,
|
|
971
971
|
.gf-error {
|
|
972
972
|
position: absolute;
|
|
973
|
-
|
|
973
|
+
top: var(--gridler-header-height, 36px);
|
|
974
|
+
left: var(--gridler-row-marker-width, 0px);
|
|
975
|
+
right: 0;
|
|
976
|
+
bottom: 0;
|
|
974
977
|
display: flex;
|
|
975
978
|
align-items: center;
|
|
976
979
|
justify-content: center;
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
const { value, onValueChange, onClose }: Props = $props();
|
|
11
11
|
|
|
12
|
-
let inputRef: HTMLInputElement | undefined
|
|
12
|
+
let inputRef: HTMLInputElement | undefined;
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
export function focus() {
|
|
15
15
|
inputRef?.focus();
|
|
16
|
-
}
|
|
16
|
+
}
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<div class="gridler-search" role="search">
|
|
@@ -3,6 +3,8 @@ interface Props {
|
|
|
3
3
|
onValueChange: (v: string) => void;
|
|
4
4
|
onClose?: () => void;
|
|
5
5
|
}
|
|
6
|
-
declare const GridlerSearch: import("svelte").Component<Props, {
|
|
6
|
+
declare const GridlerSearch: import("svelte").Component<Props, {
|
|
7
|
+
focus: () => void;
|
|
8
|
+
}, "">;
|
|
7
9
|
type GridlerSearch = ReturnType<typeof GridlerSearch>;
|
|
8
10
|
export default GridlerSearch;
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const { show, value, onToggle, onValueChange, onClose }: Props = $props();
|
|
14
|
+
|
|
15
|
+
let search: GridlerSearch | undefined = $state();
|
|
16
|
+
|
|
17
|
+
$effect(() => {
|
|
18
|
+
if (show) search?.focus();
|
|
19
|
+
});
|
|
14
20
|
</script>
|
|
15
21
|
|
|
16
22
|
<button
|
|
@@ -25,6 +31,7 @@
|
|
|
25
31
|
|
|
26
32
|
{#if show}
|
|
27
33
|
<GridlerSearch
|
|
34
|
+
bind:this={search}
|
|
28
35
|
{value}
|
|
29
36
|
{onValueChange}
|
|
30
37
|
{onClose}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GridColumn, GridCommonProps, GridColumnSortOrder, GridColumnFilters } from '../Types/generic_grid';
|
|
1
|
+
import type { GridColumn, GridCommonProps, GridColumnSortOrder, GridColumnFilters, GridContextMenuItem } from '../Types/generic_grid';
|
|
2
2
|
import type { Options } from '@warkypublic/resolvespec-js';
|
|
3
3
|
export type { GridColumnSortOrder, GridColumnFilters };
|
|
4
4
|
export type Item = [col: number, row: number];
|
|
@@ -60,8 +60,6 @@ export interface GridlerColumn extends GridColumn<Record<string, unknown>> {
|
|
|
60
60
|
width: number;
|
|
61
61
|
/** Per-column theme overrides applied during canvas rendering. */
|
|
62
62
|
themeOverride?: Partial<GridlerTheme>;
|
|
63
|
-
/** Show a context-menu trigger button on this column's header. */
|
|
64
|
-
hasMenu?: boolean;
|
|
65
63
|
}
|
|
66
64
|
export interface GridlerCellFormatOptions {
|
|
67
65
|
/** Currency code for 'currency' cells (default: 'ZAR'). */
|
|
@@ -166,14 +164,6 @@ export interface GridlerProps extends GridCommonProps<Record<string, unknown>> {
|
|
|
166
164
|
/** Show the built-in search bar. Also settable via settings.showSearch. */
|
|
167
165
|
showSearch?: boolean;
|
|
168
166
|
}
|
|
169
|
-
export
|
|
170
|
-
id: string;
|
|
171
|
-
label: string;
|
|
172
|
-
disabled?: boolean;
|
|
173
|
-
icon?: string;
|
|
174
|
-
/** Use 'separator' to render a divider line. */
|
|
175
|
-
kind?: 'item' | 'separator';
|
|
176
|
-
onselect?: (rowData?: Record<string, unknown>) => void;
|
|
177
|
-
}
|
|
167
|
+
export type GridlerContextMenuItem = GridContextMenuItem<Record<string, unknown>>;
|
|
178
168
|
export declare const DEFAULT_THEME: GridlerTheme;
|
|
179
169
|
export declare const DEFAULT_THEME_DARK: GridlerTheme;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { getNestedValue } from "@warkypublic/artemis-kit/object";
|
|
1
2
|
export function rowToCell(row, key) {
|
|
2
3
|
if (row === undefined) {
|
|
3
4
|
return { kind: "text", data: null, displayData: "", allowOverlay: false, allowEditing: false };
|
|
4
5
|
}
|
|
5
|
-
const raw = key ?
|
|
6
|
+
const raw = key ? getNestedValue(key, row) : undefined;
|
|
6
7
|
const display = raw == null ? "" : String(raw);
|
|
7
8
|
if (typeof raw === "number") {
|
|
8
9
|
return { kind: "number", data: raw, displayData: display, allowOverlay: true, allowEditing: true };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getNestedValue } from "@warkypublic/artemis-kit/object";
|
|
1
2
|
export function gridColumnFiltersToFilterOptions(f) {
|
|
2
3
|
return Object.entries(f).map(([id, filter]) => ({
|
|
3
4
|
column: filter.dataKey ?? id,
|
|
@@ -15,8 +16,7 @@ export function applyLocalFilter(data, filters, columns) {
|
|
|
15
16
|
for (const [colId, filter] of entries) {
|
|
16
17
|
const colDef = columns.find((c) => c.id === colId);
|
|
17
18
|
const key = colDef?.dataKey ?? colId;
|
|
18
|
-
|
|
19
|
-
const rawVal = row[key];
|
|
19
|
+
const rawVal = getNestedValue(key, row);
|
|
20
20
|
const cellStr = rawVal == null ? "" : String(rawVal).toLowerCase();
|
|
21
21
|
const filterVal = filter.value?.toLowerCase() ?? "";
|
|
22
22
|
const op = filter.op ?? "contains";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getNestedValue } from "@warkypublic/artemis-kit/object";
|
|
1
2
|
export function compareValues(a, b) {
|
|
2
3
|
if (a == null && b == null)
|
|
3
4
|
return 0;
|
|
@@ -27,7 +28,7 @@ export function applyLocalSort(data, sortOptions, columns) {
|
|
|
27
28
|
for (const { column, direction } of sortOptions) {
|
|
28
29
|
const colDef = columns.find((c) => c.id === column);
|
|
29
30
|
const key = colDef?.dataKey ?? column;
|
|
30
|
-
const cmp = compareValues(a
|
|
31
|
+
const cmp = compareValues(getNestedValue(key, a), getNestedValue(key, b));
|
|
31
32
|
if (cmp !== 0)
|
|
32
33
|
return direction === "asc" ? cmp : -cmp;
|
|
33
34
|
}
|
|
@@ -25,6 +25,8 @@ export interface GridColumn<RowDataType = unknown, CellType = unknown> {
|
|
|
25
25
|
disableSearch?: boolean | ((item: RowDataType) => boolean);
|
|
26
26
|
span?: [startRow: number, endRow: number];
|
|
27
27
|
renderCell?: (item: RowDataType) => CellType;
|
|
28
|
+
/** Show a context-menu trigger button on this column's header. */
|
|
29
|
+
hasMenu?: boolean;
|
|
28
30
|
}
|
|
29
31
|
export interface GridColumnSortOrder {
|
|
30
32
|
[id: string]: "asc" | "desc" | "none";
|
|
@@ -44,34 +46,74 @@ export interface GridEventCoords {
|
|
|
44
46
|
code?: string;
|
|
45
47
|
}
|
|
46
48
|
export type GridEventDetail = Record<string, unknown>;
|
|
49
|
+
/** Generic context-menu item. Implementations (e.g. GridlerContextMenuItem) extend this. */
|
|
50
|
+
export interface GridContextMenuItem<RowDataType = unknown> {
|
|
51
|
+
id: string;
|
|
52
|
+
label: string;
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
icon?: string;
|
|
55
|
+
/** Use 'separator' to render a divider line. */
|
|
56
|
+
kind?: 'item' | 'separator';
|
|
57
|
+
onselect?: (rowData?: RowDataType) => void;
|
|
58
|
+
}
|
|
47
59
|
export interface GridCommonProps<RowDataType = unknown, CellType = unknown> {
|
|
48
60
|
columns: Array<GridColumn<RowDataType, CellType>>;
|
|
49
61
|
width?: string | number;
|
|
50
62
|
height?: string | number;
|
|
51
63
|
rowHeight?: number;
|
|
52
64
|
headerHeight?: number;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
/** Number of columns pinned to the left. */
|
|
66
|
+
fixedColumns?: number;
|
|
67
|
+
/** When true, the grid manages column reordering internally on drag. */
|
|
68
|
+
manageColumns?: boolean;
|
|
69
|
+
/** Called when the internal column order changes (only fires when manageColumns is true). */
|
|
70
|
+
onColumnsChange?: (columns: GridColumn<RowDataType, CellType>[]) => void;
|
|
71
|
+
onCellEvent?: (type: GridCellEventType, item: RowDataType, column: GridColumn<RowDataType, CellType>, coords?: GridEventCoords, detail?: GridEventDetail) => void;
|
|
72
|
+
onGridEvent?: (type: GridEventType, item?: RowDataType, column?: GridColumn<RowDataType, CellType>, coords?: GridEventCoords, detail?: GridEventDetail) => void;
|
|
57
73
|
onMenuClick?: (item?: RowDataType, column?: GridColumn<RowDataType, CellType>, coords?: GridEventCoords) => Promise<void>;
|
|
74
|
+
/** Called when a body row is clicked. */
|
|
75
|
+
onRowClick?: (row: number, rowData?: RowDataType) => void;
|
|
76
|
+
/** Called when a body row is double-clicked. */
|
|
77
|
+
onRowDblClick?: (row: number, rowData?: RowDataType) => void;
|
|
78
|
+
/** Called when a body row receives a context-menu event. */
|
|
79
|
+
onRowContextMenu?: (row: number, rowData?: RowDataType, x?: number, y?: number) => void;
|
|
80
|
+
/** Called when a cell is double-clicked. */
|
|
81
|
+
onCellDblClick?: (row: number, column: GridColumn<RowDataType, CellType>, rowData?: RowDataType) => void;
|
|
58
82
|
onColumnMoved?: (startIndex: number, endIndex: number) => void;
|
|
59
83
|
onSortOrderChange?: (sortOrder: GridColumnSortOrder) => void;
|
|
60
84
|
sortOrder?: GridColumnSortOrder;
|
|
61
85
|
onFilterChange?: (filters: GridColumnFilters) => void;
|
|
62
86
|
filters?: GridColumnFilters;
|
|
63
|
-
onSearchValueChange?: (value: string) => void;
|
|
64
87
|
searchValue?: string;
|
|
88
|
+
/** Uncontrolled initial search value (only used when searchValue is undefined). */
|
|
89
|
+
defaultSearchValue?: string;
|
|
90
|
+
onSearchValueChange?: (value: string) => void;
|
|
91
|
+
/**
|
|
92
|
+
* When true, the search input value is translated into server-side filters
|
|
93
|
+
* instead of only being used for client-side highlighting.
|
|
94
|
+
*/
|
|
95
|
+
serverSideSearch?: boolean;
|
|
96
|
+
/** Columns searched when serverSideSearch is true. Defaults to all columns. */
|
|
97
|
+
searchColumns?: string[];
|
|
65
98
|
selectedItems?: RowDataType[];
|
|
66
99
|
onSelectedItemsChange?: (items: RowDataType[]) => void;
|
|
100
|
+
/** Extra items appended to the built-in context menu. */
|
|
101
|
+
menuItems?: GridContextMenuItem<RowDataType>[];
|
|
102
|
+
/** Called when any context menu item is selected (built-in or custom). */
|
|
103
|
+
onMenuItemSelect?: (item: GridContextMenuItem<RowDataType>, rowData?: RowDataType) => void;
|
|
67
104
|
dataSource?: "local" | "resolvespec" | "headerspec" | "websockspec" | "custom";
|
|
68
105
|
dataSourceOptions?: {
|
|
69
106
|
url?: string;
|
|
70
107
|
authToken?: string;
|
|
71
108
|
schema?: string;
|
|
72
109
|
entity?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Row field whose value is used as the cursor for subsequent page requests.
|
|
112
|
+
* Defaults to 'id'. Can also be set via the top-level uniqueID shorthand.
|
|
113
|
+
*/
|
|
73
114
|
uniqueID?: string;
|
|
74
115
|
headers?: Record<string, string>;
|
|
116
|
+
/** Fields to fetch in addition to column fields (for filters, sort, relations). */
|
|
75
117
|
hotfields?: string[];
|
|
76
118
|
/**
|
|
77
119
|
* Default ResolveSpec options sent with every request. Grid-controlled sort, filters, limit,
|
|
@@ -93,6 +135,32 @@ export interface GridCommonProps<RowDataType = unknown, CellType = unknown> {
|
|
|
93
135
|
};
|
|
94
136
|
data?: RowDataType[] | (() => Promise<RowDataType[]>);
|
|
95
137
|
onDataChange?: (data: RowDataType[]) => Promise<void>;
|
|
138
|
+
/** Rows per cursor-forward page. Defaults to 200. */
|
|
139
|
+
pageSize?: number;
|
|
140
|
+
/**
|
|
141
|
+
* Shorthand for dataSourceOptions.uniqueID.
|
|
142
|
+
* Row field whose value is passed as cursor_forward on subsequent requests.
|
|
143
|
+
*/
|
|
144
|
+
uniqueID?: string;
|
|
145
|
+
/** Called when a server fetch fails. */
|
|
146
|
+
onLoadError?: (error: string) => void;
|
|
147
|
+
/**
|
|
148
|
+
* Bindable. Reflects the server-reported total row count.
|
|
149
|
+
* Updated after every successful page fetch; 0 until the first response.
|
|
150
|
+
*/
|
|
151
|
+
total?: number;
|
|
152
|
+
/**
|
|
153
|
+
* Bindable. true while any server fetch is in flight.
|
|
154
|
+
*/
|
|
155
|
+
loading?: boolean;
|
|
156
|
+
/** When true, all cells are read-only. Also settable via settings.readonly. */
|
|
157
|
+
readonly?: boolean;
|
|
158
|
+
/** Allow users to drag column edges to resize. Also settable via settings.resizableColumns. */
|
|
159
|
+
resizableColumns?: boolean;
|
|
160
|
+
/** Show the built-in search bar. Also settable via settings.showSearch. */
|
|
161
|
+
showSearch?: boolean;
|
|
162
|
+
/** Row-marker style shown in the leading column. Also settable via settings.rowMarkerType. */
|
|
163
|
+
rowMarkers?: "checkbox" | "number" | "none";
|
|
96
164
|
settings?: {
|
|
97
165
|
hideHeader?: boolean;
|
|
98
166
|
showRowMarkers?: boolean;
|
package/dist/components/index.js
CHANGED