@warkypublic/svelix 0.1.31 → 0.1.33
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/GlobalStateStore/GlobalStateStore.d.ts +2 -1
- package/dist/components/GlobalStateStore/GlobalStateStore.js +149 -29
- package/dist/components/GlobalStateStore/GlobalStateStore.types.d.ts +1 -0
- package/dist/components/GlobalStateStore/GlobalStateStore.utils.js +2 -1
- package/dist/components/Gridler/components/Gridler.svelte +10 -0
- package/dist/components/Gridler/components/Gridler.svelte.d.ts +2 -0
- package/dist/components/Gridler/components/GridlerCanvas.svelte +25 -2
- package/dist/components/Gridler/components/GridlerCanvas.svelte.d.ts +2 -0
- package/dist/components/Gridler/components/GridlerFull.svelte +24 -0
- package/dist/components/Gridler/components/GridlerFull.svelte.d.ts +5 -1
- package/dist/components/Gridler/types.d.ts +4 -0
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ type GlobalStateStoreApi = {
|
|
|
6
6
|
setState: (partial: PartialUpdater, replace?: boolean) => void;
|
|
7
7
|
subscribe: (run: (value: GlobalStateStoreType) => void) => () => void;
|
|
8
8
|
};
|
|
9
|
+
declare const createGlobalStateStore: () => GlobalStateStoreApi;
|
|
9
10
|
declare const GlobalStateStore: GlobalStateStoreApi;
|
|
10
11
|
declare const languageStore: Readable<SupportedLanguage>;
|
|
11
12
|
declare function useGlobalStateStore(): Readable<GlobalStateStoreType>;
|
|
@@ -18,4 +19,4 @@ declare const setAuthToken: (token: string) => void;
|
|
|
18
19
|
declare const GetGlobalState: () => GlobalStateStoreType;
|
|
19
20
|
declare const setLanguage: (lang: SupportedLanguage) => void;
|
|
20
21
|
export type { GlobalStateStoreApi };
|
|
21
|
-
export { getApiURL, getAuthToken, GetGlobalState, GlobalStateStore, isLoggedIn, languageStore, setApiURL, setAuthToken, setLanguage, useGlobalStateStore, };
|
|
22
|
+
export { createGlobalStateStore, getApiURL, getAuthToken, GetGlobalState, GlobalStateStore, isLoggedIn, languageStore, setApiURL, setAuthToken, setLanguage, useGlobalStateStore, };
|
|
@@ -30,6 +30,7 @@ const createInitialState = () => ({
|
|
|
30
30
|
connected: true,
|
|
31
31
|
loading: false,
|
|
32
32
|
loggedIn: false,
|
|
33
|
+
validated: true,
|
|
33
34
|
},
|
|
34
35
|
user: {
|
|
35
36
|
guid: "",
|
|
@@ -45,6 +46,62 @@ const toPersistedState = (state) => ({
|
|
|
45
46
|
session: state.session,
|
|
46
47
|
user: state.user,
|
|
47
48
|
});
|
|
49
|
+
const isSessionExpired = (session) => {
|
|
50
|
+
if (!session?.expiryDate) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return new Date(session.expiryDate) < new Date();
|
|
54
|
+
};
|
|
55
|
+
const hasSessionCredentials = (session) => {
|
|
56
|
+
return Boolean(session?.authToken || session?.loggedIn);
|
|
57
|
+
};
|
|
58
|
+
const createClearedAuthenticatedState = (state, options) => {
|
|
59
|
+
const initialState = createInitialState();
|
|
60
|
+
return {
|
|
61
|
+
owner: initialState.owner,
|
|
62
|
+
program: initialState.program,
|
|
63
|
+
session: {
|
|
64
|
+
...initialState.session,
|
|
65
|
+
apiURL: state.session.apiURL,
|
|
66
|
+
connected: options?.connected ?? true,
|
|
67
|
+
error: options?.error,
|
|
68
|
+
loading: options?.loading ?? false,
|
|
69
|
+
validated: true,
|
|
70
|
+
},
|
|
71
|
+
user: {
|
|
72
|
+
...initialState.user,
|
|
73
|
+
language: state.user.language ?? initialState.user.language,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
const getSessionValidationMismatch = ({ fetchedSession, fetchedUser, previousState, }) => {
|
|
78
|
+
const previousAuthToken = previousState.session.authToken ?? "";
|
|
79
|
+
const previousGuid = previousState.user.guid ?? "";
|
|
80
|
+
const previousUsername = previousState.user.username ?? "";
|
|
81
|
+
if (fetchedSession?.loggedIn === false) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (previousAuthToken &&
|
|
85
|
+
fetchedSession?.authToken &&
|
|
86
|
+
fetchedSession.authToken !== previousAuthToken) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (previousGuid && fetchedUser?.guid && fetchedUser.guid !== previousGuid) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (previousUsername &&
|
|
93
|
+
fetchedUser?.username &&
|
|
94
|
+
fetchedUser.username !== previousUsername) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
};
|
|
99
|
+
const hasSessionValidationSignal = (session, user) => {
|
|
100
|
+
return Boolean(session?.loggedIn === true ||
|
|
101
|
+
session?.authToken ||
|
|
102
|
+
user?.guid ||
|
|
103
|
+
user?.username);
|
|
104
|
+
};
|
|
48
105
|
const createGlobalStateStore = () => {
|
|
49
106
|
const initialState = createInitialState();
|
|
50
107
|
let isStorageInitialized = false;
|
|
@@ -96,6 +153,58 @@ const createGlobalStateStore = () => {
|
|
|
96
153
|
try {
|
|
97
154
|
const currentState = getState();
|
|
98
155
|
const result = await currentState.onFetchSession?.(currentState);
|
|
156
|
+
const hasExistingSession = hasSessionCredentials(currentState.session);
|
|
157
|
+
const nextUser = { ...currentState.user, ...result?.user };
|
|
158
|
+
const nextSession = {
|
|
159
|
+
...currentState.session,
|
|
160
|
+
...result?.session,
|
|
161
|
+
apiURL: url || currentState.session.apiURL,
|
|
162
|
+
connected: true,
|
|
163
|
+
loading: false,
|
|
164
|
+
};
|
|
165
|
+
const serverConfirmsSession = result?.session?.loggedIn === true || Boolean(result?.session?.authToken);
|
|
166
|
+
const serverConfirmsUser = Boolean(result?.user?.guid || result?.user?.username);
|
|
167
|
+
const alreadyValidated = currentState.session.validated === true;
|
|
168
|
+
const requiresValidation = (hasExistingSession && !alreadyValidated) || serverConfirmsSession;
|
|
169
|
+
const hasValidationSignal = serverConfirmsSession || serverConfirmsUser || alreadyValidated;
|
|
170
|
+
const hasValidationMismatch = getSessionValidationMismatch({
|
|
171
|
+
fetchedSession: result?.session,
|
|
172
|
+
fetchedUser: result?.user,
|
|
173
|
+
previousState: currentState,
|
|
174
|
+
});
|
|
175
|
+
const resolvedLoggedIn = result?.session?.loggedIn ??
|
|
176
|
+
(hasValidationSignal && Boolean(nextSession.authToken));
|
|
177
|
+
const resolvedSession = {
|
|
178
|
+
...nextSession,
|
|
179
|
+
loggedIn: resolvedLoggedIn,
|
|
180
|
+
validated: !requiresValidation || hasValidationSignal,
|
|
181
|
+
};
|
|
182
|
+
const isInvalidSession = hasValidationMismatch ||
|
|
183
|
+
isSessionExpired(resolvedSession) ||
|
|
184
|
+
(requiresValidation &&
|
|
185
|
+
hasValidationSignal &&
|
|
186
|
+
(!resolvedSession.loggedIn || !resolvedSession.authToken));
|
|
187
|
+
if (isInvalidSession) {
|
|
188
|
+
const clearedState = createClearedAuthenticatedState(currentState);
|
|
189
|
+
setGlobalState((state) => ({
|
|
190
|
+
...state,
|
|
191
|
+
...result,
|
|
192
|
+
layout: { ...state.layout, ...result?.layout },
|
|
193
|
+
navigation: { ...state.navigation, ...result?.navigation },
|
|
194
|
+
owner: { ...clearedState.owner, ...result?.owner },
|
|
195
|
+
program: {
|
|
196
|
+
...clearedState.program,
|
|
197
|
+
...result?.program,
|
|
198
|
+
updatedAt: new Date().toISOString(),
|
|
199
|
+
},
|
|
200
|
+
session: {
|
|
201
|
+
...clearedState.session,
|
|
202
|
+
apiURL: url || state.session.apiURL,
|
|
203
|
+
},
|
|
204
|
+
user: clearedState.user,
|
|
205
|
+
}));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
99
208
|
setGlobalState((state) => ({
|
|
100
209
|
...state,
|
|
101
210
|
...result,
|
|
@@ -107,21 +216,17 @@ const createGlobalStateStore = () => {
|
|
|
107
216
|
...result?.program,
|
|
108
217
|
updatedAt: new Date().toISOString(),
|
|
109
218
|
},
|
|
110
|
-
session:
|
|
111
|
-
|
|
112
|
-
...result?.session,
|
|
113
|
-
connected: true,
|
|
114
|
-
loading: false,
|
|
115
|
-
},
|
|
116
|
-
user: { ...state.user, ...result?.user },
|
|
219
|
+
session: resolvedSession,
|
|
220
|
+
user: nextUser,
|
|
117
221
|
}));
|
|
118
222
|
}
|
|
119
223
|
catch (e) {
|
|
224
|
+
const error = `Load Exception: ${String(e)}`;
|
|
120
225
|
setGlobalState((state) => ({
|
|
121
226
|
session: {
|
|
122
227
|
...state.session,
|
|
123
228
|
connected: false,
|
|
124
|
-
error
|
|
229
|
+
error,
|
|
125
230
|
loading: false,
|
|
126
231
|
},
|
|
127
232
|
}));
|
|
@@ -134,10 +239,10 @@ const createGlobalStateStore = () => {
|
|
|
134
239
|
},
|
|
135
240
|
isLoggedIn: () => {
|
|
136
241
|
const session = getState().session;
|
|
137
|
-
if (!session.loggedIn || !session.authToken) {
|
|
242
|
+
if (!session.validated || !session.loggedIn || !session.authToken) {
|
|
138
243
|
return false;
|
|
139
244
|
}
|
|
140
|
-
if (
|
|
245
|
+
if (isSessionExpired(session)) {
|
|
141
246
|
return false;
|
|
142
247
|
}
|
|
143
248
|
return true;
|
|
@@ -152,7 +257,8 @@ const createGlobalStateStore = () => {
|
|
|
152
257
|
authToken: authToken ?? "",
|
|
153
258
|
expiryDate: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
154
259
|
loading: true,
|
|
155
|
-
loggedIn:
|
|
260
|
+
loggedIn: false,
|
|
261
|
+
validated: false,
|
|
156
262
|
},
|
|
157
263
|
user: {
|
|
158
264
|
...state.user,
|
|
@@ -162,6 +268,7 @@ const createGlobalStateStore = () => {
|
|
|
162
268
|
const currentState = getState();
|
|
163
269
|
const result = await currentState.onLogin?.(currentState);
|
|
164
270
|
if (result) {
|
|
271
|
+
const loginValidated = hasSessionValidationSignal(result.session, result.user);
|
|
165
272
|
setGlobalState((state) => ({
|
|
166
273
|
owner: result.owner
|
|
167
274
|
? { ...state.owner, ...result.owner }
|
|
@@ -169,9 +276,13 @@ const createGlobalStateStore = () => {
|
|
|
169
276
|
program: result.program
|
|
170
277
|
? { ...state.program, ...result.program }
|
|
171
278
|
: state.program,
|
|
172
|
-
session:
|
|
173
|
-
|
|
174
|
-
|
|
279
|
+
session: {
|
|
280
|
+
...state.session,
|
|
281
|
+
...result.session,
|
|
282
|
+
loggedIn: result.session?.loggedIn ??
|
|
283
|
+
(loginValidated && Boolean(result.session?.authToken ?? state.session.authToken)),
|
|
284
|
+
validated: loginValidated ? true : state.session.validated,
|
|
285
|
+
},
|
|
175
286
|
user: result.user
|
|
176
287
|
? { ...state.user, ...result.user }
|
|
177
288
|
: state.user,
|
|
@@ -180,14 +291,14 @@ const createGlobalStateStore = () => {
|
|
|
180
291
|
await fetchDataInternal();
|
|
181
292
|
}
|
|
182
293
|
catch (e) {
|
|
294
|
+
const currentState = getState();
|
|
295
|
+
const clearedState = createClearedAuthenticatedState(currentState, {
|
|
296
|
+
connected: false,
|
|
297
|
+
error: `Login Exception: ${String(e)}`,
|
|
298
|
+
});
|
|
183
299
|
setGlobalState((state) => ({
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
connected: false,
|
|
187
|
-
error: `Login Exception: ${String(e)}`,
|
|
188
|
-
loading: false,
|
|
189
|
-
loggedIn: false,
|
|
190
|
-
},
|
|
300
|
+
...state,
|
|
301
|
+
...clearedState,
|
|
191
302
|
}));
|
|
192
303
|
}
|
|
193
304
|
finally {
|
|
@@ -203,6 +314,7 @@ const createGlobalStateStore = () => {
|
|
|
203
314
|
logout: async () => {
|
|
204
315
|
await waitForInitialization();
|
|
205
316
|
return withOperationLock(async () => {
|
|
317
|
+
const previousState = getState();
|
|
206
318
|
try {
|
|
207
319
|
setGlobalState((state) => ({
|
|
208
320
|
...createInitialState(),
|
|
@@ -215,7 +327,7 @@ const createGlobalStateStore = () => {
|
|
|
215
327
|
},
|
|
216
328
|
}));
|
|
217
329
|
const currentState = getState();
|
|
218
|
-
const result = await currentState.onLogout?.(
|
|
330
|
+
const result = await currentState.onLogout?.(previousState);
|
|
219
331
|
if (result) {
|
|
220
332
|
setGlobalState((state) => ({
|
|
221
333
|
owner: result.owner
|
|
@@ -235,13 +347,14 @@ const createGlobalStateStore = () => {
|
|
|
235
347
|
await fetchDataInternal();
|
|
236
348
|
}
|
|
237
349
|
catch (e) {
|
|
350
|
+
const currentState = getState();
|
|
351
|
+
const clearedState = createClearedAuthenticatedState(currentState, {
|
|
352
|
+
connected: false,
|
|
353
|
+
error: `Logout Exception: ${String(e)}`,
|
|
354
|
+
});
|
|
238
355
|
setGlobalState((state) => ({
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
connected: false,
|
|
242
|
-
error: `Logout Exception: ${String(e)}`,
|
|
243
|
-
loading: false,
|
|
244
|
-
},
|
|
356
|
+
...state,
|
|
357
|
+
...clearedState,
|
|
245
358
|
}));
|
|
246
359
|
}
|
|
247
360
|
finally {
|
|
@@ -319,6 +432,7 @@ const createGlobalStateStore = () => {
|
|
|
319
432
|
store.set(initialStoreState);
|
|
320
433
|
initializationPromise = loadStorage()
|
|
321
434
|
.then((loadedState) => {
|
|
435
|
+
const hasPersistedSession = hasSessionCredentials(loadedState.session);
|
|
322
436
|
setGlobalState((current) => ({
|
|
323
437
|
...current,
|
|
324
438
|
...loadedState,
|
|
@@ -328,6 +442,12 @@ const createGlobalStateStore = () => {
|
|
|
328
442
|
...loadedState.session,
|
|
329
443
|
connected: true,
|
|
330
444
|
loading: false,
|
|
445
|
+
loggedIn: hasPersistedSession
|
|
446
|
+
? false
|
|
447
|
+
: loadedState.session?.loggedIn ?? current.session.loggedIn,
|
|
448
|
+
validated: hasPersistedSession
|
|
449
|
+
? false
|
|
450
|
+
: loadedState.session?.validated ?? current.session.validated,
|
|
331
451
|
},
|
|
332
452
|
}));
|
|
333
453
|
})
|
|
@@ -386,4 +506,4 @@ const GetGlobalState = () => {
|
|
|
386
506
|
const setLanguage = (lang) => {
|
|
387
507
|
GlobalStateStore.getState().setLanguage(lang);
|
|
388
508
|
};
|
|
389
|
-
export { getApiURL, getAuthToken, GetGlobalState, GlobalStateStore, isLoggedIn, languageStore, setApiURL, setAuthToken, setLanguage, useGlobalStateStore, };
|
|
509
|
+
export { createGlobalStateStore, getApiURL, getAuthToken, GetGlobalState, GlobalStateStore, isLoggedIn, languageStore, setApiURL, setAuthToken, setLanguage, useGlobalStateStore, };
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
item?: Record<string, unknown>;
|
|
29
29
|
column?: GridlerColumn;
|
|
30
30
|
}) => void;
|
|
31
|
+
onRowClick?: (row: number) => void;
|
|
32
|
+
onRowContextMenu?: (row: number) => void;
|
|
31
33
|
/** Resolve raw row data by row index for populating onCellEvent. */
|
|
32
34
|
getRowData?: (row: number) => Record<string, unknown> | undefined;
|
|
33
35
|
children?: Snippet;
|
|
@@ -59,6 +61,8 @@
|
|
|
59
61
|
onHeaderContextMenu,
|
|
60
62
|
onMenuClick,
|
|
61
63
|
onGridMenuOpen,
|
|
64
|
+
onRowClick,
|
|
65
|
+
onRowContextMenu,
|
|
62
66
|
theme = {},
|
|
63
67
|
selection = { type: "none" } as Selection,
|
|
64
68
|
onSelectionChange,
|
|
@@ -434,6 +438,9 @@
|
|
|
434
438
|
onCellEvent?.("dblclick", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
435
439
|
onGridEvent?.("dblclick", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
436
440
|
}}
|
|
441
|
+
onRowClick={(row) => {
|
|
442
|
+
onRowClick?.(row);
|
|
443
|
+
}}
|
|
437
444
|
onCellClick={(item) => {
|
|
438
445
|
onCellEvent?.("click", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
439
446
|
onGridEvent?.("click", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
@@ -450,6 +457,9 @@
|
|
|
450
457
|
onCellEvent?.("contextmenu", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" }, { x, y });
|
|
451
458
|
onGridEvent?.("contextmenu", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" }, { x, y });
|
|
452
459
|
}}
|
|
460
|
+
onRowContextMenu={(row) => {
|
|
461
|
+
onRowContextMenu?.(row);
|
|
462
|
+
}}
|
|
453
463
|
onColumnResized={(col, width) => {
|
|
454
464
|
onGridEvent?.("column_resized", undefined, columns[col], undefined, { width });
|
|
455
465
|
}}
|
|
@@ -13,6 +13,8 @@ export interface Props extends Partial<GridlerProps> {
|
|
|
13
13
|
item?: Record<string, unknown>;
|
|
14
14
|
column?: GridlerColumn;
|
|
15
15
|
}) => void;
|
|
16
|
+
onRowClick?: (row: number) => void;
|
|
17
|
+
onRowContextMenu?: (row: number) => void;
|
|
16
18
|
/** Resolve raw row data by row index for populating onCellEvent. */
|
|
17
19
|
getRowData?: (row: number) => Record<string, unknown> | undefined;
|
|
18
20
|
children?: Snippet;
|
|
@@ -56,6 +56,8 @@
|
|
|
56
56
|
onVisibleRangeChange?: (range: VisibleRange) => void;
|
|
57
57
|
onSelectionChange?: (sel: Selection) => void;
|
|
58
58
|
onCellDblClick?: (item: Item, cell: GridlerCell) => void;
|
|
59
|
+
onRowClick?: (row: number) => void;
|
|
60
|
+
onRowContextMenu?: (row: number) => void;
|
|
59
61
|
onColumnMoved?: (from: number, to: number) => void;
|
|
60
62
|
onCellEdited?: (item: Item, cell: GridlerCell) => void;
|
|
61
63
|
onDelete?: (sel: Selection) => void;
|
|
@@ -107,6 +109,8 @@
|
|
|
107
109
|
onVisibleRangeChange,
|
|
108
110
|
onSelectionChange,
|
|
109
111
|
onCellDblClick,
|
|
112
|
+
onRowClick,
|
|
113
|
+
onRowContextMenu,
|
|
110
114
|
onColumnMoved,
|
|
111
115
|
onCellEdited: _onCellEdited,
|
|
112
116
|
onDelete: _onDelete,
|
|
@@ -484,7 +488,7 @@
|
|
|
484
488
|
// ── Header context menu ───────────────────────────────────────────────────────
|
|
485
489
|
|
|
486
490
|
function handleContextMenu(e: MouseEvent) {
|
|
487
|
-
if (e.offsetX < rowMarkerWidth) return;
|
|
491
|
+
if (e.offsetY < headerHeight && e.offsetX < rowMarkerWidth) return;
|
|
488
492
|
const fixedBoundaryX =
|
|
489
493
|
fixedColumns > 0
|
|
490
494
|
? getColumnX(effectiveColumns, Math.min(fixedColumns, columns.length))
|
|
@@ -503,6 +507,14 @@
|
|
|
503
507
|
return;
|
|
504
508
|
}
|
|
505
509
|
|
|
510
|
+
const row = getRowFromOffsetY(e.offsetY);
|
|
511
|
+
if (row !== null) {
|
|
512
|
+
e.preventDefault();
|
|
513
|
+
onRowContextMenu?.(row);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (e.offsetX < rowMarkerWidth) return;
|
|
517
|
+
|
|
506
518
|
if (onCellContextMenu) {
|
|
507
519
|
const cell = getCellFromPoint(
|
|
508
520
|
localX,
|
|
@@ -537,6 +549,12 @@
|
|
|
537
549
|
|
|
538
550
|
// ── Mouse handlers ────────────────────────────────────────────────────────────
|
|
539
551
|
|
|
552
|
+
function getRowFromOffsetY(offsetY: number): number | null {
|
|
553
|
+
if (offsetY < headerHeight) return null;
|
|
554
|
+
const row = Math.floor((offsetY - headerHeight + scrollY) / rowHeight);
|
|
555
|
+
return row >= 0 && row < rows ? row : null;
|
|
556
|
+
}
|
|
557
|
+
|
|
540
558
|
function handleMouseDown(e: MouseEvent) {
|
|
541
559
|
if (e.button !== 0) return;
|
|
542
560
|
pendingGridMenuOpen = false;
|
|
@@ -630,6 +648,11 @@
|
|
|
630
648
|
return;
|
|
631
649
|
}
|
|
632
650
|
|
|
651
|
+
const row = getRowFromOffsetY(e.offsetY);
|
|
652
|
+
if (row !== null) {
|
|
653
|
+
onRowClick?.(row);
|
|
654
|
+
}
|
|
655
|
+
|
|
633
656
|
if (readonly) return;
|
|
634
657
|
|
|
635
658
|
const cell = getCellFromPoint(
|
|
@@ -1055,7 +1078,7 @@
|
|
|
1055
1078
|
}
|
|
1056
1079
|
|
|
1057
1080
|
if (rowMarkers !== "none" && offsetX < rowMarkerWidth) {
|
|
1058
|
-
const row =
|
|
1081
|
+
const row = getRowFromOffsetY(offsetY);
|
|
1059
1082
|
if (row >= 0 && row < rows) {
|
|
1060
1083
|
onRowToggle?.(row);
|
|
1061
1084
|
}
|
|
@@ -25,6 +25,8 @@ interface Props {
|
|
|
25
25
|
onVisibleRangeChange?: (range: VisibleRange) => void;
|
|
26
26
|
onSelectionChange?: (sel: Selection) => void;
|
|
27
27
|
onCellDblClick?: (item: Item, cell: GridlerCell) => void;
|
|
28
|
+
onRowClick?: (row: number) => void;
|
|
29
|
+
onRowContextMenu?: (row: number) => void;
|
|
28
30
|
onColumnMoved?: (from: number, to: number) => void;
|
|
29
31
|
onCellEdited?: (item: Item, cell: GridlerCell) => void;
|
|
30
32
|
onDelete?: (sel: Selection) => void;
|
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
| "onSearchValueChange"
|
|
39
39
|
| "onVisibleRangeChange"
|
|
40
40
|
| "onCellDblClick"
|
|
41
|
+
| "onRowClick"
|
|
42
|
+
| "onRowContextMenu"
|
|
41
43
|
> {
|
|
42
44
|
columns: GridlerColumn[];
|
|
43
45
|
|
|
@@ -81,6 +83,16 @@
|
|
|
81
83
|
row: number,
|
|
82
84
|
rowData: Record<string, unknown> | undefined,
|
|
83
85
|
) => void;
|
|
86
|
+
/** Called on row click with the row index and resolved row data. */
|
|
87
|
+
onRowClick?: (
|
|
88
|
+
row: number,
|
|
89
|
+
rowData: Record<string, unknown> | undefined,
|
|
90
|
+
) => void;
|
|
91
|
+
/** Called on row context-menu with the row index and resolved row data. */
|
|
92
|
+
onRowContextMenu?: (
|
|
93
|
+
row: number,
|
|
94
|
+
rowData: Record<string, unknown> | undefined,
|
|
95
|
+
) => void;
|
|
84
96
|
|
|
85
97
|
// ── Server-side fetching ──────────────────────────────────────────────────
|
|
86
98
|
// Use dataSource + dataSourceOptions (from GridCommonProps) for server config.
|
|
@@ -157,6 +169,8 @@
|
|
|
157
169
|
onCellEdited,
|
|
158
170
|
onCellDblClick,
|
|
159
171
|
onRowDblClick,
|
|
172
|
+
onRowClick,
|
|
173
|
+
onRowContextMenu,
|
|
160
174
|
onMenuClick,
|
|
161
175
|
onSelectionChange,
|
|
162
176
|
onCellEvent,
|
|
@@ -656,6 +670,14 @@
|
|
|
656
670
|
onRowDblClick?.(item[1], rowData);
|
|
657
671
|
}
|
|
658
672
|
|
|
673
|
+
function handleRowClick(row: number) {
|
|
674
|
+
onRowClick?.(row, resolveRowData(row));
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function handleRowContextMenu(row: number) {
|
|
678
|
+
onRowContextMenu?.(row, resolveRowData(row));
|
|
679
|
+
}
|
|
680
|
+
|
|
659
681
|
// ── Selection → selectedItems bridge ──────────────────────────────────────
|
|
660
682
|
|
|
661
683
|
let internalSelectedItems = $state<Record<string, unknown>[]>([]);
|
|
@@ -777,6 +799,8 @@
|
|
|
777
799
|
onCellEdited?.(item, value);
|
|
778
800
|
}}
|
|
779
801
|
onCellDblClick={handleCellDblClick}
|
|
802
|
+
onRowClick={handleRowClick}
|
|
803
|
+
onRowContextMenu={handleRowContextMenu}
|
|
780
804
|
selectedItems={internalSelectedItems}
|
|
781
805
|
getRowData={resolveRowData}
|
|
782
806
|
onSelectionChange={handleSelectionChange}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
import type { GridlerCell, GridlerColumn, GridlerContextMenuItem, GridlerProps, Item } from "../types";
|
|
3
|
-
export interface Props extends Omit<Partial<GridlerProps>, "columns" | "rows" | "getCellContent" | "searchValue" | "onSearchValueChange" | "onVisibleRangeChange" | "onCellDblClick"> {
|
|
3
|
+
export interface Props extends Omit<Partial<GridlerProps>, "columns" | "rows" | "getCellContent" | "searchValue" | "onSearchValueChange" | "onVisibleRangeChange" | "onCellDblClick" | "onRowClick" | "onRowContextMenu"> {
|
|
4
4
|
columns: GridlerColumn[];
|
|
5
5
|
/**
|
|
6
6
|
* Optional row data. When provided (and `getCellContent` is omitted), the default
|
|
@@ -28,6 +28,10 @@ export interface Props extends Omit<Partial<GridlerProps>, "columns" | "rows" |
|
|
|
28
28
|
onCellDblClick?: (item: Item, cell: GridlerCell, rowData: Record<string, unknown> | undefined) => void;
|
|
29
29
|
/** Called on row double-click with the row index and resolved row data. */
|
|
30
30
|
onRowDblClick?: (row: number, rowData: Record<string, unknown> | undefined) => void;
|
|
31
|
+
/** Called on row click with the row index and resolved row data. */
|
|
32
|
+
onRowClick?: (row: number, rowData: Record<string, unknown> | undefined) => void;
|
|
33
|
+
/** Called on row context-menu with the row index and resolved row data. */
|
|
34
|
+
onRowContextMenu?: (row: number, rowData: Record<string, unknown> | undefined) => void;
|
|
31
35
|
/** Rows per cursor-forward page. Defaults to 200. */
|
|
32
36
|
pageSize?: number;
|
|
33
37
|
/**
|
|
@@ -103,6 +103,10 @@ export interface GridlerProps extends GridCommonProps<Record<string, unknown>> {
|
|
|
103
103
|
onCellEdited?: (item: Item, value: GridlerCell) => void;
|
|
104
104
|
/** Called on cell double-click (low-level; GridlerFull enriches with row data). */
|
|
105
105
|
onCellDblClick?: (item: Item, cell: GridlerCell) => void;
|
|
106
|
+
/** Called when a body row is clicked. */
|
|
107
|
+
onRowClick?: (row: number) => void;
|
|
108
|
+
/** Called when a body row receives a context-menu event. */
|
|
109
|
+
onRowContextMenu?: (row: number) => void;
|
|
106
110
|
/** Called when Tab moves past the last column of the last row. */
|
|
107
111
|
onRowAppended?: () => void;
|
|
108
112
|
/** Called when Delete/Backspace is pressed with a selection. */
|