@shotleybuilder/svelte-table-kit 0.6.0 → 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 +13 -1
- package/dist/TableKit.svelte +76 -9
- package/dist/TableKit.svelte.d.ts +8 -8
- package/dist/components/CellContextMenu.svelte +208 -0
- package/dist/components/CellContextMenu.svelte.d.ts +41 -0
- package/dist/components/ColumnMenu.svelte +3 -3
- package/dist/components/FilterBar.svelte +76 -0
- package/dist/components/FilterBar.svelte.d.ts +3 -0
- package/dist/components/FilterCondition.svelte +639 -42
- package/dist/components/FilterCondition.svelte.d.ts +7 -0
- package/dist/components/GroupBar.svelte +5 -2
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -2
- package/dist/stores/persistence.d.ts +10 -1
- package/dist/stores/persistence.js +14 -1
- package/dist/types.d.ts +20 -0
- package/dist/utils/filters.d.ts +13 -2
- package/dist/utils/filters.js +75 -0
- package/dist/utils/fuzzy.d.ts +47 -0
- package/dist/utils/fuzzy.js +142 -0
- package/package.json +76 -76
|
@@ -5,6 +5,13 @@ declare const __propDef: {
|
|
|
5
5
|
props: {
|
|
6
6
|
condition: FilterCondition;
|
|
7
7
|
columns: ColumnDef<any>[];
|
|
8
|
+
columnOrder?: string[];
|
|
9
|
+
storageKey?: string;
|
|
10
|
+
columnValues?: string[];
|
|
11
|
+
numericRange?: {
|
|
12
|
+
min: number;
|
|
13
|
+
max: number;
|
|
14
|
+
} | null;
|
|
8
15
|
onUpdate: (condition: FilterCondition) => void;
|
|
9
16
|
onRemove: () => void;
|
|
10
17
|
};
|
|
@@ -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
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export { default as TableKit } from './TableKit.svelte';
|
|
2
2
|
export { default as FilterBar } from './components/FilterBar.svelte';
|
|
3
|
-
export { default as
|
|
3
|
+
export { default as FilterConditionEditor } from './components/FilterCondition.svelte';
|
|
4
4
|
export { default as GroupBar } from './components/GroupBar.svelte';
|
|
5
|
-
export
|
|
5
|
+
export { default as CellContextMenu } from './components/CellContextMenu.svelte';
|
|
6
|
+
export type { TableKitProps, TableConfig, ViewPreset, FilterCondition, FilterOperator, FilterLogic, ColumnOrderMode, ColumnDataType, ColumnMeta, SortConfig, ClassNameMap, TableFeatures, TableState } from './types';
|
|
6
7
|
export { presets } from './presets';
|
|
7
8
|
export { generateTableConfig, validateTableConfig, mergeConfigs } from './utils/config';
|
|
8
|
-
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';
|
|
9
11
|
export { formatDate, formatCurrency, formatNumber, formatPercent } from './utils/formatters';
|
|
12
|
+
export { fuzzyMatch, fuzzySearch, highlightMatches } from './utils/fuzzy';
|
|
13
|
+
export type { FuzzyMatch } from './utils/fuzzy';
|
package/dist/index.js
CHANGED
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
export { default as TableKit } from './TableKit.svelte';
|
|
4
4
|
// Sub-components
|
|
5
5
|
export { default as FilterBar } from './components/FilterBar.svelte';
|
|
6
|
-
export { default as
|
|
6
|
+
export { default as FilterConditionEditor } from './components/FilterCondition.svelte';
|
|
7
7
|
export { default as GroupBar } from './components/GroupBar.svelte';
|
|
8
|
+
export { default as CellContextMenu } from './components/CellContextMenu.svelte';
|
|
8
9
|
// Presets
|
|
9
10
|
export { presets } from './presets';
|
|
10
11
|
// Utilities for AI configuration
|
|
11
12
|
export { generateTableConfig, validateTableConfig, mergeConfigs } from './utils/config';
|
|
12
13
|
// Filter utilities
|
|
13
|
-
export { createTextFilter, createSelectFilter, createNumericFilter, evaluateCondition, applyFilters } from './utils/filters';
|
|
14
|
+
export { createTextFilter, createSelectFilter, createNumericFilter, evaluateCondition, applyFilters, getOperatorsForType } from './utils/filters';
|
|
14
15
|
// Formatters
|
|
15
16
|
export { formatDate, formatCurrency, formatNumber, formatPercent } from './utils/formatters';
|
|
17
|
+
// Fuzzy search utilities
|
|
18
|
+
export { fuzzyMatch, fuzzySearch, highlightMatches } from './utils/fuzzy';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { VisibilityState, ColumnSizingState, ColumnFiltersState, ColumnOrderState, SortingState, PaginationState } from '@tanstack/svelte-table';
|
|
2
|
+
import type { ColumnOrderMode } from '../types';
|
|
2
3
|
/**
|
|
3
4
|
* Check if we're in a browser environment
|
|
4
|
-
*
|
|
5
|
+
* Uses cross-bundler compatible check instead of SvelteKit-specific imports
|
|
5
6
|
*/
|
|
6
7
|
export declare const isBrowser: boolean;
|
|
7
8
|
/**
|
|
@@ -52,6 +53,14 @@ export declare function loadPagination(storageKey: string, defaultPageSize?: num
|
|
|
52
53
|
* Save pagination state to localStorage
|
|
53
54
|
*/
|
|
54
55
|
export declare function savePagination(storageKey: string, state: PaginationState): void;
|
|
56
|
+
/**
|
|
57
|
+
* Load filter column order mode from localStorage
|
|
58
|
+
*/
|
|
59
|
+
export declare function loadFilterColumnOrderMode(storageKey: string): ColumnOrderMode;
|
|
60
|
+
/**
|
|
61
|
+
* Save filter column order mode to localStorage
|
|
62
|
+
*/
|
|
63
|
+
export declare function saveFilterColumnOrderMode(storageKey: string, mode: ColumnOrderMode): void;
|
|
55
64
|
/**
|
|
56
65
|
* Clear all table state from localStorage
|
|
57
66
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// LocalStorage persistence utilities
|
|
2
2
|
/**
|
|
3
3
|
* Check if we're in a browser environment
|
|
4
|
-
*
|
|
4
|
+
* Uses cross-bundler compatible check instead of SvelteKit-specific imports
|
|
5
5
|
*/
|
|
6
6
|
export const isBrowser = typeof window !== 'undefined' && typeof localStorage !== 'undefined';
|
|
7
7
|
/**
|
|
@@ -107,6 +107,18 @@ export function loadPagination(storageKey, defaultPageSize = 10) {
|
|
|
107
107
|
export function savePagination(storageKey, state) {
|
|
108
108
|
saveToStorage(`${storageKey}_pagination`, state);
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Load filter column order mode from localStorage
|
|
112
|
+
*/
|
|
113
|
+
export function loadFilterColumnOrderMode(storageKey) {
|
|
114
|
+
return loadFromStorage(`${storageKey}_filter_column_order_mode`, 'definition');
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Save filter column order mode to localStorage
|
|
118
|
+
*/
|
|
119
|
+
export function saveFilterColumnOrderMode(storageKey, mode) {
|
|
120
|
+
saveToStorage(`${storageKey}_filter_column_order_mode`, mode);
|
|
121
|
+
}
|
|
110
122
|
/**
|
|
111
123
|
* Clear all table state from localStorage
|
|
112
124
|
*/
|
|
@@ -120,6 +132,7 @@ export function clearTableState(storageKey) {
|
|
|
120
132
|
localStorage.removeItem(`${storageKey}_column_order`);
|
|
121
133
|
localStorage.removeItem(`${storageKey}_sorting`);
|
|
122
134
|
localStorage.removeItem(`${storageKey}_pagination`);
|
|
135
|
+
localStorage.removeItem(`${storageKey}_filter_column_order_mode`);
|
|
123
136
|
}
|
|
124
137
|
catch (error) {
|
|
125
138
|
console.error('Failed to clear table state:', error);
|
package/dist/types.d.ts
CHANGED
|
@@ -54,6 +54,26 @@ export interface ViewPreset {
|
|
|
54
54
|
}
|
|
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
|
+
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
|
+
}
|
|
57
77
|
export interface FilterCondition {
|
|
58
78
|
id: string;
|
|
59
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
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fuzzy search utilities for column/field picker
|
|
3
|
+
*/
|
|
4
|
+
export interface FuzzyMatch {
|
|
5
|
+
/** The original string that was matched */
|
|
6
|
+
text: string;
|
|
7
|
+
/** The match score (higher = better match) */
|
|
8
|
+
score: number;
|
|
9
|
+
/** Indices of characters that matched in the original string */
|
|
10
|
+
matchedIndices: number[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Perform fuzzy matching of a search pattern against a target string.
|
|
14
|
+
* Returns null if no match, or a FuzzyMatch object with score and matched indices.
|
|
15
|
+
*
|
|
16
|
+
* Scoring:
|
|
17
|
+
* - Consecutive matches score higher
|
|
18
|
+
* - Matches at word boundaries score higher
|
|
19
|
+
* - Matches at the start of the string score higher
|
|
20
|
+
* - Case-insensitive matching
|
|
21
|
+
*
|
|
22
|
+
* @param pattern - The search pattern (e.g., "cdt" to find "Created Date")
|
|
23
|
+
* @param target - The target string to search in
|
|
24
|
+
* @returns FuzzyMatch object or null if no match
|
|
25
|
+
*/
|
|
26
|
+
export declare function fuzzyMatch(pattern: string, target: string): FuzzyMatch | null;
|
|
27
|
+
/**
|
|
28
|
+
* Search through an array of strings and return matches sorted by score.
|
|
29
|
+
*
|
|
30
|
+
* @param pattern - The search pattern
|
|
31
|
+
* @param items - Array of strings to search
|
|
32
|
+
* @param limit - Maximum number of results to return (default: all)
|
|
33
|
+
* @returns Array of FuzzyMatch objects sorted by score (highest first)
|
|
34
|
+
*/
|
|
35
|
+
export declare function fuzzySearch(pattern: string, items: string[], limit?: number): FuzzyMatch[];
|
|
36
|
+
/**
|
|
37
|
+
* Highlight matched characters in a string by wrapping them in a specified tag.
|
|
38
|
+
* Returns an array of segments with matched/unmatched flags for rendering.
|
|
39
|
+
*
|
|
40
|
+
* @param text - The original text
|
|
41
|
+
* @param matchedIndices - Array of indices that matched
|
|
42
|
+
* @returns Array of text segments with isMatch flag
|
|
43
|
+
*/
|
|
44
|
+
export declare function highlightMatches(text: string, matchedIndices: number[]): {
|
|
45
|
+
text: string;
|
|
46
|
+
isMatch: boolean;
|
|
47
|
+
}[];
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fuzzy search utilities for column/field picker
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Perform fuzzy matching of a search pattern against a target string.
|
|
6
|
+
* Returns null if no match, or a FuzzyMatch object with score and matched indices.
|
|
7
|
+
*
|
|
8
|
+
* Scoring:
|
|
9
|
+
* - Consecutive matches score higher
|
|
10
|
+
* - Matches at word boundaries score higher
|
|
11
|
+
* - Matches at the start of the string score higher
|
|
12
|
+
* - Case-insensitive matching
|
|
13
|
+
*
|
|
14
|
+
* @param pattern - The search pattern (e.g., "cdt" to find "Created Date")
|
|
15
|
+
* @param target - The target string to search in
|
|
16
|
+
* @returns FuzzyMatch object or null if no match
|
|
17
|
+
*/
|
|
18
|
+
export function fuzzyMatch(pattern, target) {
|
|
19
|
+
if (!pattern) {
|
|
20
|
+
return { text: target, score: 0, matchedIndices: [] };
|
|
21
|
+
}
|
|
22
|
+
const patternLower = pattern.toLowerCase();
|
|
23
|
+
const targetLower = target.toLowerCase();
|
|
24
|
+
// Quick check: all pattern characters must exist in target
|
|
25
|
+
let patternIdx = 0;
|
|
26
|
+
for (let i = 0; i < targetLower.length && patternIdx < patternLower.length; i++) {
|
|
27
|
+
if (targetLower[i] === patternLower[patternIdx]) {
|
|
28
|
+
patternIdx++;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (patternIdx !== patternLower.length) {
|
|
32
|
+
return null; // Not all pattern characters found
|
|
33
|
+
}
|
|
34
|
+
// Find optimal match positions using greedy algorithm
|
|
35
|
+
const matchedIndices = [];
|
|
36
|
+
let score = 0;
|
|
37
|
+
patternIdx = 0;
|
|
38
|
+
let lastMatchIdx = -1;
|
|
39
|
+
let consecutiveCount = 0;
|
|
40
|
+
for (let i = 0; i < targetLower.length && patternIdx < patternLower.length; i++) {
|
|
41
|
+
if (targetLower[i] === patternLower[patternIdx]) {
|
|
42
|
+
matchedIndices.push(i);
|
|
43
|
+
// Base score for match
|
|
44
|
+
score += 1;
|
|
45
|
+
// Bonus for consecutive matches
|
|
46
|
+
if (lastMatchIdx === i - 1) {
|
|
47
|
+
consecutiveCount++;
|
|
48
|
+
score += consecutiveCount * 2;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
consecutiveCount = 0;
|
|
52
|
+
}
|
|
53
|
+
// Bonus for match at start of string
|
|
54
|
+
if (i === 0) {
|
|
55
|
+
score += 10;
|
|
56
|
+
}
|
|
57
|
+
// Bonus for match at word boundary (after space, underscore, or uppercase)
|
|
58
|
+
if (i > 0) {
|
|
59
|
+
const prevChar = target[i - 1];
|
|
60
|
+
const currChar = target[i];
|
|
61
|
+
if (prevChar === ' ' || prevChar === '_' || prevChar === '-') {
|
|
62
|
+
score += 5;
|
|
63
|
+
}
|
|
64
|
+
else if (currChar === currChar.toUpperCase() && currChar !== currChar.toLowerCase()) {
|
|
65
|
+
// CamelCase boundary
|
|
66
|
+
score += 3;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
lastMatchIdx = i;
|
|
70
|
+
patternIdx++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Bonus for shorter target strings (prefer more specific matches)
|
|
74
|
+
score += Math.max(0, 20 - target.length);
|
|
75
|
+
// Bonus if pattern matches significant portion of target
|
|
76
|
+
const coverageRatio = pattern.length / target.length;
|
|
77
|
+
score += Math.floor(coverageRatio * 10);
|
|
78
|
+
return {
|
|
79
|
+
text: target,
|
|
80
|
+
score,
|
|
81
|
+
matchedIndices
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Search through an array of strings and return matches sorted by score.
|
|
86
|
+
*
|
|
87
|
+
* @param pattern - The search pattern
|
|
88
|
+
* @param items - Array of strings to search
|
|
89
|
+
* @param limit - Maximum number of results to return (default: all)
|
|
90
|
+
* @returns Array of FuzzyMatch objects sorted by score (highest first)
|
|
91
|
+
*/
|
|
92
|
+
export function fuzzySearch(pattern, items, limit) {
|
|
93
|
+
if (!pattern) {
|
|
94
|
+
// Return all items with zero score when no pattern
|
|
95
|
+
const results = items.map((text) => ({ text, score: 0, matchedIndices: [] }));
|
|
96
|
+
return limit ? results.slice(0, limit) : results;
|
|
97
|
+
}
|
|
98
|
+
const matches = [];
|
|
99
|
+
for (const item of items) {
|
|
100
|
+
const match = fuzzyMatch(pattern, item);
|
|
101
|
+
if (match) {
|
|
102
|
+
matches.push(match);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Sort by score descending
|
|
106
|
+
matches.sort((a, b) => b.score - a.score);
|
|
107
|
+
return limit ? matches.slice(0, limit) : matches;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Highlight matched characters in a string by wrapping them in a specified tag.
|
|
111
|
+
* Returns an array of segments with matched/unmatched flags for rendering.
|
|
112
|
+
*
|
|
113
|
+
* @param text - The original text
|
|
114
|
+
* @param matchedIndices - Array of indices that matched
|
|
115
|
+
* @returns Array of text segments with isMatch flag
|
|
116
|
+
*/
|
|
117
|
+
export function highlightMatches(text, matchedIndices) {
|
|
118
|
+
if (!matchedIndices.length) {
|
|
119
|
+
return [{ text, isMatch: false }];
|
|
120
|
+
}
|
|
121
|
+
const matchSet = new Set(matchedIndices);
|
|
122
|
+
const segments = [];
|
|
123
|
+
let currentSegment = '';
|
|
124
|
+
let currentIsMatch = matchSet.has(0);
|
|
125
|
+
for (let i = 0; i < text.length; i++) {
|
|
126
|
+
const isMatch = matchSet.has(i);
|
|
127
|
+
if (isMatch === currentIsMatch) {
|
|
128
|
+
currentSegment += text[i];
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
if (currentSegment) {
|
|
132
|
+
segments.push({ text: currentSegment, isMatch: currentIsMatch });
|
|
133
|
+
}
|
|
134
|
+
currentSegment = text[i];
|
|
135
|
+
currentIsMatch = isMatch;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (currentSegment) {
|
|
139
|
+
segments.push({ text: currentSegment, isMatch: currentIsMatch });
|
|
140
|
+
}
|
|
141
|
+
return segments;
|
|
142
|
+
}
|
package/package.json
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
2
|
+
"name": "@shotleybuilder/svelte-table-kit",
|
|
3
|
+
"version": "0.12.0",
|
|
4
|
+
"description": "A comprehensive, AI-configurable data table component for Svelte and SvelteKit, built on TanStack Table v8",
|
|
5
|
+
"author": "Sertantai",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"svelte",
|
|
9
|
+
"sveltekit",
|
|
10
|
+
"table",
|
|
11
|
+
"datagrid",
|
|
12
|
+
"tanstack",
|
|
13
|
+
"data-table",
|
|
14
|
+
"airtable",
|
|
15
|
+
"headless",
|
|
16
|
+
"typescript"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/shotleybuilder/svelte-table-kit.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/shotleybuilder/svelte-table-kit/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/shotleybuilder/svelte-table-kit#readme",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"svelte": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"svelte": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./styles": "./dist/styles/table-kit.css"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"!dist/**/*.test.*",
|
|
39
|
+
"!dist/**/*.spec.*"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"dev": "vite dev",
|
|
43
|
+
"build": "vite build && npm run package",
|
|
44
|
+
"package": "svelte-kit sync && svelte-package && publint",
|
|
45
|
+
"prepublishOnly": "npm run package",
|
|
46
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
47
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
48
|
+
"test": "vitest",
|
|
49
|
+
"test:ui": "vitest --ui",
|
|
50
|
+
"test:run": "vitest run",
|
|
51
|
+
"lint": "prettier --check . && eslint .",
|
|
52
|
+
"format": "prettier --write ."
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"svelte": "^4.0.0 || ^5.0.0",
|
|
56
|
+
"@tanstack/svelte-table": "^8.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@sveltejs/adapter-auto": "^3.0.0",
|
|
60
|
+
"@sveltejs/kit": "^2.0.0",
|
|
61
|
+
"@sveltejs/package": "^2.0.0",
|
|
62
|
+
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
|
63
|
+
"@tanstack/svelte-table": "^8.21.3",
|
|
64
|
+
"@types/eslint": "^9.6.0",
|
|
65
|
+
"eslint": "^9.0.0",
|
|
66
|
+
"eslint-config-prettier": "^9.1.0",
|
|
67
|
+
"eslint-plugin-svelte": "^2.36.0",
|
|
68
|
+
"prettier": "^3.1.1",
|
|
69
|
+
"prettier-plugin-svelte": "^3.1.2",
|
|
70
|
+
"publint": "^0.1.9",
|
|
71
|
+
"svelte": "^4.2.7",
|
|
72
|
+
"svelte-check": "^3.6.0",
|
|
73
|
+
"tslib": "^2.4.1",
|
|
74
|
+
"typescript": "^5.0.0",
|
|
75
|
+
"vite": "^5.0.11",
|
|
76
|
+
"vitest": "^2.0.0"
|
|
77
|
+
}
|
|
78
78
|
}
|