@rowakit/table 0.1.0 → 0.2.1

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/index.d.ts CHANGED
@@ -12,6 +12,30 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
12
12
  * @packageDocumentation
13
13
  */
14
14
 
15
+ /**
16
+ * Filter value shape for server-side filtering.
17
+ */
18
+ type FilterValue = {
19
+ op: 'contains';
20
+ value: string;
21
+ } | {
22
+ op: 'equals';
23
+ value: string | number | boolean | null;
24
+ } | {
25
+ op: 'in';
26
+ value: Array<string | number>;
27
+ } | {
28
+ op: 'range';
29
+ value: {
30
+ from?: string;
31
+ to?: string;
32
+ };
33
+ };
34
+ /**
35
+ * Filters map: field name → filter value.
36
+ * Undefined values represent cleared filters.
37
+ */
38
+ type Filters = Record<string, FilterValue | undefined>;
15
39
  /**
16
40
  * Query parameters passed to the fetcher function.
17
41
  *
@@ -29,8 +53,8 @@ interface FetcherQuery {
29
53
  /** Sort direction */
30
54
  direction: 'asc' | 'desc';
31
55
  };
32
- /** Optional filters (key-value pairs) */
33
- filters?: Record<string, unknown>;
56
+ /** Optional filters (omitted when empty) */
57
+ filters?: Filters;
34
58
  }
35
59
  /**
36
60
  * Result returned from the fetcher function.
@@ -66,7 +90,11 @@ type Fetcher<T> = (query: FetcherQuery) => Promise<FetcherResult<T>>;
66
90
  * These represent the built-in column types that can be created
67
91
  * via the `col.*` helper factory.
68
92
  */
69
- type ColumnKind = 'text' | 'date' | 'boolean' | 'actions' | 'custom';
93
+ type ColumnKind = 'text' | 'date' | 'boolean' | 'actions' | 'custom' | 'badge' | 'number';
94
+ /**
95
+ * Badge tone options for status/enum columns.
96
+ */
97
+ type BadgeTone = 'neutral' | 'success' | 'warning' | 'danger';
70
98
  /**
71
99
  * Base column definition properties shared across all column types.
72
100
  */
@@ -81,6 +109,12 @@ interface BaseColumnDef<T> {
81
109
  sortable?: boolean;
82
110
  /** Optional field name to extract from row data (for sortable columns) */
83
111
  field?: keyof T & string;
112
+ /** Column width in pixels */
113
+ width?: number;
114
+ /** Text alignment */
115
+ align?: 'left' | 'center' | 'right';
116
+ /** Enable text truncation with ellipsis */
117
+ truncate?: boolean;
84
118
  }
85
119
  /**
86
120
  * Text column definition.
@@ -106,6 +140,25 @@ interface BooleanColumnDef<T> extends BaseColumnDef<T> {
106
140
  field: keyof T & string;
107
141
  format?: (value: boolean) => string;
108
142
  }
143
+ /**
144
+ * Badge column definition for status/enum fields.
145
+ */
146
+ interface BadgeColumnDef<T> extends BaseColumnDef<T> {
147
+ kind: 'badge';
148
+ field: keyof T & string;
149
+ map?: Record<string, {
150
+ label: string;
151
+ tone: BadgeTone;
152
+ }>;
153
+ }
154
+ /**
155
+ * Number column definition.
156
+ */
157
+ interface NumberColumnDef<T> extends BaseColumnDef<T> {
158
+ kind: 'number';
159
+ field: keyof T & string;
160
+ format?: Intl.NumberFormatOptions | ((value: number, row: T) => string);
161
+ }
109
162
  /**
110
163
  * Actions column definition.
111
164
  */
@@ -127,7 +180,7 @@ interface CustomColumnDef<T> extends BaseColumnDef<T> {
127
180
  /**
128
181
  * Union type for all column definitions.
129
182
  */
130
- type ColumnDef<T> = TextColumnDef<T> | DateColumnDef<T> | BooleanColumnDef<T> | ActionsColumnDef<T> | CustomColumnDef<T>;
183
+ type ColumnDef<T> = TextColumnDef<T> | DateColumnDef<T> | BooleanColumnDef<T> | BadgeColumnDef<T> | NumberColumnDef<T> | ActionsColumnDef<T> | CustomColumnDef<T>;
131
184
  /**
132
185
  * Row action definition.
133
186
  *
@@ -168,6 +221,12 @@ interface TextOptions {
168
221
  sortable?: boolean;
169
222
  /** Optional formatter function */
170
223
  format?: (value: unknown) => string;
224
+ /** Column width in pixels */
225
+ width?: number;
226
+ /** Text alignment */
227
+ align?: 'left' | 'center' | 'right';
228
+ /** Enable text truncation with ellipsis */
229
+ truncate?: boolean;
171
230
  }
172
231
  interface DateOptions {
173
232
  /** Optional custom header label */
@@ -176,6 +235,12 @@ interface DateOptions {
176
235
  sortable?: boolean;
177
236
  /** Optional date formatter function */
178
237
  format?: (value: Date | string | number) => string;
238
+ /** Column width in pixels */
239
+ width?: number;
240
+ /** Text alignment */
241
+ align?: 'left' | 'center' | 'right';
242
+ /** Enable text truncation with ellipsis */
243
+ truncate?: boolean;
179
244
  }
180
245
  interface BooleanOptions {
181
246
  /** Optional custom header label */
@@ -184,6 +249,43 @@ interface BooleanOptions {
184
249
  sortable?: boolean;
185
250
  /** Optional boolean formatter function */
186
251
  format?: (value: boolean) => string;
252
+ /** Column width in pixels */
253
+ width?: number;
254
+ /** Text alignment */
255
+ align?: 'left' | 'center' | 'right';
256
+ /** Enable text truncation with ellipsis */
257
+ truncate?: boolean;
258
+ }
259
+ interface BadgeOptions {
260
+ /** Optional custom header label */
261
+ header?: string;
262
+ /** Enable sorting for this column */
263
+ sortable?: boolean;
264
+ /** Value-to-badge mapping */
265
+ map?: Record<string, {
266
+ label: string;
267
+ tone: BadgeTone;
268
+ }>;
269
+ /** Column width in pixels */
270
+ width?: number;
271
+ /** Text alignment */
272
+ align?: 'left' | 'center' | 'right';
273
+ /** Enable text truncation with ellipsis */
274
+ truncate?: boolean;
275
+ }
276
+ interface NumberOptions<T = unknown> {
277
+ /** Optional custom header label */
278
+ header?: string;
279
+ /** Enable sorting for this column */
280
+ sortable?: boolean;
281
+ /** Formatting options: Intl.NumberFormatOptions or custom formatter */
282
+ format?: Intl.NumberFormatOptions | ((value: number, row: T) => string);
283
+ /** Column width in pixels */
284
+ width?: number;
285
+ /** Text alignment */
286
+ align?: 'left' | 'center' | 'right';
287
+ /** Enable text truncation with ellipsis */
288
+ truncate?: boolean;
187
289
  }
188
290
  /**
189
291
  * Create a text column definition.
@@ -222,6 +324,37 @@ declare function date<T>(field: keyof T & string, options?: DateOptions): DateCo
222
324
  * ```
223
325
  */
224
326
  declare function boolean<T>(field: keyof T & string, options?: BooleanOptions): BooleanColumnDef<T>;
327
+ /**
328
+ * Create a badge column definition for status/enum fields.
329
+ *
330
+ * @example
331
+ * ```ts
332
+ * col.badge('status')
333
+ * col.badge('status', {
334
+ * map: {
335
+ * active: { label: 'Active', tone: 'success' },
336
+ * paused: { label: 'Paused', tone: 'warning' },
337
+ * disabled: { label: 'Disabled', tone: 'danger' }
338
+ * }
339
+ * })
340
+ * ```
341
+ */
342
+ declare function badge<T>(field: keyof T & string, options?: BadgeOptions): BadgeColumnDef<T>;
343
+ /**
344
+ * Create a number column definition.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * col.number('amount')
349
+ * col.number('price', {
350
+ * format: { style: 'currency', currency: 'USD' }
351
+ * })
352
+ * col.number('count', {
353
+ * format: (val, row) => `${val} items`
354
+ * })
355
+ * ```
356
+ */
357
+ declare function number<T>(field: keyof T & string, options?: NumberOptions<T>): NumberColumnDef<T>;
225
358
  /**
226
359
  * Create an actions column definition.
227
360
  *
@@ -267,6 +400,7 @@ declare function actions<T>(actions: ActionDef<T>[]): ActionsColumnDef<T>;
267
400
  * })
268
401
  * ```
269
402
  */
403
+ declare function custom<T>(field: keyof T & string, render: (row: T) => ReactNode): CustomColumnDef<T>;
270
404
  declare function custom<T>(options: {
271
405
  /** Unique column identifier */
272
406
  id: string;
@@ -301,6 +435,8 @@ declare const col: {
301
435
  readonly text: typeof text;
302
436
  readonly date: typeof date;
303
437
  readonly boolean: typeof boolean;
438
+ readonly badge: typeof badge;
439
+ readonly number: typeof number;
304
440
  readonly actions: typeof actions;
305
441
  readonly custom: typeof custom;
306
442
  };
@@ -318,6 +454,8 @@ interface SmartTableProps<T> {
318
454
  rowKey?: keyof T | ((row: T) => string | number);
319
455
  /** Optional CSS class name for the table container */
320
456
  className?: string;
457
+ /** Enable filters (default: false) */
458
+ enableFilters?: boolean;
321
459
  }
322
460
  /**
323
461
  * RowaKitTable - Server-side table component for internal/business apps.
@@ -370,7 +508,7 @@ interface SmartTableProps<T> {
370
508
  * }
371
509
  * ```
372
510
  */
373
- declare function RowaKitTable<T>({ fetcher, columns, defaultPageSize, pageSizeOptions, rowKey, className, }: SmartTableProps<T>): react_jsx_runtime.JSX.Element;
511
+ declare function RowaKitTable<T>({ fetcher, columns, defaultPageSize, pageSizeOptions, rowKey, className, enableFilters, }: SmartTableProps<T>): react_jsx_runtime.JSX.Element;
374
512
  /**
375
513
  * @deprecated Use RowaKitTable instead. SmartTable is kept as an alias for backward compatibility.
376
514
  */
@@ -386,4 +524,4 @@ declare const SmartTable: typeof RowaKitTable;
386
524
 
387
525
  declare const VERSION = "0.1.0";
388
526
 
389
- export { type ActionDef, type ActionsColumnDef, type BaseColumnDef, type BooleanColumnDef, type ColumnDef, type ColumnKind, type CustomColumnDef, type DateColumnDef, type Fetcher, type FetcherQuery, type FetcherResult, RowaKitTable, SmartTable, type SmartTableProps, type TextColumnDef, VERSION, col };
527
+ export { type ActionDef, type ActionsColumnDef, type BadgeColumnDef, type BadgeTone, type BaseColumnDef, type BooleanColumnDef, type ColumnDef, type ColumnKind, type CustomColumnDef, type DateColumnDef, type Fetcher, type FetcherQuery, type FetcherResult, type FilterValue, type Filters, type NumberColumnDef, RowaKitTable, SmartTable, type SmartTableProps, type TextColumnDef, VERSION, col };
package/dist/index.js CHANGED
@@ -9,7 +9,10 @@ function text(field, options) {
9
9
  field,
10
10
  header: options?.header,
11
11
  sortable: options?.sortable ?? false,
12
- format: options?.format
12
+ format: options?.format,
13
+ width: options?.width,
14
+ align: options?.align,
15
+ truncate: options?.truncate
13
16
  };
14
17
  }
15
18
  function date(field, options) {
@@ -19,7 +22,10 @@ function date(field, options) {
19
22
  field,
20
23
  header: options?.header,
21
24
  sortable: options?.sortable ?? false,
22
- format: options?.format
25
+ format: options?.format,
26
+ width: options?.width,
27
+ align: options?.align,
28
+ truncate: options?.truncate
23
29
  };
24
30
  }
25
31
  function boolean(field, options) {
@@ -29,7 +35,36 @@ function boolean(field, options) {
29
35
  field,
30
36
  header: options?.header,
31
37
  sortable: options?.sortable ?? false,
32
- format: options?.format
38
+ format: options?.format,
39
+ width: options?.width,
40
+ align: options?.align,
41
+ truncate: options?.truncate
42
+ };
43
+ }
44
+ function badge(field, options) {
45
+ return {
46
+ id: field,
47
+ kind: "badge",
48
+ field,
49
+ header: options?.header,
50
+ sortable: options?.sortable ?? false,
51
+ map: options?.map,
52
+ width: options?.width,
53
+ align: options?.align,
54
+ truncate: options?.truncate
55
+ };
56
+ }
57
+ function number(field, options) {
58
+ return {
59
+ id: field,
60
+ kind: "number",
61
+ field,
62
+ header: options?.header,
63
+ sortable: options?.sortable ?? false,
64
+ format: options?.format,
65
+ width: options?.width,
66
+ align: options?.align,
67
+ truncate: options?.truncate
33
68
  };
34
69
  }
35
70
  function actions(actions2) {
@@ -39,19 +74,32 @@ function actions(actions2) {
39
74
  actions: actions2
40
75
  };
41
76
  }
42
- function custom(options) {
77
+ function custom(arg1, arg2) {
78
+ if (typeof arg1 === "string") {
79
+ if (typeof arg2 !== "function") {
80
+ throw new Error("col.custom(field, render): render must be a function");
81
+ }
82
+ return {
83
+ id: arg1,
84
+ kind: "custom",
85
+ field: arg1,
86
+ render: arg2
87
+ };
88
+ }
43
89
  return {
44
- id: options.id,
90
+ id: arg1.id,
45
91
  kind: "custom",
46
- header: options.header,
47
- field: options.field,
48
- render: options.render
92
+ header: arg1.header,
93
+ field: arg1.field,
94
+ render: arg1.render
49
95
  };
50
96
  }
51
97
  var col = {
52
98
  text,
53
99
  date,
54
100
  boolean,
101
+ badge,
102
+ number,
55
103
  actions,
56
104
  custom
57
105
  };
@@ -99,6 +147,25 @@ function renderCell(column, row, isLoading, setConfirmState) {
99
147
  }
100
148
  return value ? "Yes" : "No";
101
149
  }
150
+ case "badge": {
151
+ const value = row[column.field];
152
+ const valueStr = String(value ?? "");
153
+ const mapped = column.map?.[valueStr];
154
+ const label = mapped?.label ?? valueStr;
155
+ const tone = mapped?.tone ?? "neutral";
156
+ return /* @__PURE__ */ jsx("span", { className: `rowakit-badge rowakit-badge-${tone}`, children: label });
157
+ }
158
+ case "number": {
159
+ const value = row[column.field];
160
+ const numValue = Number(value ?? 0);
161
+ if (column.format) {
162
+ if (typeof column.format === "function") {
163
+ return column.format(numValue, row);
164
+ }
165
+ return new Intl.NumberFormat(void 0, column.format).format(numValue);
166
+ }
167
+ return numValue.toLocaleString();
168
+ }
102
169
  case "actions": {
103
170
  return /* @__PURE__ */ jsx("div", { className: "rowakit-table-actions", children: column.actions.map((action) => {
104
171
  const isDisabled = isLoading || action.disabled === true || typeof action.disabled === "function" && action.disabled(row);
@@ -143,7 +210,8 @@ function RowaKitTable({
143
210
  defaultPageSize = 20,
144
211
  pageSizeOptions = [10, 20, 50],
145
212
  rowKey,
146
- className = ""
213
+ className = "",
214
+ enableFilters = false
147
215
  }) {
148
216
  const [dataState, setDataState] = useState({
149
217
  state: "idle",
@@ -154,8 +222,27 @@ function RowaKitTable({
154
222
  page: 1,
155
223
  pageSize: defaultPageSize
156
224
  });
225
+ const [filters, setFilters] = useState({});
157
226
  const [confirmState, setConfirmState] = useState(null);
158
227
  const requestIdRef = useRef(0);
228
+ useEffect(() => {
229
+ if (!enableFilters) return;
230
+ const activeFilters = {};
231
+ let hasFilters = false;
232
+ for (const [field, value] of Object.entries(filters)) {
233
+ if (value !== void 0) {
234
+ activeFilters[field] = value;
235
+ hasFilters = true;
236
+ }
237
+ }
238
+ const filtersToSend = hasFilters ? activeFilters : void 0;
239
+ setQuery((prev) => ({
240
+ ...prev,
241
+ filters: filtersToSend,
242
+ page: 1
243
+ // Reset page to 1 when filters change
244
+ }));
245
+ }, [filters, enableFilters]);
159
246
  useEffect(() => {
160
247
  const currentRequestId = ++requestIdRef.current;
161
248
  setDataState((prev) => ({ ...prev, state: "loading" }));
@@ -223,38 +310,190 @@ function RowaKitTable({
223
310
  }
224
311
  return query.sort.direction === "asc" ? " \u2191" : " \u2193";
225
312
  };
313
+ const handleFilterChange = (field, value) => {
314
+ setFilters((prev) => ({
315
+ ...prev,
316
+ [field]: value
317
+ }));
318
+ };
319
+ const handleClearFilter = (field) => {
320
+ setFilters((prev) => {
321
+ const newFilters = { ...prev };
322
+ delete newFilters[field];
323
+ return newFilters;
324
+ });
325
+ };
326
+ const handleClearAllFilters = () => {
327
+ setFilters({});
328
+ };
226
329
  const isLoading = dataState.state === "loading";
227
330
  const isError = dataState.state === "error";
228
331
  const isEmpty = dataState.state === "empty";
229
332
  const totalPages = Math.ceil(dataState.total / query.pageSize);
230
333
  const canGoPrevious = query.page > 1 && !isLoading;
231
334
  const canGoNext = query.page < totalPages && !isLoading;
335
+ const hasActiveFilters = enableFilters && Object.values(filters).some((v) => v !== void 0);
232
336
  return /* @__PURE__ */ jsxs("div", { className: `rowakit-table${className ? ` ${className}` : ""}`, children: [
337
+ hasActiveFilters && /* @__PURE__ */ jsx("div", { className: "rowakit-table-filter-controls", children: /* @__PURE__ */ jsx(
338
+ "button",
339
+ {
340
+ onClick: handleClearAllFilters,
341
+ className: "rowakit-button rowakit-button-secondary",
342
+ type: "button",
343
+ children: "Clear all filters"
344
+ }
345
+ ) }),
233
346
  /* @__PURE__ */ jsxs("table", { children: [
234
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => {
235
- const isSortable = column.kind !== "actions" && (column.kind === "custom" ? false : column.sortable === true);
236
- const field = column.kind === "actions" ? "" : column.kind === "custom" ? column.field : column.field;
237
- return /* @__PURE__ */ jsxs(
238
- "th",
239
- {
240
- onClick: isSortable ? () => handleSort(String(field)) : void 0,
241
- role: isSortable ? "button" : void 0,
242
- tabIndex: isSortable ? 0 : void 0,
243
- onKeyDown: isSortable ? (e) => {
244
- if (e.key === "Enter" || e.key === " ") {
245
- e.preventDefault();
246
- handleSort(String(field));
347
+ /* @__PURE__ */ jsxs("thead", { children: [
348
+ /* @__PURE__ */ jsx("tr", { children: columns.map((column) => {
349
+ const isSortable = column.kind !== "actions" && (column.kind === "custom" ? false : column.sortable === true);
350
+ const field = column.kind === "actions" ? "" : column.kind === "custom" ? column.field : column.field;
351
+ return /* @__PURE__ */ jsxs(
352
+ "th",
353
+ {
354
+ onClick: isSortable ? () => handleSort(String(field)) : void 0,
355
+ role: isSortable ? "button" : void 0,
356
+ tabIndex: isSortable ? 0 : void 0,
357
+ onKeyDown: isSortable ? (e) => {
358
+ if (e.key === "Enter" || e.key === " ") {
359
+ e.preventDefault();
360
+ handleSort(String(field));
361
+ }
362
+ } : void 0,
363
+ "aria-sort": isSortable && query.sort?.field === String(field) ? query.sort.direction === "asc" ? "ascending" : "descending" : void 0,
364
+ style: {
365
+ width: column.width ? `${column.width}px` : void 0,
366
+ textAlign: column.align
367
+ },
368
+ className: column.truncate ? "rowakit-cell-truncate" : void 0,
369
+ children: [
370
+ getHeaderLabel(column),
371
+ isSortable && getSortIndicator(String(field))
372
+ ]
373
+ },
374
+ column.id
375
+ );
376
+ }) }),
377
+ enableFilters && /* @__PURE__ */ jsx("tr", { className: "rowakit-table-filter-row", children: columns.map((column) => {
378
+ const field = column.kind === "actions" || column.kind === "custom" ? "" : String(column.field);
379
+ const canFilter = field && column.kind !== "actions";
380
+ if (!canFilter) {
381
+ return /* @__PURE__ */ jsx("th", {}, column.id);
382
+ }
383
+ const filterValue = filters[field];
384
+ if (column.kind === "badge") {
385
+ const options = column.map ? Object.keys(column.map) : [];
386
+ return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs(
387
+ "select",
388
+ {
389
+ className: "rowakit-filter-select",
390
+ value: filterValue?.op === "equals" ? String(filterValue.value ?? "") : "",
391
+ onChange: (e) => {
392
+ const value = e.target.value;
393
+ if (value === "") {
394
+ handleClearFilter(field);
395
+ } else {
396
+ handleFilterChange(field, { op: "equals", value });
397
+ }
398
+ },
399
+ children: [
400
+ /* @__PURE__ */ jsx("option", { value: "", children: "All" }),
401
+ options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
402
+ ]
247
403
  }
248
- } : void 0,
249
- "aria-sort": isSortable && query.sort?.field === String(field) ? query.sort.direction === "asc" ? "ascending" : "descending" : void 0,
250
- children: [
251
- getHeaderLabel(column),
252
- isSortable && getSortIndicator(String(field))
253
- ]
254
- },
255
- column.id
256
- );
257
- }) }) }),
404
+ ) }, column.id);
405
+ }
406
+ if (column.kind === "boolean") {
407
+ return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs(
408
+ "select",
409
+ {
410
+ className: "rowakit-filter-select",
411
+ value: filterValue?.op === "equals" && typeof filterValue.value === "boolean" ? String(filterValue.value) : "",
412
+ onChange: (e) => {
413
+ const value = e.target.value;
414
+ if (value === "") {
415
+ handleClearFilter(field);
416
+ } else {
417
+ handleFilterChange(field, { op: "equals", value: value === "true" });
418
+ }
419
+ },
420
+ children: [
421
+ /* @__PURE__ */ jsx("option", { value: "", children: "All" }),
422
+ /* @__PURE__ */ jsx("option", { value: "true", children: "True" }),
423
+ /* @__PURE__ */ jsx("option", { value: "false", children: "False" })
424
+ ]
425
+ }
426
+ ) }, column.id);
427
+ }
428
+ if (column.kind === "date") {
429
+ const fromValue = filterValue?.op === "range" ? filterValue.value.from ?? "" : "";
430
+ const toValue = filterValue?.op === "range" ? filterValue.value.to ?? "" : "";
431
+ return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs("div", { className: "rowakit-filter-date-range", children: [
432
+ /* @__PURE__ */ jsx(
433
+ "input",
434
+ {
435
+ type: "date",
436
+ className: "rowakit-filter-input",
437
+ placeholder: "From",
438
+ value: fromValue,
439
+ onChange: (e) => {
440
+ const from = e.target.value || void 0;
441
+ const to = toValue || void 0;
442
+ if (!from && !to) {
443
+ handleClearFilter(field);
444
+ } else {
445
+ handleFilterChange(field, { op: "range", value: { from, to } });
446
+ }
447
+ }
448
+ }
449
+ ),
450
+ /* @__PURE__ */ jsx(
451
+ "input",
452
+ {
453
+ type: "date",
454
+ className: "rowakit-filter-input",
455
+ placeholder: "To",
456
+ value: toValue,
457
+ onChange: (e) => {
458
+ const to = e.target.value || void 0;
459
+ const from = fromValue || void 0;
460
+ if (!from && !to) {
461
+ handleClearFilter(field);
462
+ } else {
463
+ handleFilterChange(field, { op: "range", value: { from, to } });
464
+ }
465
+ }
466
+ }
467
+ )
468
+ ] }) }, column.id);
469
+ }
470
+ const isNumberColumn = column.kind === "number";
471
+ return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx(
472
+ "input",
473
+ {
474
+ type: isNumberColumn ? "number" : "text",
475
+ className: "rowakit-filter-input",
476
+ placeholder: `Filter ${getHeaderLabel(column)}...`,
477
+ value: filterValue?.op === "contains" ? filterValue.value : filterValue?.op === "equals" && typeof filterValue.value === "string" ? filterValue.value : filterValue?.op === "equals" && typeof filterValue.value === "number" ? String(filterValue.value) : "",
478
+ onChange: (e) => {
479
+ const rawValue = e.target.value;
480
+ if (rawValue === "") {
481
+ handleClearFilter(field);
482
+ } else if (isNumberColumn) {
483
+ const numValue = Number(rawValue);
484
+ if (!isNaN(numValue)) {
485
+ handleFilterChange(field, { op: "equals", value: rawValue });
486
+ } else {
487
+ handleClearFilter(field);
488
+ }
489
+ } else {
490
+ handleFilterChange(field, { op: "contains", value: rawValue });
491
+ }
492
+ }
493
+ }
494
+ ) }, column.id);
495
+ }) })
496
+ ] }),
258
497
  /* @__PURE__ */ jsxs("tbody", { children: [
259
498
  isLoading && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: columns.length, className: "rowakit-table-loading", children: [
260
499
  /* @__PURE__ */ jsx("div", { className: "rowakit-table-loading-spinner" }),
@@ -275,7 +514,24 @@ function RowaKitTable({
275
514
  isEmpty && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: columns.length, className: "rowakit-table-empty", children: "No data" }) }),
276
515
  dataState.state === "success" && dataState.items.map((row) => {
277
516
  const key = getRowKey(row, rowKey);
278
- return /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx("td", { children: renderCell(column, row, isLoading, setConfirmState) }, column.id)) }, key);
517
+ return /* @__PURE__ */ jsx("tr", { children: columns.map((column) => {
518
+ const cellClass = [
519
+ column.kind === "number" ? "rowakit-cell-number" : "",
520
+ column.truncate ? "rowakit-cell-truncate" : ""
521
+ ].filter(Boolean).join(" ") || void 0;
522
+ return /* @__PURE__ */ jsx(
523
+ "td",
524
+ {
525
+ className: cellClass,
526
+ style: {
527
+ width: column.width ? `${column.width}px` : void 0,
528
+ textAlign: column.align || (column.kind === "number" ? "right" : void 0)
529
+ },
530
+ children: renderCell(column, row, isLoading, setConfirmState)
531
+ },
532
+ column.id
533
+ );
534
+ }) }, key);
279
535
  })
280
536
  ] })
281
537
  ] }),