@shotleybuilder/svelte-table-kit 0.10.1 → 0.12.0
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/README.md +2 -1
- package/dist/TableKit.svelte +10 -8
- package/dist/TableKit.svelte.d.ts +9 -9
- package/dist/components/FilterCondition.svelte +117 -63
- package/dist/components/GroupBar.svelte +5 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/types.d.ts +19 -0
- package/dist/utils/filters.d.ts +13 -2
- package/dist/utils/filters.js +75 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Svelte Table Kit brings Airtable-like functionality to your Svelte applications
|
|
|
26
26
|
- 📋 **Column context menu** - Quick access to sort, filter, group, and hide actions
|
|
27
27
|
|
|
28
28
|
**Advanced Filtering:**
|
|
29
|
-
-
|
|
29
|
+
- 14 filter operators: equals, contains, starts with, greater than, is_before, is_after, etc.
|
|
30
30
|
- AND/OR logic between conditions
|
|
31
31
|
- Collapsible FilterBar UI (space-efficient)
|
|
32
32
|
- Active filter count badge
|
|
@@ -35,6 +35,7 @@ Svelte Table Kit brings Airtable-like functionality to your Svelte applications
|
|
|
35
35
|
- **Fuzzy search** - Type to quickly find columns in large tables with highlighted matches (v0.8.0+)
|
|
36
36
|
- **Value suggestions** - Autocomplete dropdown shows existing column values as you type (v0.10.0+)
|
|
37
37
|
- **Numeric range hints** - Numeric columns display min/max range in the value input (v0.10.0+)
|
|
38
|
+
- **Data type awareness** - Operators and value inputs adapt based on column data type (v0.11.0+)
|
|
38
39
|
|
|
39
40
|
**Sorting Options:**
|
|
40
41
|
- **Column header mode** (default) - Click headers to sort with ↑↓↕ indicators
|
package/dist/TableKit.svelte
CHANGED
|
@@ -47,6 +47,9 @@ export let features = {
|
|
|
47
47
|
export let onRowClick = void 0;
|
|
48
48
|
export let onRowSelect = void 0;
|
|
49
49
|
export let onStateChange = void 0;
|
|
50
|
+
function getColumnId(col) {
|
|
51
|
+
return col.accessorKey || col.id || "";
|
|
52
|
+
}
|
|
50
53
|
let sorting = writable([]);
|
|
51
54
|
let columnVisibility = writable({});
|
|
52
55
|
let columnSizing = writable({});
|
|
@@ -79,7 +82,7 @@ $: {
|
|
|
79
82
|
if (config.defaultVisibleColumns && columns.length > 0) {
|
|
80
83
|
const visibilityMap = {};
|
|
81
84
|
columns.forEach((col) => {
|
|
82
|
-
const colId = col
|
|
85
|
+
const colId = getColumnId(col);
|
|
83
86
|
if (config && config.defaultVisibleColumns) {
|
|
84
87
|
visibilityMap[colId] = config.defaultVisibleColumns.includes(colId);
|
|
85
88
|
}
|
|
@@ -293,7 +296,7 @@ function handleDrop(targetColumnId) {
|
|
|
293
296
|
draggedColumnId = null;
|
|
294
297
|
}
|
|
295
298
|
$: if ($columnOrder.length === 0 && columns.length > 0) {
|
|
296
|
-
columnOrder.set(columns.map((col) => col
|
|
299
|
+
columnOrder.set(columns.map((col) => getColumnId(col)));
|
|
297
300
|
}
|
|
298
301
|
function showCellContextMenu(event, cell) {
|
|
299
302
|
if (features.filtering === false) return;
|
|
@@ -333,8 +336,8 @@ $: if (onStateChange) {
|
|
|
333
336
|
columnVisibility: $columnVisibility,
|
|
334
337
|
columnOrder: $columnOrder,
|
|
335
338
|
columnSizing: $columnSizing,
|
|
336
|
-
columnFilters: $
|
|
337
|
-
sorting: $sorting,
|
|
339
|
+
columnFilters: $filterConditions,
|
|
340
|
+
sorting: $sorting.map((s) => ({ columnId: s.id, direction: s.desc ? "desc" : "asc" })),
|
|
338
341
|
pagination: $table.getState().pagination
|
|
339
342
|
});
|
|
340
343
|
}
|
|
@@ -639,8 +642,10 @@ $: if (onStateChange) {
|
|
|
639
642
|
>
|
|
640
643
|
{#if !header.isPlaceholder}
|
|
641
644
|
<div class="th-wrapper">
|
|
645
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
642
646
|
<div
|
|
643
647
|
class="th-content"
|
|
648
|
+
role={features.columnReordering !== false ? 'button' : undefined}
|
|
644
649
|
style="padding: {verticalPadding}rem {horizontalPadding}rem; cursor: {features.columnReordering !==
|
|
645
650
|
false
|
|
646
651
|
? 'grab'
|
|
@@ -660,10 +665,7 @@ $: if (onStateChange) {
|
|
|
660
665
|
/>
|
|
661
666
|
</span>
|
|
662
667
|
<span class="sort-icon">
|
|
663
|
-
{
|
|
664
|
-
asc: '↑',
|
|
665
|
-
desc: '↓'
|
|
666
|
-
}[header.column.getIsSorted()] ?? '↕'}
|
|
668
|
+
{header.column.getIsSorted() === 'asc' ? '↑' : header.column.getIsSorted() === 'desc' ? '↓' : '↕'}
|
|
667
669
|
</span>
|
|
668
670
|
</button>
|
|
669
671
|
{:else}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
|
2
2
|
import { type ColumnDef } from '@tanstack/svelte-table';
|
|
3
3
|
import type { TableKitProps } from './types';
|
|
4
|
-
declare class __sveltets_Render<T
|
|
4
|
+
declare class __sveltets_Render<T extends Record<string, unknown>> {
|
|
5
5
|
props(): {
|
|
6
6
|
data?: T[] | undefined;
|
|
7
7
|
columns?: ColumnDef<T>[] | undefined;
|
|
8
8
|
config?: TableKitProps<T_1>["config"];
|
|
9
|
-
storageKey?: TableKitProps<T_1>["storageKey"]
|
|
9
|
+
storageKey?: NonNullable<TableKitProps<T_1>["storageKey"]>;
|
|
10
10
|
persistState?: TableKitProps<T_1>["persistState"];
|
|
11
11
|
align?: TableKitProps<T_1>["align"];
|
|
12
12
|
rowHeight?: TableKitProps<T_1>["rowHeight"];
|
|
13
13
|
columnSpacing?: TableKitProps<T_1>["columnSpacing"];
|
|
14
|
-
features?: TableKitProps<T_1>["features"]
|
|
14
|
+
features?: NonNullable<TableKitProps<T_1>["features"]>;
|
|
15
15
|
onRowClick?: ((row: T) => void) | undefined;
|
|
16
|
-
onRowSelect?: ((rows: T[]) => void) | undefined;
|
|
16
|
+
/** @deprecated Row selection not yet implemented - reserved for future use */ onRowSelect?: ((rows: T[]) => void) | undefined;
|
|
17
17
|
onStateChange?: TableKitProps<T_1>["onStateChange"];
|
|
18
18
|
};
|
|
19
19
|
events(): {} & {
|
|
@@ -23,14 +23,14 @@ declare class __sveltets_Render<T> {
|
|
|
23
23
|
'toolbar-left': {};
|
|
24
24
|
empty: {};
|
|
25
25
|
cell: {
|
|
26
|
-
cell: import("@tanstack/table-core").Cell<
|
|
26
|
+
cell: import("@tanstack/table-core").Cell<T, unknown>;
|
|
27
27
|
column: string;
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
-
export type TableKitProps_<T
|
|
32
|
-
export type TableKitEvents<T
|
|
33
|
-
export type TableKitSlots<T
|
|
34
|
-
export default class TableKit<T
|
|
31
|
+
export type TableKitProps_<T extends Record<string, unknown>> = ReturnType<__sveltets_Render<T>['props']>;
|
|
32
|
+
export type TableKitEvents<T extends Record<string, unknown>> = ReturnType<__sveltets_Render<T>['events']>;
|
|
33
|
+
export type TableKitSlots<T extends Record<string, unknown>> = ReturnType<__sveltets_Render<T>['slots']>;
|
|
34
|
+
export default class TableKit<T extends Record<string, unknown>> extends SvelteComponent<TableKitProps_<T>, TableKitEvents<T>, TableKitSlots<T>> {
|
|
35
35
|
}
|
|
36
36
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script>import { loadFilterColumnOrderMode, saveFilterColumnOrderMode } from "../stores/persistence";
|
|
2
2
|
import { fuzzyMatch, highlightMatches } from "../utils/fuzzy";
|
|
3
|
+
import { getOperatorsForType } from "../utils/filters";
|
|
3
4
|
import { onMount, tick } from "svelte";
|
|
4
5
|
export let condition;
|
|
5
6
|
export let columns;
|
|
@@ -131,25 +132,30 @@ $: filteredColumns = (() => {
|
|
|
131
132
|
$: if (filteredColumns) {
|
|
132
133
|
highlightedIndex = 0;
|
|
133
134
|
}
|
|
135
|
+
$: selectedColumn = condition.field ? columns.find((c) => getColumnId(c) === condition.field) : null;
|
|
134
136
|
$: selectedColumnLabel = (() => {
|
|
135
137
|
if (!condition.field) return "";
|
|
136
|
-
|
|
137
|
-
return col ? getColumnLabel(col) : condition.field;
|
|
138
|
+
return selectedColumn ? getColumnLabel(selectedColumn) : condition.field;
|
|
138
139
|
})();
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
140
|
+
$: columnDataType = (() => {
|
|
141
|
+
if (!selectedColumn) return "text";
|
|
142
|
+
const meta = selectedColumn.meta;
|
|
143
|
+
return meta?.dataType || "text";
|
|
144
|
+
})();
|
|
145
|
+
$: selectOptions = (() => {
|
|
146
|
+
if (!selectedColumn) return [];
|
|
147
|
+
const meta = selectedColumn.meta;
|
|
148
|
+
return meta?.selectOptions || [];
|
|
149
|
+
})();
|
|
150
|
+
$: operatorOptions = getOperatorsForType(columnDataType);
|
|
151
|
+
$: {
|
|
152
|
+
if (condition.field && operatorOptions.length > 0) {
|
|
153
|
+
const currentOperatorValid = operatorOptions.some((op) => op.value === condition.operator);
|
|
154
|
+
if (!currentOperatorValid) {
|
|
155
|
+
onUpdate({ ...condition, operator: "equals" });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
153
159
|
function selectColumn(col) {
|
|
154
160
|
const field = getColumnId(col);
|
|
155
161
|
onUpdate({ ...condition, field });
|
|
@@ -339,54 +345,102 @@ $: valueDisabled = condition.operator === "is_empty" || condition.operator === "
|
|
|
339
345
|
</select>
|
|
340
346
|
|
|
341
347
|
<div class="value-input-wrapper" bind:this={valueSuggestionsRef}>
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
<
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
{#each
|
|
364
|
-
<
|
|
365
|
-
class="suggestion-option"
|
|
366
|
-
class:highlighted={i === valueSuggestionIndex}
|
|
367
|
-
on:click={() => selectValueSuggestion(value)}
|
|
368
|
-
on:mouseenter={() => (valueSuggestionIndex = i)}
|
|
369
|
-
type="button"
|
|
370
|
-
>
|
|
371
|
-
{#if matchedIndices.length > 0}
|
|
372
|
-
{#each highlightMatches(value, matchedIndices) as segment}
|
|
373
|
-
{#if segment.isMatch}
|
|
374
|
-
<mark class="match-highlight">{segment.text}</mark>
|
|
375
|
-
{:else}
|
|
376
|
-
{segment.text}
|
|
377
|
-
{/if}
|
|
378
|
-
{/each}
|
|
379
|
-
{:else}
|
|
380
|
-
{value}
|
|
381
|
-
{/if}
|
|
382
|
-
</button>
|
|
348
|
+
{#if columnDataType === 'boolean'}
|
|
349
|
+
<!-- Boolean: dropdown with true/false -->
|
|
350
|
+
<select
|
|
351
|
+
class="value-input"
|
|
352
|
+
value={condition.value || ''}
|
|
353
|
+
on:change={handleValueChange}
|
|
354
|
+
disabled={valueDisabled}
|
|
355
|
+
>
|
|
356
|
+
<option value="">Select...</option>
|
|
357
|
+
<option value="true">True</option>
|
|
358
|
+
<option value="false">False</option>
|
|
359
|
+
</select>
|
|
360
|
+
{:else if columnDataType === 'select' && selectOptions.length > 0}
|
|
361
|
+
<!-- Select: dropdown with options from meta -->
|
|
362
|
+
<select
|
|
363
|
+
class="value-input"
|
|
364
|
+
value={condition.value || ''}
|
|
365
|
+
on:change={handleValueChange}
|
|
366
|
+
disabled={valueDisabled}
|
|
367
|
+
>
|
|
368
|
+
<option value="">Select...</option>
|
|
369
|
+
{#each selectOptions as opt}
|
|
370
|
+
<option value={opt.value}>{opt.label}</option>
|
|
383
371
|
{/each}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
{
|
|
389
|
-
|
|
372
|
+
</select>
|
|
373
|
+
{:else if columnDataType === 'date'}
|
|
374
|
+
<!-- Date: date input -->
|
|
375
|
+
<input
|
|
376
|
+
bind:this={valueInputRef}
|
|
377
|
+
type="date"
|
|
378
|
+
class="value-input"
|
|
379
|
+
value={condition.value || ''}
|
|
380
|
+
on:input={handleValueChange}
|
|
381
|
+
disabled={valueDisabled}
|
|
382
|
+
/>
|
|
383
|
+
{:else if columnDataType === 'number'}
|
|
384
|
+
<!-- Number: number input with range hint -->
|
|
385
|
+
<input
|
|
386
|
+
bind:this={valueInputRef}
|
|
387
|
+
type="number"
|
|
388
|
+
class="value-input"
|
|
389
|
+
value={condition.value || ''}
|
|
390
|
+
on:input={handleValueChange}
|
|
391
|
+
disabled={valueDisabled}
|
|
392
|
+
placeholder={valueDisabled ? 'N/A' : numericRange ? `${numericRange.min} - ${numericRange.max}` : 'Enter number...'}
|
|
393
|
+
/>
|
|
394
|
+
{#if numericRange && !valueDisabled}
|
|
395
|
+
<div class="numeric-range-hint">
|
|
396
|
+
Range: {numericRange.min} - {numericRange.max}
|
|
397
|
+
</div>
|
|
398
|
+
{/if}
|
|
399
|
+
{:else}
|
|
400
|
+
<!-- Text: text input with autocomplete suggestions -->
|
|
401
|
+
<input
|
|
402
|
+
bind:this={valueInputRef}
|
|
403
|
+
type="text"
|
|
404
|
+
class="value-input"
|
|
405
|
+
value={condition.value || ''}
|
|
406
|
+
on:input={handleValueChange}
|
|
407
|
+
on:focus={handleValueFocus}
|
|
408
|
+
on:keydown={handleValueKeydown}
|
|
409
|
+
disabled={valueDisabled}
|
|
410
|
+
placeholder={valueDisabled ? 'N/A' : 'Enter value...'}
|
|
411
|
+
autocomplete="off"
|
|
412
|
+
/>
|
|
413
|
+
|
|
414
|
+
{#if showValueSuggestions && filteredValueSuggestions.length > 0}
|
|
415
|
+
<div class="value-suggestions">
|
|
416
|
+
{#each filteredValueSuggestions as { value, matchedIndices }, i}
|
|
417
|
+
<button
|
|
418
|
+
class="suggestion-option"
|
|
419
|
+
class:highlighted={i === valueSuggestionIndex}
|
|
420
|
+
on:click={() => selectValueSuggestion(value)}
|
|
421
|
+
on:mouseenter={() => (valueSuggestionIndex = i)}
|
|
422
|
+
type="button"
|
|
423
|
+
>
|
|
424
|
+
{#if matchedIndices.length > 0}
|
|
425
|
+
{#each highlightMatches(value, matchedIndices) as segment}
|
|
426
|
+
{#if segment.isMatch}
|
|
427
|
+
<mark class="match-highlight">{segment.text}</mark>
|
|
428
|
+
{:else}
|
|
429
|
+
{segment.text}
|
|
430
|
+
{/if}
|
|
431
|
+
{/each}
|
|
432
|
+
{:else}
|
|
433
|
+
{value}
|
|
434
|
+
{/if}
|
|
435
|
+
</button>
|
|
436
|
+
{/each}
|
|
437
|
+
{#if columnValues.length > 50}
|
|
438
|
+
<div class="suggestions-overflow">
|
|
439
|
+
and {columnValues.length - 50} more...
|
|
440
|
+
</div>
|
|
441
|
+
{/if}
|
|
442
|
+
</div>
|
|
443
|
+
{/if}
|
|
390
444
|
{/if}
|
|
391
445
|
</div>
|
|
392
446
|
|
|
@@ -31,8 +31,11 @@ function clearAllGroups() {
|
|
|
31
31
|
onGroupingChange([]);
|
|
32
32
|
setExpanded(false);
|
|
33
33
|
}
|
|
34
|
+
function getColumnId(col) {
|
|
35
|
+
return col.accessorKey || col.id || "";
|
|
36
|
+
}
|
|
34
37
|
$: availableColumns = columns.filter((col) => {
|
|
35
|
-
const columnId = col
|
|
38
|
+
const columnId = getColumnId(col);
|
|
36
39
|
return columnId && col.enableGrouping !== false;
|
|
37
40
|
});
|
|
38
41
|
$: hasGroups = grouping.length > 0;
|
|
@@ -85,7 +88,7 @@ $: canAddMore = grouping.length < MAX_LEVELS;
|
|
|
85
88
|
>
|
|
86
89
|
<option value="">Select field...</option>
|
|
87
90
|
{#each availableColumns as column}
|
|
88
|
-
{@const columnId = column
|
|
91
|
+
{@const columnId = getColumnId(column)}
|
|
89
92
|
<option value={columnId}>
|
|
90
93
|
{column.header || columnId}
|
|
91
94
|
</option>
|
package/dist/index.d.ts
CHANGED
|
@@ -3,10 +3,11 @@ export { default as FilterBar } from './components/FilterBar.svelte';
|
|
|
3
3
|
export { default as FilterConditionEditor } from './components/FilterCondition.svelte';
|
|
4
4
|
export { default as GroupBar } from './components/GroupBar.svelte';
|
|
5
5
|
export { default as CellContextMenu } from './components/CellContextMenu.svelte';
|
|
6
|
-
export type { TableKitProps, TableConfig, ViewPreset, FilterCondition, FilterOperator, FilterLogic, ColumnOrderMode, SortConfig, ClassNameMap, TableFeatures, TableState } from './types';
|
|
6
|
+
export type { TableKitProps, TableConfig, ViewPreset, FilterCondition, FilterOperator, FilterLogic, ColumnOrderMode, ColumnDataType, ColumnMeta, SortConfig, ClassNameMap, TableFeatures, TableState } from './types';
|
|
7
7
|
export { presets } from './presets';
|
|
8
8
|
export { generateTableConfig, validateTableConfig, mergeConfigs } from './utils/config';
|
|
9
|
-
export { createTextFilter, createSelectFilter, createNumericFilter, evaluateCondition, applyFilters } from './utils/filters';
|
|
9
|
+
export { createTextFilter, createSelectFilter, createNumericFilter, evaluateCondition, applyFilters, getOperatorsForType } from './utils/filters';
|
|
10
|
+
export type { OperatorOption } from './utils/filters';
|
|
10
11
|
export { formatDate, formatCurrency, formatNumber, formatPercent } from './utils/formatters';
|
|
11
12
|
export { fuzzyMatch, fuzzySearch, highlightMatches } from './utils/fuzzy';
|
|
12
13
|
export type { FuzzyMatch } from './utils/fuzzy';
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export { presets } from './presets';
|
|
|
11
11
|
// Utilities for AI configuration
|
|
12
12
|
export { generateTableConfig, validateTableConfig, mergeConfigs } from './utils/config';
|
|
13
13
|
// Filter utilities
|
|
14
|
-
export { createTextFilter, createSelectFilter, createNumericFilter, evaluateCondition, applyFilters } from './utils/filters';
|
|
14
|
+
export { createTextFilter, createSelectFilter, createNumericFilter, evaluateCondition, applyFilters, getOperatorsForType } from './utils/filters';
|
|
15
15
|
// Formatters
|
|
16
16
|
export { formatDate, formatCurrency, formatNumber, formatPercent } from './utils/formatters';
|
|
17
17
|
// Fuzzy search utilities
|
package/dist/types.d.ts
CHANGED
|
@@ -55,6 +55,25 @@ export interface ViewPreset {
|
|
|
55
55
|
export type FilterOperator = 'equals' | 'not_equals' | 'contains' | 'not_contains' | 'starts_with' | 'ends_with' | 'is_empty' | 'is_not_empty' | 'greater_than' | 'less_than' | 'greater_or_equal' | 'less_or_equal' | 'is_before' | 'is_after';
|
|
56
56
|
export type FilterLogic = 'and' | 'or';
|
|
57
57
|
export type ColumnOrderMode = 'definition' | 'ui' | 'alphabetical';
|
|
58
|
+
/**
|
|
59
|
+
* Column data type for filter operator adaptation
|
|
60
|
+
*/
|
|
61
|
+
export type ColumnDataType = 'text' | 'number' | 'date' | 'boolean' | 'select';
|
|
62
|
+
/**
|
|
63
|
+
* Extended column meta for data type awareness
|
|
64
|
+
* Use in column definitions: { meta: { dataType: 'number', selectOptions: [...] } }
|
|
65
|
+
*/
|
|
66
|
+
export interface ColumnMeta {
|
|
67
|
+
/** Data type for filter operator adaptation */
|
|
68
|
+
dataType?: ColumnDataType;
|
|
69
|
+
/** Options for 'select' type columns */
|
|
70
|
+
selectOptions?: {
|
|
71
|
+
value: string;
|
|
72
|
+
label: string;
|
|
73
|
+
}[];
|
|
74
|
+
/** Column group for grouped picker (future feature) */
|
|
75
|
+
group?: string;
|
|
76
|
+
}
|
|
58
77
|
export interface FilterCondition {
|
|
59
78
|
id: string;
|
|
60
79
|
field: string;
|
package/dist/utils/filters.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import type { FilterCondition, FilterLogic } from '../types';
|
|
1
|
+
import type { FilterCondition, FilterOperator, FilterLogic, ColumnDataType } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Operator option for UI display
|
|
4
|
+
*/
|
|
5
|
+
export interface OperatorOption {
|
|
6
|
+
value: FilterOperator;
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get operators available for a specific data type
|
|
11
|
+
*/
|
|
12
|
+
export declare function getOperatorsForType(dataType?: ColumnDataType): OperatorOption[];
|
|
2
13
|
/**
|
|
3
14
|
* Evaluate a single filter condition against a row value
|
|
4
15
|
*/
|
|
@@ -6,7 +17,7 @@ export declare function evaluateCondition(condition: FilterCondition, rowValue:
|
|
|
6
17
|
/**
|
|
7
18
|
* Filter data array by multiple conditions with AND or OR logic
|
|
8
19
|
*/
|
|
9
|
-
export declare function applyFilters<T extends Record<string,
|
|
20
|
+
export declare function applyFilters<T extends Record<string, unknown>>(data: T[], conditions: FilterCondition[], logic?: FilterLogic): T[];
|
|
10
21
|
/**
|
|
11
22
|
* Create a text filter configuration
|
|
12
23
|
*/
|
package/dist/utils/filters.js
CHANGED
|
@@ -1,4 +1,69 @@
|
|
|
1
1
|
// Filter creation and evaluation utilities
|
|
2
|
+
/**
|
|
3
|
+
* All available operators with labels
|
|
4
|
+
*/
|
|
5
|
+
const ALL_OPERATORS = [
|
|
6
|
+
{ value: 'equals', label: 'equals' },
|
|
7
|
+
{ value: 'not_equals', label: 'does not equal' },
|
|
8
|
+
{ value: 'contains', label: 'contains' },
|
|
9
|
+
{ value: 'not_contains', label: 'does not contain' },
|
|
10
|
+
{ value: 'starts_with', label: 'starts with' },
|
|
11
|
+
{ value: 'ends_with', label: 'ends with' },
|
|
12
|
+
{ value: 'is_empty', label: 'is empty' },
|
|
13
|
+
{ value: 'is_not_empty', label: 'is not empty' },
|
|
14
|
+
{ value: 'greater_than', label: '>' },
|
|
15
|
+
{ value: 'less_than', label: '<' },
|
|
16
|
+
{ value: 'greater_or_equal', label: '>=' },
|
|
17
|
+
{ value: 'less_or_equal', label: '<=' },
|
|
18
|
+
{ value: 'is_before', label: 'is before' },
|
|
19
|
+
{ value: 'is_after', label: 'is after' }
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Get operators available for a specific data type
|
|
23
|
+
*/
|
|
24
|
+
export function getOperatorsForType(dataType = 'text') {
|
|
25
|
+
switch (dataType) {
|
|
26
|
+
case 'text':
|
|
27
|
+
return ALL_OPERATORS.filter((op) => [
|
|
28
|
+
'equals',
|
|
29
|
+
'not_equals',
|
|
30
|
+
'contains',
|
|
31
|
+
'not_contains',
|
|
32
|
+
'starts_with',
|
|
33
|
+
'ends_with',
|
|
34
|
+
'is_empty',
|
|
35
|
+
'is_not_empty'
|
|
36
|
+
].includes(op.value));
|
|
37
|
+
case 'number':
|
|
38
|
+
return ALL_OPERATORS.filter((op) => [
|
|
39
|
+
'equals',
|
|
40
|
+
'not_equals',
|
|
41
|
+
'greater_than',
|
|
42
|
+
'less_than',
|
|
43
|
+
'greater_or_equal',
|
|
44
|
+
'less_or_equal',
|
|
45
|
+
'is_empty',
|
|
46
|
+
'is_not_empty'
|
|
47
|
+
].includes(op.value));
|
|
48
|
+
case 'date':
|
|
49
|
+
return ALL_OPERATORS.filter((op) => ['equals', 'not_equals', 'is_before', 'is_after', 'is_empty', 'is_not_empty'].includes(op.value));
|
|
50
|
+
case 'boolean':
|
|
51
|
+
return ALL_OPERATORS.filter((op) => ['equals', 'is_empty', 'is_not_empty'].includes(op.value));
|
|
52
|
+
case 'select':
|
|
53
|
+
return ALL_OPERATORS.filter((op) => ['equals', 'not_equals', 'is_empty', 'is_not_empty'].includes(op.value));
|
|
54
|
+
default:
|
|
55
|
+
return ALL_OPERATORS.filter((op) => [
|
|
56
|
+
'equals',
|
|
57
|
+
'not_equals',
|
|
58
|
+
'contains',
|
|
59
|
+
'not_contains',
|
|
60
|
+
'starts_with',
|
|
61
|
+
'ends_with',
|
|
62
|
+
'is_empty',
|
|
63
|
+
'is_not_empty'
|
|
64
|
+
].includes(op.value));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
2
67
|
/**
|
|
3
68
|
* Evaluate a single filter condition against a row value
|
|
4
69
|
*/
|
|
@@ -32,6 +97,16 @@ export function evaluateCondition(condition, rowValue) {
|
|
|
32
97
|
return Number(rowValue) >= Number(value);
|
|
33
98
|
case 'less_or_equal':
|
|
34
99
|
return Number(rowValue) <= Number(value);
|
|
100
|
+
case 'is_before': {
|
|
101
|
+
const rowDate = new Date(rowValue);
|
|
102
|
+
const filterDate = new Date(value);
|
|
103
|
+
return !isNaN(rowDate.getTime()) && !isNaN(filterDate.getTime()) && rowDate < filterDate;
|
|
104
|
+
}
|
|
105
|
+
case 'is_after': {
|
|
106
|
+
const rowDate = new Date(rowValue);
|
|
107
|
+
const filterDate = new Date(value);
|
|
108
|
+
return !isNaN(rowDate.getTime()) && !isNaN(filterDate.getTime()) && rowDate > filterDate;
|
|
109
|
+
}
|
|
35
110
|
default:
|
|
36
111
|
return true;
|
|
37
112
|
}
|
package/package.json
CHANGED