@shotleybuilder/svelte-gridlite-kit 0.1.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 +260 -0
- package/dist/GridLite.svelte +1361 -0
- package/dist/GridLite.svelte.d.ts +42 -0
- package/dist/components/CellContextMenu.svelte +209 -0
- package/dist/components/CellContextMenu.svelte.d.ts +28 -0
- package/dist/components/ColumnMenu.svelte +234 -0
- package/dist/components/ColumnMenu.svelte.d.ts +29 -0
- package/dist/components/ColumnPicker.svelte +403 -0
- package/dist/components/ColumnPicker.svelte.d.ts +29 -0
- package/dist/components/FilterBar.svelte +390 -0
- package/dist/components/FilterBar.svelte.d.ts +38 -0
- package/dist/components/FilterCondition.svelte +643 -0
- package/dist/components/FilterCondition.svelte.d.ts +35 -0
- package/dist/components/GroupBar.svelte +463 -0
- package/dist/components/GroupBar.svelte.d.ts +33 -0
- package/dist/components/RowDetailModal.svelte +213 -0
- package/dist/components/RowDetailModal.svelte.d.ts +25 -0
- package/dist/components/SortBar.svelte +232 -0
- package/dist/components/SortBar.svelte.d.ts +30 -0
- package/dist/components/SortCondition.svelte +129 -0
- package/dist/components/SortCondition.svelte.d.ts +30 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +29 -0
- package/dist/query/builder.d.ts +160 -0
- package/dist/query/builder.js +432 -0
- package/dist/query/live.d.ts +50 -0
- package/dist/query/live.js +118 -0
- package/dist/query/schema.d.ts +30 -0
- package/dist/query/schema.js +75 -0
- package/dist/state/migrations.d.ts +29 -0
- package/dist/state/migrations.js +113 -0
- package/dist/state/views.d.ts +54 -0
- package/dist/state/views.js +130 -0
- package/dist/styles/gridlite.css +966 -0
- package/dist/types.d.ts +164 -0
- package/dist/types.js +2 -0
- package/dist/utils/filters.d.ts +14 -0
- package/dist/utils/filters.js +49 -0
- package/dist/utils/formatters.d.ts +16 -0
- package/dist/utils/formatters.js +39 -0
- package/dist/utils/fuzzy.d.ts +47 -0
- package/dist/utils/fuzzy.js +142 -0
- package/package.json +76 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { PGlite } from "@electric-sql/pglite";
|
|
2
|
+
export interface GridLiteProps {
|
|
3
|
+
/** PGLite database instance */
|
|
4
|
+
db: PGlite;
|
|
5
|
+
/** Table name to query (mutually exclusive with `query`) */
|
|
6
|
+
table?: string;
|
|
7
|
+
/** Raw SQL query (mutually exclusive with `table`) */
|
|
8
|
+
query?: string;
|
|
9
|
+
/** Grid configuration */
|
|
10
|
+
config?: GridConfig;
|
|
11
|
+
/** Feature flags */
|
|
12
|
+
features?: GridFeatures;
|
|
13
|
+
/** Styling */
|
|
14
|
+
classNames?: Partial<ClassNameMap>;
|
|
15
|
+
rowHeight?: RowHeight;
|
|
16
|
+
columnSpacing?: ColumnSpacing;
|
|
17
|
+
/** Callbacks */
|
|
18
|
+
onRowClick?: (row: Record<string, unknown>) => void;
|
|
19
|
+
onStateChange?: (state: GridState) => void;
|
|
20
|
+
}
|
|
21
|
+
export interface GridFeatures {
|
|
22
|
+
columnVisibility?: boolean;
|
|
23
|
+
columnResizing?: boolean;
|
|
24
|
+
columnReordering?: boolean;
|
|
25
|
+
filtering?: boolean;
|
|
26
|
+
sorting?: boolean;
|
|
27
|
+
sortingMode?: "header" | "control";
|
|
28
|
+
pagination?: boolean;
|
|
29
|
+
grouping?: boolean;
|
|
30
|
+
globalSearch?: boolean;
|
|
31
|
+
rowDetail?: boolean;
|
|
32
|
+
rowDetailMode?: "modal" | "drawer" | "inline";
|
|
33
|
+
}
|
|
34
|
+
export interface GridConfig {
|
|
35
|
+
/** Unique identifier for this grid instance (used for state persistence) */
|
|
36
|
+
id: string;
|
|
37
|
+
/** Column configuration overrides */
|
|
38
|
+
columns?: ColumnConfig[];
|
|
39
|
+
/** Default visible columns (by column name) */
|
|
40
|
+
defaultVisibleColumns?: string[];
|
|
41
|
+
/** Default column order (by column name) */
|
|
42
|
+
defaultColumnOrder?: string[];
|
|
43
|
+
/** Default column sizing */
|
|
44
|
+
defaultColumnSizing?: Record<string, number>;
|
|
45
|
+
/** Default filters applied on load */
|
|
46
|
+
defaultFilters?: FilterCondition[];
|
|
47
|
+
/** Default filter logic */
|
|
48
|
+
filterLogic?: FilterLogic;
|
|
49
|
+
/** Default sorting applied on load */
|
|
50
|
+
defaultSorting?: SortConfig[];
|
|
51
|
+
/** Default grouping applied on load */
|
|
52
|
+
defaultGrouping?: GroupConfig[];
|
|
53
|
+
/** Pagination config */
|
|
54
|
+
pagination?: {
|
|
55
|
+
pageSize: number;
|
|
56
|
+
pageSizeOptions?: number[];
|
|
57
|
+
};
|
|
58
|
+
/** Saved view presets */
|
|
59
|
+
presets?: ViewPreset[];
|
|
60
|
+
}
|
|
61
|
+
export interface ColumnConfig {
|
|
62
|
+
/** Column name (must match database column) */
|
|
63
|
+
name: string;
|
|
64
|
+
/** Display label (defaults to column name) */
|
|
65
|
+
label?: string;
|
|
66
|
+
/** Data type override (auto-detected from schema if not set) */
|
|
67
|
+
dataType?: ColumnDataType;
|
|
68
|
+
/** Options for 'select' type columns */
|
|
69
|
+
selectOptions?: {
|
|
70
|
+
value: string;
|
|
71
|
+
label: string;
|
|
72
|
+
}[];
|
|
73
|
+
/** Custom cell formatter */
|
|
74
|
+
format?: (value: unknown) => string;
|
|
75
|
+
/** Whether column is visible by default */
|
|
76
|
+
visible?: boolean;
|
|
77
|
+
/** Column width in pixels */
|
|
78
|
+
width?: number;
|
|
79
|
+
/** Minimum column width */
|
|
80
|
+
minWidth?: number;
|
|
81
|
+
/** Maximum column width */
|
|
82
|
+
maxWidth?: number;
|
|
83
|
+
}
|
|
84
|
+
/** Column data type — drives filter operator selection and value input rendering */
|
|
85
|
+
export type ColumnDataType = "text" | "number" | "date" | "boolean" | "select";
|
|
86
|
+
/** Schema-introspected column metadata (internal, from information_schema) */
|
|
87
|
+
export interface ColumnMetadata {
|
|
88
|
+
name: string;
|
|
89
|
+
dataType: ColumnDataType;
|
|
90
|
+
postgresType: string;
|
|
91
|
+
nullable: boolean;
|
|
92
|
+
hasDefault: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface FilterCondition {
|
|
95
|
+
id: string;
|
|
96
|
+
field: string;
|
|
97
|
+
operator: FilterOperator;
|
|
98
|
+
value: unknown;
|
|
99
|
+
}
|
|
100
|
+
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";
|
|
101
|
+
export type FilterLogic = "and" | "or";
|
|
102
|
+
export interface SortConfig {
|
|
103
|
+
column: string;
|
|
104
|
+
direction: "asc" | "desc";
|
|
105
|
+
}
|
|
106
|
+
export interface GroupConfig {
|
|
107
|
+
column: string;
|
|
108
|
+
aggregations?: AggregationConfig[];
|
|
109
|
+
}
|
|
110
|
+
export interface AggregationConfig {
|
|
111
|
+
column: string;
|
|
112
|
+
function: AggregateFunction;
|
|
113
|
+
alias?: string;
|
|
114
|
+
}
|
|
115
|
+
export type AggregateFunction = "count" | "sum" | "avg" | "min" | "max";
|
|
116
|
+
export interface ViewPreset {
|
|
117
|
+
id: string;
|
|
118
|
+
name: string;
|
|
119
|
+
description?: string;
|
|
120
|
+
filters?: FilterCondition[];
|
|
121
|
+
filterLogic?: FilterLogic;
|
|
122
|
+
sorting?: SortConfig[];
|
|
123
|
+
grouping?: GroupConfig[];
|
|
124
|
+
columnVisibility?: Record<string, boolean>;
|
|
125
|
+
columnOrder?: string[];
|
|
126
|
+
}
|
|
127
|
+
export interface GridState {
|
|
128
|
+
columnVisibility: Record<string, boolean>;
|
|
129
|
+
columnOrder: string[];
|
|
130
|
+
columnSizing: Record<string, number>;
|
|
131
|
+
filters: FilterCondition[];
|
|
132
|
+
filterLogic: FilterLogic;
|
|
133
|
+
sorting: SortConfig[];
|
|
134
|
+
grouping: GroupConfig[];
|
|
135
|
+
globalFilter: string;
|
|
136
|
+
pagination: {
|
|
137
|
+
page: number;
|
|
138
|
+
pageSize: number;
|
|
139
|
+
totalRows: number;
|
|
140
|
+
totalPages: number;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export type RowHeight = "short" | "medium" | "tall" | "extra_tall";
|
|
144
|
+
export type ColumnSpacing = "narrow" | "normal" | "wide";
|
|
145
|
+
export type ToolbarLayout = "airtable" | "excel" | "shadcn" | "aggrid";
|
|
146
|
+
export interface ClassNameMap {
|
|
147
|
+
container: string;
|
|
148
|
+
table: string;
|
|
149
|
+
thead: string;
|
|
150
|
+
tbody: string;
|
|
151
|
+
tfoot: string;
|
|
152
|
+
tr: string;
|
|
153
|
+
th: string;
|
|
154
|
+
td: string;
|
|
155
|
+
pagination: string;
|
|
156
|
+
filterBar: string;
|
|
157
|
+
sortBar: string;
|
|
158
|
+
groupBar: string;
|
|
159
|
+
}
|
|
160
|
+
/** Parameterized SQL query — never construct SQL by string concatenation */
|
|
161
|
+
export interface ParameterizedQuery {
|
|
162
|
+
sql: string;
|
|
163
|
+
params: unknown[];
|
|
164
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter operator utilities
|
|
3
|
+
*
|
|
4
|
+
* Maps ColumnDataType to available filter operators for UI rendering.
|
|
5
|
+
*/
|
|
6
|
+
import type { FilterOperator, ColumnDataType } from '../types.js';
|
|
7
|
+
export interface OperatorOption {
|
|
8
|
+
value: FilterOperator;
|
|
9
|
+
label: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get operators available for a specific data type.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getOperatorsForType(dataType?: ColumnDataType): OperatorOption[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter operator utilities
|
|
3
|
+
*
|
|
4
|
+
* Maps ColumnDataType to available filter operators for UI rendering.
|
|
5
|
+
*/
|
|
6
|
+
const ALL_OPERATORS = [
|
|
7
|
+
{ value: 'equals', label: 'equals' },
|
|
8
|
+
{ value: 'not_equals', label: 'does not equal' },
|
|
9
|
+
{ value: 'contains', label: 'contains' },
|
|
10
|
+
{ value: 'not_contains', label: 'does not contain' },
|
|
11
|
+
{ value: 'starts_with', label: 'starts with' },
|
|
12
|
+
{ value: 'ends_with', label: 'ends with' },
|
|
13
|
+
{ value: 'is_empty', label: 'is empty' },
|
|
14
|
+
{ value: 'is_not_empty', label: 'is not empty' },
|
|
15
|
+
{ value: 'greater_than', label: '>' },
|
|
16
|
+
{ value: 'less_than', label: '<' },
|
|
17
|
+
{ value: 'greater_or_equal', label: '>=' },
|
|
18
|
+
{ value: 'less_or_equal', label: '<=' },
|
|
19
|
+
{ value: 'is_before', label: 'is before' },
|
|
20
|
+
{ value: 'is_after', label: 'is after' }
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Get operators available for a specific data type.
|
|
24
|
+
*/
|
|
25
|
+
export function getOperatorsForType(dataType = 'text') {
|
|
26
|
+
switch (dataType) {
|
|
27
|
+
case 'text':
|
|
28
|
+
return ALL_OPERATORS.filter((op) => [
|
|
29
|
+
'equals', 'not_equals', 'contains', 'not_contains',
|
|
30
|
+
'starts_with', 'ends_with', 'is_empty', 'is_not_empty'
|
|
31
|
+
].includes(op.value));
|
|
32
|
+
case 'number':
|
|
33
|
+
return ALL_OPERATORS.filter((op) => [
|
|
34
|
+
'equals', 'not_equals', 'greater_than', 'less_than',
|
|
35
|
+
'greater_or_equal', 'less_or_equal', 'is_empty', 'is_not_empty'
|
|
36
|
+
].includes(op.value));
|
|
37
|
+
case 'date':
|
|
38
|
+
return ALL_OPERATORS.filter((op) => ['equals', 'not_equals', 'is_before', 'is_after', 'is_empty', 'is_not_empty'].includes(op.value));
|
|
39
|
+
case 'boolean':
|
|
40
|
+
return ALL_OPERATORS.filter((op) => ['equals', 'is_empty', 'is_not_empty'].includes(op.value));
|
|
41
|
+
case 'select':
|
|
42
|
+
return ALL_OPERATORS.filter((op) => ['equals', 'not_equals', 'is_empty', 'is_not_empty'].includes(op.value));
|
|
43
|
+
default:
|
|
44
|
+
return ALL_OPERATORS.filter((op) => [
|
|
45
|
+
'equals', 'not_equals', 'contains', 'not_contains',
|
|
46
|
+
'starts_with', 'ends_with', 'is_empty', 'is_not_empty'
|
|
47
|
+
].includes(op.value));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format date string to localized format
|
|
3
|
+
*/
|
|
4
|
+
export declare function formatDate(dateStr: string | Date, locale?: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Format number as currency
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatCurrency(value: number, currency?: string, locale?: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Format number with locale-specific separators
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatNumber(value: number, locale?: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Format number as percentage
|
|
15
|
+
*/
|
|
16
|
+
export declare function formatPercent(value: number, decimals?: number, locale?: string): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Cell formatting utilities
|
|
2
|
+
/**
|
|
3
|
+
* Format date string to localized format
|
|
4
|
+
*/
|
|
5
|
+
export function formatDate(dateStr, locale = 'en-GB') {
|
|
6
|
+
if (!dateStr)
|
|
7
|
+
return '-';
|
|
8
|
+
const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr;
|
|
9
|
+
return date.toLocaleDateString(locale, {
|
|
10
|
+
day: '2-digit',
|
|
11
|
+
month: 'short',
|
|
12
|
+
year: 'numeric'
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Format number as currency
|
|
17
|
+
*/
|
|
18
|
+
export function formatCurrency(value, currency = 'GBP', locale = 'en-GB') {
|
|
19
|
+
return new Intl.NumberFormat(locale, {
|
|
20
|
+
style: 'currency',
|
|
21
|
+
currency
|
|
22
|
+
}).format(value);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Format number with locale-specific separators
|
|
26
|
+
*/
|
|
27
|
+
export function formatNumber(value, locale = 'en-GB') {
|
|
28
|
+
return new Intl.NumberFormat(locale).format(value);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Format number as percentage
|
|
32
|
+
*/
|
|
33
|
+
export function formatPercent(value, decimals = 1, locale = 'en-GB') {
|
|
34
|
+
return new Intl.NumberFormat(locale, {
|
|
35
|
+
style: 'percent',
|
|
36
|
+
minimumFractionDigits: decimals,
|
|
37
|
+
maximumFractionDigits: decimals
|
|
38
|
+
}).format(value / 100);
|
|
39
|
+
}
|
|
@@ -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
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shotleybuilder/svelte-gridlite-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A SQL-native data grid component for Svelte and SvelteKit, powered by PGLite",
|
|
5
|
+
"author": "Sertantai",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"svelte",
|
|
9
|
+
"sveltekit",
|
|
10
|
+
"datagrid",
|
|
11
|
+
"pglite",
|
|
12
|
+
"postgres",
|
|
13
|
+
"sql",
|
|
14
|
+
"data-table",
|
|
15
|
+
"typescript"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/shotleybuilder/svelte-gridlite-kit.git"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/shotleybuilder/svelte-gridlite-kit/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/shotleybuilder/svelte-gridlite-kit#readme",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"svelte": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"svelte": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./styles": "./dist/styles/gridlite.css"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"!dist/**/*.test.*",
|
|
38
|
+
"!dist/**/*.spec.*"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"dev": "vite dev",
|
|
42
|
+
"build": "vite build && npm run package",
|
|
43
|
+
"package": "svelte-kit sync && svelte-package && publint",
|
|
44
|
+
"prepublishOnly": "npm run package",
|
|
45
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
46
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
47
|
+
"test": "vitest",
|
|
48
|
+
"test:run": "vitest run",
|
|
49
|
+
"lint": "prettier --check . && eslint .",
|
|
50
|
+
"format": "prettier --write ."
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"svelte": "^4.0.0 || ^5.0.0",
|
|
54
|
+
"@electric-sql/pglite": "^0.2.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@electric-sql/pglite": "^0.2.0",
|
|
58
|
+
"@sveltejs/adapter-auto": "^3.0.0",
|
|
59
|
+
"@sveltejs/kit": "^2.0.0",
|
|
60
|
+
"@sveltejs/package": "^2.0.0",
|
|
61
|
+
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
|
62
|
+
"@types/eslint": "^9.6.0",
|
|
63
|
+
"eslint": "^9.0.0",
|
|
64
|
+
"eslint-config-prettier": "^9.1.0",
|
|
65
|
+
"eslint-plugin-svelte": "^2.36.0",
|
|
66
|
+
"prettier": "^3.1.1",
|
|
67
|
+
"prettier-plugin-svelte": "^3.1.2",
|
|
68
|
+
"publint": "^0.1.9",
|
|
69
|
+
"svelte": "^4.2.7",
|
|
70
|
+
"svelte-check": "^3.6.0",
|
|
71
|
+
"tslib": "^2.4.1",
|
|
72
|
+
"typescript": "^5.0.0",
|
|
73
|
+
"vite": "^5.0.11",
|
|
74
|
+
"vitest": "^2.0.0"
|
|
75
|
+
}
|
|
76
|
+
}
|