@warkypublic/svelix 0.1.31 → 0.1.32
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 +150 -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,59 @@ 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 ||
|
|
187
|
+
!resolvedSession.authToken));
|
|
188
|
+
if (isInvalidSession) {
|
|
189
|
+
const clearedState = createClearedAuthenticatedState(currentState);
|
|
190
|
+
setGlobalState((state) => ({
|
|
191
|
+
...state,
|
|
192
|
+
...result,
|
|
193
|
+
layout: { ...state.layout, ...result?.layout },
|
|
194
|
+
navigation: { ...state.navigation, ...result?.navigation },
|
|
195
|
+
owner: { ...clearedState.owner, ...result?.owner },
|
|
196
|
+
program: {
|
|
197
|
+
...clearedState.program,
|
|
198
|
+
...result?.program,
|
|
199
|
+
updatedAt: new Date().toISOString(),
|
|
200
|
+
},
|
|
201
|
+
session: {
|
|
202
|
+
...clearedState.session,
|
|
203
|
+
apiURL: url || state.session.apiURL,
|
|
204
|
+
},
|
|
205
|
+
user: clearedState.user,
|
|
206
|
+
}));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
99
209
|
setGlobalState((state) => ({
|
|
100
210
|
...state,
|
|
101
211
|
...result,
|
|
@@ -107,21 +217,17 @@ const createGlobalStateStore = () => {
|
|
|
107
217
|
...result?.program,
|
|
108
218
|
updatedAt: new Date().toISOString(),
|
|
109
219
|
},
|
|
110
|
-
session:
|
|
111
|
-
|
|
112
|
-
...result?.session,
|
|
113
|
-
connected: true,
|
|
114
|
-
loading: false,
|
|
115
|
-
},
|
|
116
|
-
user: { ...state.user, ...result?.user },
|
|
220
|
+
session: resolvedSession,
|
|
221
|
+
user: nextUser,
|
|
117
222
|
}));
|
|
118
223
|
}
|
|
119
224
|
catch (e) {
|
|
225
|
+
const error = `Load Exception: ${String(e)}`;
|
|
120
226
|
setGlobalState((state) => ({
|
|
121
227
|
session: {
|
|
122
228
|
...state.session,
|
|
123
229
|
connected: false,
|
|
124
|
-
error
|
|
230
|
+
error,
|
|
125
231
|
loading: false,
|
|
126
232
|
},
|
|
127
233
|
}));
|
|
@@ -134,10 +240,10 @@ const createGlobalStateStore = () => {
|
|
|
134
240
|
},
|
|
135
241
|
isLoggedIn: () => {
|
|
136
242
|
const session = getState().session;
|
|
137
|
-
if (!session.loggedIn || !session.authToken) {
|
|
243
|
+
if (!session.validated || !session.loggedIn || !session.authToken) {
|
|
138
244
|
return false;
|
|
139
245
|
}
|
|
140
|
-
if (
|
|
246
|
+
if (isSessionExpired(session)) {
|
|
141
247
|
return false;
|
|
142
248
|
}
|
|
143
249
|
return true;
|
|
@@ -152,7 +258,8 @@ const createGlobalStateStore = () => {
|
|
|
152
258
|
authToken: authToken ?? "",
|
|
153
259
|
expiryDate: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
154
260
|
loading: true,
|
|
155
|
-
loggedIn:
|
|
261
|
+
loggedIn: false,
|
|
262
|
+
validated: false,
|
|
156
263
|
},
|
|
157
264
|
user: {
|
|
158
265
|
...state.user,
|
|
@@ -162,6 +269,7 @@ const createGlobalStateStore = () => {
|
|
|
162
269
|
const currentState = getState();
|
|
163
270
|
const result = await currentState.onLogin?.(currentState);
|
|
164
271
|
if (result) {
|
|
272
|
+
const loginValidated = hasSessionValidationSignal(result.session, result.user);
|
|
165
273
|
setGlobalState((state) => ({
|
|
166
274
|
owner: result.owner
|
|
167
275
|
? { ...state.owner, ...result.owner }
|
|
@@ -169,9 +277,13 @@ const createGlobalStateStore = () => {
|
|
|
169
277
|
program: result.program
|
|
170
278
|
? { ...state.program, ...result.program }
|
|
171
279
|
: state.program,
|
|
172
|
-
session:
|
|
173
|
-
|
|
174
|
-
|
|
280
|
+
session: {
|
|
281
|
+
...state.session,
|
|
282
|
+
...result.session,
|
|
283
|
+
loggedIn: result.session?.loggedIn ??
|
|
284
|
+
(loginValidated && Boolean(result.session?.authToken ?? state.session.authToken)),
|
|
285
|
+
validated: loginValidated ? true : state.session.validated,
|
|
286
|
+
},
|
|
175
287
|
user: result.user
|
|
176
288
|
? { ...state.user, ...result.user }
|
|
177
289
|
: state.user,
|
|
@@ -180,14 +292,14 @@ const createGlobalStateStore = () => {
|
|
|
180
292
|
await fetchDataInternal();
|
|
181
293
|
}
|
|
182
294
|
catch (e) {
|
|
295
|
+
const currentState = getState();
|
|
296
|
+
const clearedState = createClearedAuthenticatedState(currentState, {
|
|
297
|
+
connected: false,
|
|
298
|
+
error: `Login Exception: ${String(e)}`,
|
|
299
|
+
});
|
|
183
300
|
setGlobalState((state) => ({
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
connected: false,
|
|
187
|
-
error: `Login Exception: ${String(e)}`,
|
|
188
|
-
loading: false,
|
|
189
|
-
loggedIn: false,
|
|
190
|
-
},
|
|
301
|
+
...state,
|
|
302
|
+
...clearedState,
|
|
191
303
|
}));
|
|
192
304
|
}
|
|
193
305
|
finally {
|
|
@@ -203,6 +315,7 @@ const createGlobalStateStore = () => {
|
|
|
203
315
|
logout: async () => {
|
|
204
316
|
await waitForInitialization();
|
|
205
317
|
return withOperationLock(async () => {
|
|
318
|
+
const previousState = getState();
|
|
206
319
|
try {
|
|
207
320
|
setGlobalState((state) => ({
|
|
208
321
|
...createInitialState(),
|
|
@@ -215,7 +328,7 @@ const createGlobalStateStore = () => {
|
|
|
215
328
|
},
|
|
216
329
|
}));
|
|
217
330
|
const currentState = getState();
|
|
218
|
-
const result = await currentState.onLogout?.(
|
|
331
|
+
const result = await currentState.onLogout?.(previousState);
|
|
219
332
|
if (result) {
|
|
220
333
|
setGlobalState((state) => ({
|
|
221
334
|
owner: result.owner
|
|
@@ -235,13 +348,14 @@ const createGlobalStateStore = () => {
|
|
|
235
348
|
await fetchDataInternal();
|
|
236
349
|
}
|
|
237
350
|
catch (e) {
|
|
351
|
+
const currentState = getState();
|
|
352
|
+
const clearedState = createClearedAuthenticatedState(currentState, {
|
|
353
|
+
connected: false,
|
|
354
|
+
error: `Logout Exception: ${String(e)}`,
|
|
355
|
+
});
|
|
238
356
|
setGlobalState((state) => ({
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
connected: false,
|
|
242
|
-
error: `Logout Exception: ${String(e)}`,
|
|
243
|
-
loading: false,
|
|
244
|
-
},
|
|
357
|
+
...state,
|
|
358
|
+
...clearedState,
|
|
245
359
|
}));
|
|
246
360
|
}
|
|
247
361
|
finally {
|
|
@@ -319,6 +433,7 @@ const createGlobalStateStore = () => {
|
|
|
319
433
|
store.set(initialStoreState);
|
|
320
434
|
initializationPromise = loadStorage()
|
|
321
435
|
.then((loadedState) => {
|
|
436
|
+
const hasPersistedSession = hasSessionCredentials(loadedState.session);
|
|
322
437
|
setGlobalState((current) => ({
|
|
323
438
|
...current,
|
|
324
439
|
...loadedState,
|
|
@@ -328,6 +443,12 @@ const createGlobalStateStore = () => {
|
|
|
328
443
|
...loadedState.session,
|
|
329
444
|
connected: true,
|
|
330
445
|
loading: false,
|
|
446
|
+
loggedIn: hasPersistedSession
|
|
447
|
+
? false
|
|
448
|
+
: loadedState.session?.loggedIn ?? current.session.loggedIn,
|
|
449
|
+
validated: hasPersistedSession
|
|
450
|
+
? false
|
|
451
|
+
: loadedState.session?.validated ?? current.session.validated,
|
|
331
452
|
},
|
|
332
453
|
}));
|
|
333
454
|
})
|
|
@@ -386,4 +507,4 @@ const GetGlobalState = () => {
|
|
|
386
507
|
const setLanguage = (lang) => {
|
|
387
508
|
GlobalStateStore.getState().setLanguage(lang);
|
|
388
509
|
};
|
|
389
|
-
export { getApiURL, getAuthToken, GetGlobalState, GlobalStateStore, isLoggedIn, languageStore, setApiURL, setAuthToken, setLanguage, useGlobalStateStore, };
|
|
510
|
+
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. */
|