@warkypublic/svelix 0.1.29 → 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.
@@ -97,6 +97,7 @@
97
97
  let dropdownZ = $state(1100);
98
98
  let pointerInteractingWithDropdown = $state(false);
99
99
  let insideDialog = $state(false);
100
+ let suppressOpenOnFocus = $state(false);
100
101
  const effectiveDisablePortal = $derived(disablePortal || insideDialog);
101
102
  // Plain variable — NOT $state to avoid deep proxy on the complex virtualizer object.
102
103
  let rawVirtualizer: SvelteVirtualizer<
@@ -303,6 +304,12 @@
303
304
  activeOptionIndex = null;
304
305
  }
305
306
  break;
307
+ case "Tab":
308
+ if (multiSelect && $store.opened && $store.boxerData.length > 0) {
309
+ e.preventDefault();
310
+ onOptionSubmit(0);
311
+ }
312
+ break;
306
313
  }
307
314
  }
308
315
 
@@ -331,6 +338,7 @@
331
338
  e.preventDefault();
332
339
  store.setOpened(false);
333
340
  activeOptionIndex = null;
341
+ suppressOpenOnFocus = true;
334
342
  targetRef?.focus();
335
343
  break;
336
344
  }
@@ -387,6 +395,7 @@
387
395
  activeOptionIndex = null;
388
396
  }
389
397
  export function focus() {
398
+ suppressOpenOnFocus = false;
390
399
  targetRef?.focus();
391
400
  }
392
401
  export function getValue() {
@@ -504,7 +513,13 @@
504
513
  isFetching={$store.isFetching}
505
514
  search={$store.input}
506
515
  onclear={onClear}
507
- onfocus={() => store.setOpened(true)}
516
+ onfocus={() => {
517
+ if (suppressOpenOnFocus) {
518
+ suppressOpenOnFocus = false;
519
+ return;
520
+ }
521
+ store.setOpened(true);
522
+ }}
508
523
  onkeydown={onInputKeydown}
509
524
  onsearch={(v) => {
510
525
  store.setSearch(v);
@@ -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
- ...state.session,
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: `Load Exception: ${String(e)}`,
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 (session.expiryDate && new Date(session.expiryDate) < new Date()) {
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: true,
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: result.session
173
- ? { ...state.session, ...result.session }
174
- : state.session,
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
- session: {
185
- ...state.session,
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?.(currentState);
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
- session: {
240
- ...state.session,
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, };
@@ -107,6 +107,7 @@ interface SessionState {
107
107
  loggedIn?: boolean;
108
108
  meta?: Record<string, any>;
109
109
  parameters?: Record<string, any>;
110
+ validated?: boolean;
110
111
  }
111
112
  type SupportedLanguage = 'af' | 'en';
112
113
  interface ThemeSettings {
@@ -3,7 +3,8 @@ const SKIP_PATHS = new Set([
3
3
  'app.controls',
4
4
  'session.connected',
5
5
  'session.error',
6
- 'session.loading'
6
+ 'session.loading',
7
+ 'session.validated'
7
8
  ]);
8
9
  const PERSIST_KEYS = [
9
10
  'layout',
@@ -28,6 +28,10 @@
28
28
  item?: Record<string, unknown>;
29
29
  column?: GridlerColumn;
30
30
  }) => void;
31
+ onRowClick?: (row: number) => void;
32
+ onRowContextMenu?: (row: number) => void;
33
+ /** Resolve raw row data by row index for populating onCellEvent. */
34
+ getRowData?: (row: number) => Record<string, unknown> | undefined;
31
35
  children?: Snippet;
32
36
  }
33
37
 
@@ -57,6 +61,8 @@
57
61
  onHeaderContextMenu,
58
62
  onMenuClick,
59
63
  onGridMenuOpen,
64
+ onRowClick,
65
+ onRowContextMenu,
60
66
  theme = {},
61
67
  selection = { type: "none" } as Selection,
62
68
  onSelectionChange,
@@ -68,8 +74,9 @@
68
74
  sortOrder,
69
75
  onFilterChange: _onFilterChange,
70
76
  filters: _filters,
71
- selectedItems: _selectedItems,
77
+ selectedItems,
72
78
  onSelectedItemsChange: _onSelectedItemsChange,
79
+ getRowData,
73
80
  settings,
74
81
  children,
75
82
  }: Props = $props();
@@ -245,6 +252,7 @@
245
252
 
246
253
  function handleKeyDown(e: KeyboardEvent) {
247
254
  if (!canvasComponent?.getHasFocus()) return;
255
+ onGridEvent?.("keydown", undefined, undefined, { x: 0, y: 0, code: e.key });
248
256
 
249
257
  // Search toggle
250
258
  if ((e.ctrlKey || e.metaKey) && e.key === "f") {
@@ -308,7 +316,7 @@
308
316
  if (!resolvedReadonly) {
309
317
  beginEdit(focusedCell);
310
318
  }
311
- onCellEvent?.("enter_key", {}, columns[col] ?? { id: "", title: "" });
319
+ onCellEvent?.("enter_key", getRowData?.(row) ?? {}, columns[col] ?? { id: "", title: "" });
312
320
  e.preventDefault();
313
321
  return;
314
322
  case "Escape": {
@@ -334,7 +342,7 @@
334
342
  onRowAppended?.();
335
343
  }
336
344
  }
337
- onCellEvent?.("tab_key", {}, columns[col] ?? { id: "", title: "" });
345
+ onCellEvent?.("tab_key", getRowData?.(row) ?? {}, columns[col] ?? { id: "", title: "" });
338
346
  e.preventDefault();
339
347
  break;
340
348
  case "Delete":
@@ -346,7 +354,7 @@
346
354
  onDelete?.(delSel);
347
355
  }
348
356
  }
349
- onCellEvent?.("delte_key", {}, columns[col] ?? { id: "", title: "" });
357
+ onCellEvent?.("delete_key", getRowData?.(row) ?? {}, columns[col] ?? { id: "", title: "" });
350
358
  return;
351
359
  }
352
360
  default:
@@ -376,7 +384,7 @@
376
384
 
377
385
  function handleVisibleRangeChange(range: VisibleRange) {
378
386
  onVisibleRangeChange?.(range);
379
- onGridEvent?.("scroll", { row: range.firstRow, column: range.firstCol });
387
+ onGridEvent?.("scroll", undefined, undefined, undefined, { row: range.firstRow, column: range.firstCol });
380
388
  }
381
389
 
382
390
  async function handleMenuOpen(d: {
@@ -423,15 +431,51 @@
423
431
  onSelectionChange={(sel) => {
424
432
  currentSelection = sel;
425
433
  onSelectionChange?.(sel);
434
+ onGridEvent?.("selection_changed");
426
435
  }}
427
436
  onCellDblClick={(item, cell) => {
428
437
  onCellDblClick?.(item, cell);
429
- onCellEvent?.("dblclick", {}, columns[item[0]] ?? { id: "", title: "" });
438
+ onCellEvent?.("dblclick", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
439
+ onGridEvent?.("dblclick", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
440
+ }}
441
+ onRowClick={(row) => {
442
+ onRowClick?.(row);
443
+ }}
444
+ onCellClick={(item) => {
445
+ onCellEvent?.("click", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
446
+ onGridEvent?.("click", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
447
+ }}
448
+ onCellHover={(item) => {
449
+ onCellEvent?.("hover", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
450
+ onGridEvent?.("hover", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
451
+ }}
452
+ onCellLeave={(item) => {
453
+ onCellEvent?.("leave", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
454
+ onGridEvent?.("leave", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
455
+ }}
456
+ onCellContextMenu={(item, x, y) => {
457
+ onCellEvent?.("contextmenu", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" }, { x, y });
458
+ onGridEvent?.("contextmenu", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" }, { x, y });
459
+ }}
460
+ onRowContextMenu={(row) => {
461
+ onRowContextMenu?.(row);
462
+ }}
463
+ onColumnResized={(col, width) => {
464
+ onGridEvent?.("column_resized", undefined, columns[col], undefined, { width });
465
+ }}
466
+ onGridResize={(width, height) => {
467
+ onGridEvent?.("resize", undefined, undefined, undefined, { width, height });
468
+ }}
469
+ onGridEnter={() => {
470
+ onGridEvent?.("enter");
430
471
  }}
431
472
  {sortOrder}
432
473
  {onSortOrderChange}
433
474
  {onHeaderContextMenu}
434
- {onColumnMoved}
475
+ onColumnMoved={onColumnMoved || onGridEvent ? (from, to) => {
476
+ onColumnMoved?.(from, to);
477
+ onGridEvent?.("column_moved", undefined, undefined, undefined, { from, to });
478
+ } : undefined}
435
479
  {onCellEdited}
436
480
  {onDelete}
437
481
  {onRowAppended}
@@ -13,6 +13,10 @@ 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;
18
+ /** Resolve raw row data by row index for populating onCellEvent. */
19
+ getRowData?: (row: number) => Record<string, unknown> | undefined;
16
20
  children?: Snippet;
17
21
  }
18
22
  declare const Gridler: import("svelte").Component<Props, {}, "">;
@@ -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;
@@ -72,6 +74,13 @@
72
74
  item?: Record<string, unknown>;
73
75
  column?: GridColumn<Record<string, unknown>>;
74
76
  }) => void;
77
+ onCellClick?: (item: Item) => void;
78
+ onCellHover?: (item: Item) => void;
79
+ onCellLeave?: (item: Item) => void;
80
+ onCellContextMenu?: (item: Item, x: number, y: number) => void;
81
+ onColumnResized?: (col: number, width: number) => void;
82
+ onGridResize?: (width: number, height: number) => void;
83
+ onGridEnter?: () => void;
75
84
  onkeydown?: (e: KeyboardEvent) => void;
76
85
  children?: Snippet;
77
86
  }
@@ -100,6 +109,8 @@
100
109
  onVisibleRangeChange,
101
110
  onSelectionChange,
102
111
  onCellDblClick,
112
+ onRowClick,
113
+ onRowContextMenu,
103
114
  onColumnMoved,
104
115
  onCellEdited: _onCellEdited,
105
116
  onDelete: _onDelete,
@@ -111,6 +122,13 @@
111
122
  onSearchValueChange,
112
123
  onSearchClose,
113
124
  onGridMenuOpen,
125
+ onCellClick,
126
+ onCellHover,
127
+ onCellLeave,
128
+ onCellContextMenu,
129
+ onColumnResized,
130
+ onGridResize,
131
+ onGridEnter,
114
132
  onkeydown,
115
133
  children,
116
134
  }: Props = $props();
@@ -133,6 +151,7 @@
133
151
  let hasFocus = $state(false);
134
152
  const gridId = `gridler-${Math.random().toString(36).slice(2, 10)}`;
135
153
 
154
+ let hoveredCell = $state<Item | null>(null);
136
155
  let isDraggingVScrollbar = $state(false);
137
156
  let isDraggingHScrollbar = $state(false);
138
157
  let vScrollDragStartY = $state(0);
@@ -286,6 +305,7 @@
286
305
  if (entry) {
287
306
  containerWidth = entry.contentRect.width;
288
307
  containerHeight = entry.contentRect.height;
308
+ onGridResize?.(entry.contentRect.width, entry.contentRect.height);
289
309
  }
290
310
  });
291
311
  observer.observe(containerRef);
@@ -468,8 +488,7 @@
468
488
  // ── Header context menu ───────────────────────────────────────────────────────
469
489
 
470
490
  function handleContextMenu(e: MouseEvent) {
471
- if (!onHeaderContextMenu) return;
472
- if (e.offsetY >= headerHeight || e.offsetX < rowMarkerWidth) return;
491
+ if (e.offsetY < headerHeight && e.offsetX < rowMarkerWidth) return;
473
492
  const fixedBoundaryX =
474
493
  fixedColumns > 0
475
494
  ? getColumnX(effectiveColumns, Math.min(fixedColumns, columns.length))
@@ -477,10 +496,39 @@
477
496
  const localX = e.offsetX - rowMarkerWidth;
478
497
  const effectiveScrollX =
479
498
  fixedColumns > 0 && localX >= 0 && localX < fixedBoundaryX ? 0 : scrollX;
480
- const col = getColumnFromX(localX + effectiveScrollX, effectiveColumns);
481
- if (col >= 0 && col < columns.length) {
499
+
500
+ if (e.offsetY < headerHeight) {
501
+ if (!onHeaderContextMenu) return;
502
+ const col = getColumnFromX(localX + effectiveScrollX, effectiveColumns);
503
+ if (col >= 0 && col < columns.length) {
504
+ e.preventDefault();
505
+ onHeaderContextMenu(col, e.clientX, e.clientY);
506
+ }
507
+ return;
508
+ }
509
+
510
+ const row = getRowFromOffsetY(e.offsetY);
511
+ if (row !== null) {
482
512
  e.preventDefault();
483
- onHeaderContextMenu(col, e.clientX, e.clientY);
513
+ onRowContextMenu?.(row);
514
+ }
515
+
516
+ if (e.offsetX < rowMarkerWidth) return;
517
+
518
+ if (onCellContextMenu) {
519
+ const cell = getCellFromPoint(
520
+ localX,
521
+ e.offsetY,
522
+ effectiveScrollX,
523
+ scrollY,
524
+ effectiveColumns,
525
+ rowHeight,
526
+ headerHeight,
527
+ );
528
+ if (cell) {
529
+ e.preventDefault();
530
+ onCellContextMenu(cell, e.clientX, e.clientY);
531
+ }
484
532
  }
485
533
  }
486
534
 
@@ -501,6 +549,12 @@
501
549
 
502
550
  // ── Mouse handlers ────────────────────────────────────────────────────────────
503
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
+
504
558
  function handleMouseDown(e: MouseEvent) {
505
559
  if (e.button !== 0) return;
506
560
  pendingGridMenuOpen = false;
@@ -594,6 +648,11 @@
594
648
  return;
595
649
  }
596
650
 
651
+ const row = getRowFromOffsetY(e.offsetY);
652
+ if (row !== null) {
653
+ onRowClick?.(row);
654
+ }
655
+
597
656
  if (readonly) return;
598
657
 
599
658
  const cell = getCellFromPoint(
@@ -619,6 +678,7 @@
619
678
  }
620
679
 
621
680
  if (cell) {
681
+ onCellClick?.(cell);
622
682
  if (e.shiftKey && focusedCell) {
623
683
  const newSelection: Selection = {
624
684
  type: "range",
@@ -802,6 +862,36 @@
802
862
  return;
803
863
  }
804
864
 
865
+ // Hover cell tracking
866
+ if (e.offsetY >= headerHeight && e.offsetX >= rowMarkerWidth) {
867
+ const hoverFixedBoundaryX =
868
+ fixedColumns > 0
869
+ ? getColumnX(effectiveColumns, Math.min(fixedColumns, columns.length))
870
+ : 0;
871
+ const hoverLocalX = e.offsetX - rowMarkerWidth;
872
+ const hoverScrollX =
873
+ fixedColumns > 0 && hoverLocalX >= 0 && hoverLocalX < hoverFixedBoundaryX
874
+ ? 0
875
+ : scrollX;
876
+ const hoverCell = getCellFromPoint(
877
+ hoverLocalX,
878
+ e.offsetY,
879
+ hoverScrollX,
880
+ scrollY,
881
+ effectiveColumns,
882
+ rowHeight,
883
+ headerHeight,
884
+ );
885
+ if (hoverCell?.[0] !== hoveredCell?.[0] || hoverCell?.[1] !== hoveredCell?.[1]) {
886
+ if (hoveredCell) onCellLeave?.(hoveredCell);
887
+ hoveredCell = hoverCell;
888
+ if (hoverCell) onCellHover?.(hoverCell);
889
+ }
890
+ } else if (hoveredCell !== null) {
891
+ onCellLeave?.(hoveredCell);
892
+ hoveredCell = null;
893
+ }
894
+
805
895
  if (!isDragging || !dragStart) return;
806
896
 
807
897
  const fixedBoundaryX =
@@ -865,6 +955,9 @@
865
955
  scheduleDraw();
866
956
  }
867
957
 
958
+ if (resizingColumn !== null) {
959
+ onColumnResized?.(resizingColumn, columns[resizingColumn].width);
960
+ }
868
961
  isDragging = false;
869
962
  dragStart = null;
870
963
  resizingColumn = null;
@@ -876,6 +969,10 @@
876
969
  hoverResizeCol = null;
877
970
  hoverMenuButton = false;
878
971
  hoverSortCol = null;
972
+ if (hoveredCell) {
973
+ onCellLeave?.(hoveredCell);
974
+ hoveredCell = null;
975
+ }
879
976
  if (!isDraggingVScrollbar && !isDraggingHScrollbar) {
880
977
  handleMouseUp(e);
881
978
  } else {
@@ -981,7 +1078,7 @@
981
1078
  }
982
1079
 
983
1080
  if (rowMarkers !== "none" && offsetX < rowMarkerWidth) {
984
- const row = Math.floor((offsetY - headerHeight + scrollY) / rowHeight);
1081
+ const row = getRowFromOffsetY(offsetY);
985
1082
  if (row >= 0 && row < rows) {
986
1083
  onRowToggle?.(row);
987
1084
  }
@@ -1073,6 +1170,7 @@
1073
1170
  style:--gridler-header-height={`${headerHeight}px`}
1074
1171
  onfocusin={handleFocusIn}
1075
1172
  onfocusout={handleFocusOut}
1173
+ onmouseenter={onGridEnter}
1076
1174
  {onkeydown}
1077
1175
  >
1078
1176
  <canvas
@@ -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;
@@ -41,6 +43,13 @@ interface Props {
41
43
  item?: Record<string, unknown>;
42
44
  column?: GridColumn<Record<string, unknown>>;
43
45
  }) => void;
46
+ onCellClick?: (item: Item) => void;
47
+ onCellHover?: (item: Item) => void;
48
+ onCellLeave?: (item: Item) => void;
49
+ onCellContextMenu?: (item: Item, x: number, y: number) => void;
50
+ onColumnResized?: (col: number, width: number) => void;
51
+ onGridResize?: (width: number, height: number) => void;
52
+ onGridEnter?: () => void;
44
53
  onkeydown?: (e: KeyboardEvent) => void;
45
54
  children?: Snippet;
46
55
  }
@@ -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,
@@ -199,9 +213,11 @@
199
213
  if (typeof data === "function") {
200
214
  data().then((result) => {
201
215
  dataState = result;
216
+ onGridEvent?.("load");
202
217
  });
203
218
  } else {
204
219
  dataState = data.slice();
220
+ onGridEvent?.("load");
205
221
  }
206
222
  });
207
223
 
@@ -258,6 +274,7 @@
258
274
  internalSearchValue = value;
259
275
  }
260
276
  onSearchValueChange?.(value);
277
+ onGridEvent?.("search_changed", undefined, undefined, undefined, { searchValue: value });
261
278
  }
262
279
 
263
280
  // ── Context menu ───────────────────────────────────────────────────────────
@@ -477,6 +494,7 @@
477
494
  function handleFilterChange(next: GridColumnFilters) {
478
495
  if (filters === undefined) internalFilters = next;
479
496
  onFilterChange?.(next);
497
+ onGridEvent?.("filter_changed", undefined, undefined, undefined, { filters: next });
480
498
  }
481
499
 
482
500
 
@@ -530,6 +548,7 @@
530
548
  function handleSortOrderChange(order: GridColumnSortOrder) {
531
549
  if (sortOrder === undefined) internalSortOrder = order;
532
550
  onSortOrderChange?.(order);
551
+ onGridEvent?.("sort_changed", undefined, undefined, undefined, { sortOrder: order });
533
552
  }
534
553
 
535
554
  const resolvedSort = $derived(
@@ -619,6 +638,7 @@
619
638
  serverData = [...serverData, ...result.data];
620
639
  serverCursor = result.nextCursor;
621
640
  serverAllLoaded = result.data.length < pageSize || !result.nextCursor;
641
+ onGridEvent?.("page_loaded", undefined, undefined, undefined, { data: result.data, total: serverData.length });
622
642
  } catch {
623
643
  // Silent — don't clobber existing rows with an error on append.
624
644
  } finally {
@@ -650,18 +670,49 @@
650
670
  onRowDblClick?.(item[1], rowData);
651
671
  }
652
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
+
653
681
  // ── Selection → selectedItems bridge ──────────────────────────────────────
654
682
 
683
+ let internalSelectedItems = $state<Record<string, unknown>[]>([]);
684
+
655
685
  function handleSelectionChange(sel: Selection) {
656
- if (sel.type === "row" && onSelectedItemsChange) {
657
- const items = sel.rows
686
+ let items: Record<string, unknown>[] = [];
687
+ if (sel.type === "row") {
688
+ items = sel.rows
658
689
  .map((i) => resolveRowData(i))
659
690
  .filter((r): r is Record<string, unknown> => r !== undefined);
660
- onSelectedItemsChange(items);
691
+ } else if (sel.type === "cell") {
692
+ const row = resolveRowData(sel.item[1]);
693
+ if (row) items = [row];
694
+ } else if (sel.type === "range") {
695
+ const minRow = Math.min(sel.start[1], sel.end[1]);
696
+ const maxRow = Math.max(sel.start[1], sel.end[1]);
697
+ for (let i = minRow; i <= maxRow; i++) {
698
+ const row = resolveRowData(i);
699
+ if (row) items.push(row);
700
+ }
661
701
  }
702
+ internalSelectedItems = items;
703
+ if (items.length > 0) onSelectedItemsChange?.(items);
662
704
  onSelectionChange?.(sel);
663
705
  }
664
706
 
707
+ // ── Settings change event ─────────────────────────────────────────────────
708
+
709
+ let lastSettings = $state(settings);
710
+ $effect(() => {
711
+ if (settings === untrack(() => lastSettings)) return;
712
+ lastSettings = settings;
713
+ onGridEvent?.("settings_changed", undefined, undefined, undefined, { settings });
714
+ });
715
+
665
716
  // ── Cell content ───────────────────────────────────────────────────────────
666
717
 
667
718
  const serverGetCellContent = $derived(makeGetCellContent(serverData, columnsState));
@@ -700,6 +751,7 @@
700
751
  };
701
752
  dataState = next;
702
753
  await onDataChange?.(next);
754
+ onGridEvent?.("data_changed", next[originalIndex], columnsState[col], undefined, { data: next });
703
755
  }
704
756
 
705
757
  async function handleGridMenuClick(d?: {
@@ -747,7 +799,12 @@
747
799
  onCellEdited?.(item, value);
748
800
  }}
749
801
  onCellDblClick={handleCellDblClick}
802
+ onRowClick={handleRowClick}
803
+ onRowContextMenu={handleRowContextMenu}
804
+ selectedItems={internalSelectedItems}
805
+ getRowData={resolveRowData}
750
806
  onSelectionChange={handleSelectionChange}
807
+ {onMenuClick}
751
808
  onGridMenuOpen={handleGridMenuClick}
752
809
  sortOrder={resolvedSortOrder}
753
810
  onSortOrderChange={handleSortOrderChange}
@@ -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. */
package/llm/README.md CHANGED
@@ -16,6 +16,7 @@ This folder contains AI-readable documentation that ships with the package.
16
16
  - [tools/SVAR.md](./tools/SVAR.md)
17
17
  - [tools/resolvespec-js.md](./tools/resolvespec-js.md)
18
18
  - [plans/canvasgrid.md](./plans/canvasgrid.md)
19
+ - [gridler-events.md](./gridler-events.md)
19
20
 
20
21
  ## Installed package paths
21
22
 
@@ -0,0 +1,139 @@
1
+ # Gridler Events
2
+
3
+ `GridlerFull` (and the lower-level `Gridler`) emit two event callbacks defined in `GridCommonProps` (`src/lib/components/Types/generic_grid.ts`):
4
+
5
+ | Callback | Purpose |
6
+ |---|---|
7
+ | `onGridEvent` | Grid-level events (interaction, state, lifecycle) |
8
+ | `onCellEvent` | Cell-level events (mouse + keyboard on a specific cell) |
9
+
10
+ ---
11
+
12
+ ## `onGridEvent`
13
+
14
+ ```ts
15
+ onGridEvent?: (
16
+ type: GridEventType,
17
+ item?: Record<string, unknown>, // resolved raw row data
18
+ column?: GridColumn,
19
+ coords?: GridEventCoords, // { x, y, code? }
20
+ detail?: GridEventDetail, // event-specific payload
21
+ ) => void
22
+ ```
23
+
24
+ ### All `GridEventType` values
25
+
26
+ | Type | Fired from | `item` | `column` | `coords` | `detail` |
27
+ |---|---|---|---|---|---|
28
+ | `click` | Mouse click on a cell | row data | clicked column | — | — |
29
+ | `dblclick` | Double-click on a cell | row data | clicked column | — | — |
30
+ | `contextmenu` | Right-click on a cell | row data | clicked column | `{ x, y }` client coords | — |
31
+ | `keydown` | Any key pressed while grid focused | — | — | `{ x:0, y:0, code }` | — |
32
+ | `hover` | Mouse moves over a new cell | row data | hovered column | — | — |
33
+ | `leave` | Mouse leaves a cell | row data | vacated column | — | — |
34
+ | `enter` | Mouse enters the grid container | — | — | — | — |
35
+ | `load` | Local `data` prop set/changed, or first server page loaded | — | — | — | — |
36
+ | `scroll` | Visible range changes (scroll) | — | — | — | `{ row, column }` first visible |
37
+ | `resize` | Grid container resized (ResizeObserver) | — | — | — | `{ width, height }` |
38
+ | `page_loaded` | Next cursor page loaded from server | — | — | — | `{ data, total }` |
39
+ | `sort_changed` | Sort order applied or cleared | — | — | — | `{ sortOrder: GridColumnSortOrder }` |
40
+ | `filter_changed` | Filter applied or cleared | — | — | — | `{ filters: GridColumnFilters }` |
41
+ | `search_changed` | Search input value changed | — | — | — | `{ searchValue: string }` |
42
+ | `selection_changed` | Cell, row, range, or column selection changes | — | — | — | — |
43
+ | `column_moved` | Column dragged to new position | — | — | — | `{ from: number, to: number }` |
44
+ | `column_resized` | Column edge dragged to new width | — | moved column | — | `{ width: number }` |
45
+ | `data_changed` | Cell edited and committed (local mode only) | updated row | edited column | — | `{ data: Row[] }` full new dataset |
46
+ | `settings_changed` | `settings` prop reference changed | — | — | — | `{ settings }` |
47
+
48
+ ---
49
+
50
+ ## `onCellEvent`
51
+
52
+ ```ts
53
+ onCellEvent?: (
54
+ type: GridCellEventType,
55
+ item: Record<string, unknown>, // resolved raw row data
56
+ column: GridColumn,
57
+ coords?: GridEventCoords,
58
+ detail?: GridEventDetail,
59
+ ) => void
60
+ ```
61
+
62
+ ### All `GridCellEventType` values
63
+
64
+ | Type | Fired when |
65
+ |---|---|
66
+ | `click` | Mouse click on the cell |
67
+ | `dblclick` | Double-click on the cell |
68
+ | `contextmenu` | Right-click on the cell |
69
+ | `hover` | Mouse moves over this cell |
70
+ | `leave` | Mouse leaves this cell |
71
+ | `enter_key` | Enter pressed while this cell is focused |
72
+ | `tab_key` | Tab pressed while this cell is focused |
73
+ | `delete_key` | Delete or Backspace pressed while this cell is focused |
74
+
75
+ For all `onCellEvent` calls, `item` carries the **resolved raw row data** (from `serverData` or local `filteredDataState`). When the row cannot be resolved by index (e.g. from a top-level menu button), `item` falls back to the value in `selectedItems[0]`.
76
+
77
+ ---
78
+
79
+ ## Row data resolution
80
+
81
+ `GridlerFull` passes `getRowData` to `Gridler`, which calls `resolveRowData(rowIndex)`:
82
+
83
+ ```ts
84
+ function resolveRowData(row: number): Record<string, unknown> | undefined {
85
+ return isServerMode ? serverData[row] : filteredDataState[row];
86
+ }
87
+ ```
88
+
89
+ The current selection's row data is also tracked in `internalSelectedItems` state and passed as `selectedItems` to `Gridler`. This is used as a fallback when no row index is available.
90
+
91
+ ---
92
+
93
+ ## Field selection sent to adapters (hotfields)
94
+
95
+ When `dataSource="resolvespec"` or `"headerspec"`, `GridlerFull` derives a `resolvedFields` array and passes it to every `readPage` call as `options.columns` (maps to `X-Select-Fields` in headerspec):
96
+
97
+ ```ts
98
+ const resolvedFields = $derived.by(() => {
99
+ const columnFields = columnsState.map((c) => c.dataKey ?? c.id);
100
+ const hotfields = dataSourceOptions?.hotfields ?? [];
101
+ const seen = new Set(columnFields);
102
+ const extra = hotfields.filter((f) => !seen.has(f));
103
+ return [...columnFields, ...extra];
104
+ });
105
+ ```
106
+
107
+ - **Column fields** come from `column.dataKey ?? column.id` for every column.
108
+ - **Hotfields** (`dataSourceOptions.hotfields`) are extra fields needed server-side (for filtering, sorting, FK lookups) that are not grid columns. They are appended without duplicating existing column fields.
109
+
110
+ ---
111
+
112
+ ## Usage example
113
+
114
+ ```svelte
115
+ <GridlerFull
116
+ {columns}
117
+ {data}
118
+ onGridEvent={(type, item, column, coords, detail) => {
119
+ if (type === 'sort_changed') console.log('Sort:', detail?.sortOrder);
120
+ if (type === 'filter_changed') console.log('Filter:', detail?.filters);
121
+ if (type === 'search_changed') console.log('Search:', detail?.searchValue);
122
+ if (type === 'data_changed') console.log('Edited row:', item, 'All data:', detail?.data);
123
+ if (type === 'column_moved') console.log('Moved col', detail?.from, '→', detail?.to);
124
+ if (type === 'page_loaded') console.log('New page:', detail?.data, 'Total so far:', detail?.total);
125
+ }}
126
+ onCellEvent={(type, item, column) => {
127
+ if (type === 'click') console.log('Clicked:', item, column.id);
128
+ if (type === 'enter_key') console.log('Enter on row:', item);
129
+ }}
130
+ dataSource="resolvespec"
131
+ dataSourceOptions={{
132
+ url: 'https://api.example.com',
133
+ schema: 'public',
134
+ entity: 'users',
135
+ uniqueID: 'id',
136
+ hotfields: ['tenant_id', 'deleted_at'], // fetched but not shown as columns
137
+ }}
138
+ />
139
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warkypublic/svelix",
3
- "version": "0.1.29",
3
+ "version": "0.1.32",
4
4
  "description": "Svelte 5 component library with Skeleton UI and Tailwind CSS",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {
@@ -26,43 +26,43 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@changesets/cli": "^2.30.0",
29
- "@chromatic-com/storybook": "^5.0.2",
30
- "@eslint/compat": "^2.0.3",
29
+ "@chromatic-com/storybook": "^5.1.1",
30
+ "@eslint/compat": "^2.0.4",
31
31
  "@eslint/js": "^10.0.1",
32
- "@playwright/test": "^1.58.2",
33
- "@sentry/svelte": "^10.45.0",
34
- "@skeletonlabs/skeleton": "^4.13.0",
35
- "@skeletonlabs/skeleton-svelte": "^4.13.0",
36
- "@storybook/addon-a11y": "^10.3.1",
37
- "@storybook/addon-docs": "^10.3.1",
38
- "@storybook/addon-svelte-csf": "^5.0.12",
39
- "@storybook/addon-vitest": "^10.3.1",
40
- "@storybook/sveltekit": "^10.3.1",
32
+ "@playwright/test": "^1.59.1",
33
+ "@sentry/svelte": "^10.47.0",
34
+ "@skeletonlabs/skeleton": "^4.15.1",
35
+ "@skeletonlabs/skeleton-svelte": "^4.15.1",
36
+ "@storybook/addon-a11y": "^10.3.4",
37
+ "@storybook/addon-docs": "^10.3.4",
38
+ "@storybook/addon-svelte-csf": "^5.1.2",
39
+ "@storybook/addon-vitest": "^10.3.4",
40
+ "@storybook/sveltekit": "^10.3.4",
41
41
  "@storybook/test": "^8.6.15",
42
42
  "@sveltejs/adapter-auto": "^7.0.1",
43
- "@sveltejs/kit": "^2.55.0",
43
+ "@sveltejs/kit": "^2.56.1",
44
44
  "@sveltejs/package": "^2.5.7",
45
45
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
46
46
  "@tailwindcss/vite": "^4.2.2",
47
47
  "@tanstack/svelte-virtual": "^3.13.23",
48
- "@types/node": "^25.5.0",
49
- "@vitest/browser-playwright": "^4.1.0",
50
- "@vitest/coverage-v8": "^4.1.0",
51
- "eslint": "^10.0.3",
52
- "eslint-plugin-svelte": "^3.15.2",
48
+ "@types/node": "^25.5.2",
49
+ "@vitest/browser-playwright": "^4.1.3",
50
+ "@vitest/coverage-v8": "^4.1.3",
51
+ "eslint": "^10.2.0",
52
+ "eslint-plugin-svelte": "^3.17.0",
53
53
  "globals": "^17.4.0",
54
- "playwright": "^1.58.2",
54
+ "playwright": "^1.59.1",
55
55
  "publint": "^0.3.18",
56
- "storybook": "^10.3.1",
57
- "svelte": "^5.54.0",
58
- "svelte-check": "^4.4.5",
56
+ "storybook": "^10.3.4",
57
+ "svelte": "^5.55.1",
58
+ "svelte-check": "^4.4.6",
59
59
  "tailwindcss": "^4.2.2",
60
60
  "tslib": "^2.8.1",
61
- "typescript": "^5.9.3",
62
- "typescript-eslint": "^8.57.1",
63
- "vite": "^8.0.1",
61
+ "typescript": "^6.0.2",
62
+ "typescript-eslint": "^8.58.0",
63
+ "vite": "^8.0.7",
64
64
  "vite-plugin-monaco-editor": "^1.1.0",
65
- "vitest": "^4.1.0"
65
+ "vitest": "^4.1.3"
66
66
  },
67
67
  "svelte": "./dist/index.js",
68
68
  "types": "./dist/index.d.ts",
@@ -76,13 +76,13 @@
76
76
  "@cartamd/plugin-math": "^4.3.1",
77
77
  "@friendofsvelte/tipex": "^0.1.1",
78
78
  "@js-temporal/polyfill": "^0.5.1",
79
- "@svar-ui/svelte-grid": "^2.6.0",
79
+ "@svar-ui/svelte-grid": "^2.6.1",
80
80
  "@warkypublic/resolvespec-js": "^1.0.1",
81
81
  "@warkypublic/artemis-kit": "^1.0.10",
82
82
  "carta-md": "^4.11.1",
83
83
  "github-markdown-css": "^5.9.0",
84
- "isomorphic-dompurify": "^3.5.1",
85
- "katex": "^0.16.39",
84
+ "isomorphic-dompurify": "^3.7.1",
85
+ "katex": "^0.16.45",
86
86
  "monaco-editor": "^0.55.1"
87
87
  },
88
88
  "scripts": {