@warkypublic/svelix 0.1.42 → 0.1.44

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.
@@ -157,6 +157,29 @@
157
157
  cancelEdit();
158
158
  }
159
159
 
160
+ function getSelectionRowData(sel: Selection): Record<string, unknown>[] | undefined {
161
+ if (!getRowData) return undefined;
162
+ if (sel.type === "cell") {
163
+ const d = getRowData(sel.item[1]);
164
+ return d ? [d] : undefined;
165
+ }
166
+ if (sel.type === "row") {
167
+ const items = sel.rows.map(getRowData).filter((r): r is Record<string, unknown> => r !== undefined);
168
+ return items.length > 0 ? items : undefined;
169
+ }
170
+ if (sel.type === "range") {
171
+ const minRow = Math.min(sel.start[1], sel.end[1]);
172
+ const maxRow = Math.max(sel.start[1], sel.end[1]);
173
+ const items: Record<string, unknown>[] = [];
174
+ for (let i = minRow; i <= maxRow; i++) {
175
+ const d = getRowData(i);
176
+ if (d) items.push(d);
177
+ }
178
+ return items.length > 0 ? items : undefined;
179
+ }
180
+ return undefined;
181
+ }
182
+
160
183
  // ── Row toggle (checkbox / number markers) ────────────────────────────────────
161
184
 
162
185
  function handleRowToggle(row: number) {
@@ -173,7 +196,7 @@
173
196
  }
174
197
  currentSelection = newSel;
175
198
  canvasComponent?.setCurrentSelection(newSel);
176
- onSelectionChange?.(newSel);
199
+ onSelectionChange?.(newSel, getSelectionRowData(newSel));
177
200
  }
178
201
 
179
202
  // ── Clipboard ────────────────────────────────────────────────────────────────
@@ -295,7 +318,7 @@
295
318
  };
296
319
  currentSelection = newSel;
297
320
  canvasComponent?.setCurrentSelection(newSel);
298
- onSelectionChange?.(newSel);
321
+ onSelectionChange?.(newSel, getSelectionRowData(newSel));
299
322
  canvasComponent?.setAnnouncement("All cells selected");
300
323
  }
301
324
  e.preventDefault();
@@ -386,7 +409,7 @@
386
409
  const newSel: Selection = { type: "cell", item: newFocusedCell };
387
410
  currentSelection = newSel;
388
411
  canvasComponent?.setCurrentSelection(newSel);
389
- onSelectionChange?.(newSel);
412
+ onSelectionChange?.(newSel, getSelectionRowData(newSel));
390
413
  canvasComponent?.setAnnouncement(
391
414
  `Row ${newRow + 1}, column ${columns[newCol]?.title ?? newCol + 1}`,
392
415
  );
@@ -445,7 +468,7 @@
445
468
  onVisibleRangeChange={handleVisibleRangeChange}
446
469
  onSelectionChange={(sel) => {
447
470
  currentSelection = sel;
448
- onSelectionChange?.(sel);
471
+ onSelectionChange?.(sel, getSelectionRowData(sel));
449
472
  onGridEvent?.("selection_changed");
450
473
  }}
451
474
  onCellDblClick={(item, cell) => {
@@ -1137,6 +1137,7 @@
1137
1137
  style:--gridler-bg-header-focus={mergedTheme.bgHeaderHasFocus}
1138
1138
  style:--gridler-bg-search-result={mergedTheme.bgSearchResult}
1139
1139
  style:--gridler-header-height={`${headerHeight}px`}
1140
+ style:--gridler-row-marker-width={`${rowMarkerWidth}px`}
1140
1141
  onfocusin={handleFocusIn}
1141
1142
  onfocusout={handleFocusOut}
1142
1143
  onmouseenter={onGridEnter}
@@ -758,7 +758,7 @@
758
758
  }
759
759
  internalSelectedItems = items;
760
760
  if (items.length > 0) onSelectedItemsChange?.(items);
761
- onSelectionChange?.(sel);
761
+ onSelectionChange?.(sel, items.length > 0 ? items : undefined);
762
762
  }
763
763
 
764
764
  // ── Settings change event ─────────────────────────────────────────────────
@@ -970,7 +970,10 @@
970
970
  .gf-loading,
971
971
  .gf-error {
972
972
  position: absolute;
973
- inset: 0;
973
+ top: var(--gridler-header-height, 36px);
974
+ left: var(--gridler-row-marker-width, 0px);
975
+ right: 0;
976
+ bottom: 0;
974
977
  display: flex;
975
978
  align-items: center;
976
979
  justify-content: center;
@@ -9,11 +9,11 @@
9
9
 
10
10
  const { value, onValueChange, onClose }: Props = $props();
11
11
 
12
- let inputRef: HTMLInputElement | undefined = $state();
12
+ let inputRef: HTMLInputElement | undefined;
13
13
 
14
- $effect(() => {
14
+ export function focus() {
15
15
  inputRef?.focus();
16
- });
16
+ }
17
17
  </script>
18
18
 
19
19
  <div class="gridler-search" role="search">
@@ -3,6 +3,8 @@ interface Props {
3
3
  onValueChange: (v: string) => void;
4
4
  onClose?: () => void;
5
5
  }
6
- declare const GridlerSearch: import("svelte").Component<Props, {}, "">;
6
+ declare const GridlerSearch: import("svelte").Component<Props, {
7
+ focus: () => void;
8
+ }, "">;
7
9
  type GridlerSearch = ReturnType<typeof GridlerSearch>;
8
10
  export default GridlerSearch;
@@ -11,6 +11,12 @@
11
11
  }
12
12
 
13
13
  const { show, value, onToggle, onValueChange, onClose }: Props = $props();
14
+
15
+ let search: GridlerSearch | undefined = $state();
16
+
17
+ $effect(() => {
18
+ if (show) search?.focus();
19
+ });
14
20
  </script>
15
21
 
16
22
  <button
@@ -25,6 +31,7 @@
25
31
 
26
32
  {#if show}
27
33
  <GridlerSearch
34
+ bind:this={search}
28
35
  {value}
29
36
  {onValueChange}
30
37
  {onClose}
@@ -1,4 +1,4 @@
1
- import type { GridColumn, GridCommonProps, GridColumnSortOrder, GridColumnFilters } from '../Types/generic_grid';
1
+ import type { GridColumn, GridCommonProps, GridColumnSortOrder, GridColumnFilters, GridContextMenuItem } from '../Types/generic_grid';
2
2
  import type { Options } from '@warkypublic/resolvespec-js';
3
3
  export type { GridColumnSortOrder, GridColumnFilters };
4
4
  export type Item = [col: number, row: number];
@@ -60,8 +60,6 @@ export interface GridlerColumn extends GridColumn<Record<string, unknown>> {
60
60
  width: number;
61
61
  /** Per-column theme overrides applied during canvas rendering. */
62
62
  themeOverride?: Partial<GridlerTheme>;
63
- /** Show a context-menu trigger button on this column's header. */
64
- hasMenu?: boolean;
65
63
  }
66
64
  export interface GridlerCellFormatOptions {
67
65
  /** Currency code for 'currency' cells (default: 'ZAR'). */
@@ -155,8 +153,8 @@ export interface GridlerProps extends GridCommonProps<Record<string, unknown>> {
155
153
  theme?: Partial<GridlerTheme>;
156
154
  /** Controlled selection state. */
157
155
  selection?: Selection;
158
- /** Called when the internal selection changes. */
159
- onSelectionChange?: (selection: Selection) => void;
156
+ /** Called when the internal selection changes. Row data for all selected rows is provided where available. */
157
+ onSelectionChange?: (selection: Selection, rowData?: Record<string, unknown>[]) => void;
160
158
  /** Called when the visible row/column range changes (e.g. on scroll). */
161
159
  onVisibleRangeChange?: (range: VisibleRange) => void;
162
160
  /** When true, all cells are read-only. Also settable via settings.readonly. */
@@ -166,14 +164,6 @@ export interface GridlerProps extends GridCommonProps<Record<string, unknown>> {
166
164
  /** Show the built-in search bar. Also settable via settings.showSearch. */
167
165
  showSearch?: boolean;
168
166
  }
169
- export interface GridlerContextMenuItem {
170
- id: string;
171
- label: string;
172
- disabled?: boolean;
173
- icon?: string;
174
- /** Use 'separator' to render a divider line. */
175
- kind?: 'item' | 'separator';
176
- onselect?: (rowData?: Record<string, unknown>) => void;
177
- }
167
+ export type GridlerContextMenuItem = GridContextMenuItem<Record<string, unknown>>;
178
168
  export declare const DEFAULT_THEME: GridlerTheme;
179
169
  export declare const DEFAULT_THEME_DARK: GridlerTheme;
@@ -1,8 +1,9 @@
1
+ import { getNestedValue } from "@warkypublic/artemis-kit/object";
1
2
  export function rowToCell(row, key) {
2
3
  if (row === undefined) {
3
4
  return { kind: "text", data: null, displayData: "", allowOverlay: false, allowEditing: false };
4
5
  }
5
- const raw = key ? row[key] : undefined;
6
+ const raw = key ? getNestedValue(key, row) : undefined;
6
7
  const display = raw == null ? "" : String(raw);
7
8
  if (typeof raw === "number") {
8
9
  return { kind: "number", data: raw, displayData: display, allowOverlay: true, allowEditing: true };
@@ -1,3 +1,4 @@
1
+ import { getNestedValue } from "@warkypublic/artemis-kit/object";
1
2
  export function gridColumnFiltersToFilterOptions(f) {
2
3
  return Object.entries(f).map(([id, filter]) => ({
3
4
  column: filter.dataKey ?? id,
@@ -15,8 +16,7 @@ export function applyLocalFilter(data, filters, columns) {
15
16
  for (const [colId, filter] of entries) {
16
17
  const colDef = columns.find((c) => c.id === colId);
17
18
  const key = colDef?.dataKey ?? colId;
18
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
- const rawVal = row[key];
19
+ const rawVal = getNestedValue(key, row);
20
20
  const cellStr = rawVal == null ? "" : String(rawVal).toLowerCase();
21
21
  const filterVal = filter.value?.toLowerCase() ?? "";
22
22
  const op = filter.op ?? "contains";
@@ -1,3 +1,4 @@
1
+ import { getNestedValue } from "@warkypublic/artemis-kit/object";
1
2
  export function compareValues(a, b) {
2
3
  if (a == null && b == null)
3
4
  return 0;
@@ -27,7 +28,7 @@ export function applyLocalSort(data, sortOptions, columns) {
27
28
  for (const { column, direction } of sortOptions) {
28
29
  const colDef = columns.find((c) => c.id === column);
29
30
  const key = colDef?.dataKey ?? column;
30
- const cmp = compareValues(a[key], b[key]);
31
+ const cmp = compareValues(getNestedValue(key, a), getNestedValue(key, b));
31
32
  if (cmp !== 0)
32
33
  return direction === "asc" ? cmp : -cmp;
33
34
  }
@@ -25,6 +25,8 @@ export interface GridColumn<RowDataType = unknown, CellType = unknown> {
25
25
  disableSearch?: boolean | ((item: RowDataType) => boolean);
26
26
  span?: [startRow: number, endRow: number];
27
27
  renderCell?: (item: RowDataType) => CellType;
28
+ /** Show a context-menu trigger button on this column's header. */
29
+ hasMenu?: boolean;
28
30
  }
29
31
  export interface GridColumnSortOrder {
30
32
  [id: string]: "asc" | "desc" | "none";
@@ -44,34 +46,74 @@ export interface GridEventCoords {
44
46
  code?: string;
45
47
  }
46
48
  export type GridEventDetail = Record<string, unknown>;
49
+ /** Generic context-menu item. Implementations (e.g. GridlerContextMenuItem) extend this. */
50
+ export interface GridContextMenuItem<RowDataType = unknown> {
51
+ id: string;
52
+ label: string;
53
+ disabled?: boolean;
54
+ icon?: string;
55
+ /** Use 'separator' to render a divider line. */
56
+ kind?: 'item' | 'separator';
57
+ onselect?: (rowData?: RowDataType) => void;
58
+ }
47
59
  export interface GridCommonProps<RowDataType = unknown, CellType = unknown> {
48
60
  columns: Array<GridColumn<RowDataType, CellType>>;
49
61
  width?: string | number;
50
62
  height?: string | number;
51
63
  rowHeight?: number;
52
64
  headerHeight?: number;
53
- onCellEvent?: (type: GridCellEventType, item: RowDataType, column: GridColumn<RowDataType, CellType>, coords?: GridEventCoords, // For keyboard/mouse events, code represents the key code of the pressed key
54
- detail?: GridEventDetail) => void;
55
- onGridEvent?: (type: GridEventType, item?: RowDataType, column?: GridColumn<RowDataType, CellType>, coords?: GridEventCoords, // For keyboard/mouse events, code represents the key code of the pressed key
56
- detail?: GridEventDetail) => void;
65
+ /** Number of columns pinned to the left. */
66
+ fixedColumns?: number;
67
+ /** When true, the grid manages column reordering internally on drag. */
68
+ manageColumns?: boolean;
69
+ /** Called when the internal column order changes (only fires when manageColumns is true). */
70
+ onColumnsChange?: (columns: GridColumn<RowDataType, CellType>[]) => void;
71
+ onCellEvent?: (type: GridCellEventType, item: RowDataType, column: GridColumn<RowDataType, CellType>, coords?: GridEventCoords, detail?: GridEventDetail) => void;
72
+ onGridEvent?: (type: GridEventType, item?: RowDataType, column?: GridColumn<RowDataType, CellType>, coords?: GridEventCoords, detail?: GridEventDetail) => void;
57
73
  onMenuClick?: (item?: RowDataType, column?: GridColumn<RowDataType, CellType>, coords?: GridEventCoords) => Promise<void>;
74
+ /** Called when a body row is clicked. */
75
+ onRowClick?: (row: number, rowData?: RowDataType) => void;
76
+ /** Called when a body row is double-clicked. */
77
+ onRowDblClick?: (row: number, rowData?: RowDataType) => void;
78
+ /** Called when a body row receives a context-menu event. */
79
+ onRowContextMenu?: (row: number, rowData?: RowDataType, x?: number, y?: number) => void;
80
+ /** Called when a cell is double-clicked. */
81
+ onCellDblClick?: (row: number, column: GridColumn<RowDataType, CellType>, rowData?: RowDataType) => void;
58
82
  onColumnMoved?: (startIndex: number, endIndex: number) => void;
59
83
  onSortOrderChange?: (sortOrder: GridColumnSortOrder) => void;
60
84
  sortOrder?: GridColumnSortOrder;
61
85
  onFilterChange?: (filters: GridColumnFilters) => void;
62
86
  filters?: GridColumnFilters;
63
- onSearchValueChange?: (value: string) => void;
64
87
  searchValue?: string;
88
+ /** Uncontrolled initial search value (only used when searchValue is undefined). */
89
+ defaultSearchValue?: string;
90
+ onSearchValueChange?: (value: string) => void;
91
+ /**
92
+ * When true, the search input value is translated into server-side filters
93
+ * instead of only being used for client-side highlighting.
94
+ */
95
+ serverSideSearch?: boolean;
96
+ /** Columns searched when serverSideSearch is true. Defaults to all columns. */
97
+ searchColumns?: string[];
65
98
  selectedItems?: RowDataType[];
66
99
  onSelectedItemsChange?: (items: RowDataType[]) => void;
100
+ /** Extra items appended to the built-in context menu. */
101
+ menuItems?: GridContextMenuItem<RowDataType>[];
102
+ /** Called when any context menu item is selected (built-in or custom). */
103
+ onMenuItemSelect?: (item: GridContextMenuItem<RowDataType>, rowData?: RowDataType) => void;
67
104
  dataSource?: "local" | "resolvespec" | "headerspec" | "websockspec" | "custom";
68
105
  dataSourceOptions?: {
69
106
  url?: string;
70
107
  authToken?: string;
71
108
  schema?: string;
72
109
  entity?: string;
110
+ /**
111
+ * Row field whose value is used as the cursor for subsequent page requests.
112
+ * Defaults to 'id'. Can also be set via the top-level uniqueID shorthand.
113
+ */
73
114
  uniqueID?: string;
74
115
  headers?: Record<string, string>;
116
+ /** Fields to fetch in addition to column fields (for filters, sort, relations). */
75
117
  hotfields?: string[];
76
118
  /**
77
119
  * Default ResolveSpec options sent with every request. Grid-controlled sort, filters, limit,
@@ -93,6 +135,32 @@ export interface GridCommonProps<RowDataType = unknown, CellType = unknown> {
93
135
  };
94
136
  data?: RowDataType[] | (() => Promise<RowDataType[]>);
95
137
  onDataChange?: (data: RowDataType[]) => Promise<void>;
138
+ /** Rows per cursor-forward page. Defaults to 200. */
139
+ pageSize?: number;
140
+ /**
141
+ * Shorthand for dataSourceOptions.uniqueID.
142
+ * Row field whose value is passed as cursor_forward on subsequent requests.
143
+ */
144
+ uniqueID?: string;
145
+ /** Called when a server fetch fails. */
146
+ onLoadError?: (error: string) => void;
147
+ /**
148
+ * Bindable. Reflects the server-reported total row count.
149
+ * Updated after every successful page fetch; 0 until the first response.
150
+ */
151
+ total?: number;
152
+ /**
153
+ * Bindable. true while any server fetch is in flight.
154
+ */
155
+ loading?: boolean;
156
+ /** When true, all cells are read-only. Also settable via settings.readonly. */
157
+ readonly?: boolean;
158
+ /** Allow users to drag column edges to resize. Also settable via settings.resizableColumns. */
159
+ resizableColumns?: boolean;
160
+ /** Show the built-in search bar. Also settable via settings.showSearch. */
161
+ showSearch?: boolean;
162
+ /** Row-marker style shown in the leading column. Also settable via settings.rowMarkerType. */
163
+ rowMarkers?: "checkbox" | "number" | "none";
96
164
  settings?: {
97
165
  hideHeader?: boolean;
98
166
  showRowMarkers?: boolean;
@@ -41,6 +41,7 @@ This file now tracks the implemented state of the project rather than the origin
41
41
  ### Data and explorer components
42
42
  - `Gridler`
43
43
  - `GridlerFull`
44
+ - `onSelectionChange(selection, rowData?)` — second arg carries resolved row data for all selected rows (cell, row, range types)
44
45
  - `SvarkGrid`
45
46
  - `VTree`
46
47
  - shared generic grid types and supporting utilities
@@ -39,7 +39,7 @@ onGridEvent?: (
39
39
  | `sort_changed` | Sort order applied or cleared | — | — | — | `{ sortOrder: GridColumnSortOrder }` |
40
40
  | `filter_changed` | Filter applied or cleared | — | — | — | `{ filters: GridColumnFilters }` |
41
41
  | `search_changed` | Search input value changed | — | — | — | `{ searchValue: string }` |
42
- | `selection_changed` | Cell, row, range, or column selection changes | — | — | — | — |
42
+ | `selection_changed` | Cell, row, range, or column selection changes | — | — | — | — | (use `onSelectionChange` for the selection object + row data) |
43
43
  | `column_moved` | Column dragged to new position | — | — | — | `{ from: number, to: number }` |
44
44
  | `column_resized` | Column edge dragged to new width | — | moved column | — | `{ width: number }` |
45
45
  | `data_changed` | Cell edited and committed (local mode only) | updated row | edited column | — | `{ data: Row[] }` full new dataset |
@@ -47,6 +47,55 @@ onGridEvent?: (
47
47
 
48
48
  ---
49
49
 
50
+ ## `onSelectionChange`
51
+
52
+ Fired on every selection change — mouse clicks, arrow-key navigation, Ctrl+A, row-marker toggles.
53
+
54
+ ```ts
55
+ onSelectionChange?: (
56
+ selection: Selection,
57
+ rowData?: Record<string, unknown>[], // resolved row data for all selected rows
58
+ ) => void
59
+ ```
60
+
61
+ ### `Selection` type
62
+
63
+ ```ts
64
+ type Selection =
65
+ | { type: "none" }
66
+ | { type: "cell"; item: [col, row] }
67
+ | { type: "range"; start: [col, row]; end: [col, row] }
68
+ | { type: "row"; rows: number[] }
69
+ | { type: "column"; columns: number[] }
70
+ ```
71
+
72
+ ### `rowData` population
73
+
74
+ | Selection type | `rowData` |
75
+ |---|---|
76
+ | `cell` | single-element array with the row's data |
77
+ | `row` | array of data for each selected row |
78
+ | `range` | array of data for every row from `start[1]` to `end[1]` |
79
+ | `column` / `none` | `undefined` |
80
+
81
+ `rowData` is `undefined` when no row data can be resolved (column/none selections, or when `getRowData`/`data` is not provided).
82
+
83
+ ### Usage
84
+
85
+ ```svelte
86
+ <GridlerFull
87
+ {columns}
88
+ {data}
89
+ onSelectionChange={(sel, rowData) => {
90
+ if (sel.type === 'cell') {
91
+ console.log('Selected row:', rowData?.[0]);
92
+ }
93
+ }}
94
+ />
95
+ ```
96
+
97
+ ---
98
+
50
99
  ## `onCellEvent`
51
100
 
52
101
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warkypublic/svelix",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "description": "Svelte 5 component library with Skeleton UI and Tailwind CSS",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {