• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

humanspeak / svelte-headless-table / 12913869200

22 Jan 2025 05:36PM UTC coverage: 34.206% (-25.6%) from 59.766%
12913869200

push

github

web-flow
Merge pull request #9 from humanspeak/feature-version

Feature: 5.x

404 of 538 branches covered (75.09%)

Branch coverage included in aggregate %.

2129 of 3542 new or added lines in 41 files covered. (60.11%)

1 existing line in 1 file now uncovered.

2639 of 8358 relevant lines covered (31.57%)

17.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/lib/plugins/addTableFilter.ts
NEW
1
import type { BodyRow } from '../bodyRows.js'
×
NEW
2
import type { TablePlugin, NewTablePropSet, DeriveRowsFn } from '../types/TablePlugin.js'
×
NEW
3
import { recordSetStore } from '../utils/store.js'
×
NEW
4
import { derived, writable, type Readable, type Writable } from 'svelte/store'
×
NEW
5
import { textPrefixFilter } from './addColumnFilters.js'
×
6

×
7
export interface TableFilterConfig {
×
NEW
8
    fn?: TableFilterFn
×
NEW
9
    initialFilterValue?: string
×
NEW
10
    includeHiddenColumns?: boolean
×
NEW
11
    serverSide?: boolean
×
12
}
×
13

×
14
export interface TableFilterState<Item> {
×
NEW
15
    filterValue: Writable<string>
×
NEW
16
    preFilteredRows: Readable<BodyRow<Item>[]>
×
17
}
×
18

×
19
// Item generic needed to infer type on `getFilteredRows`
×
20
// eslint-disable-next-line @typescript-eslint/no-unused-vars
×
21
export interface TableFilterColumnOptions<Item> {
×
NEW
22
    exclude?: boolean
×
NEW
23
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
×
NEW
24
    getFilterValue?: (value: any) => string
×
25
}
×
26

×
27
// eslint-disable-next-line @typescript-eslint/no-explicit-any
×
NEW
28
export type TableFilterFn = (props: TableFilterFnProps) => boolean
×
29

×
30
// eslint-disable-next-line @typescript-eslint/no-explicit-any
×
31
export type TableFilterFnProps = {
×
NEW
32
    filterValue: string
×
NEW
33
    value: string
×
NEW
34
}
×
35

×
36
export type TableFilterPropSet = NewTablePropSet<{
×
NEW
37
    'tbody.tr.td': {
×
NEW
38
        matches: boolean
×
NEW
39
    }
×
NEW
40
}>
×
41

×
42
interface GetFilteredRowsOptions {
×
NEW
43
    tableCellMatches: Record<string, boolean>
×
NEW
44
    fn: TableFilterFn
×
NEW
45
    includeHiddenColumns: boolean
×
46
}
×
47

×
48
const getFilteredRows = <Item, Row extends BodyRow<Item>>(
×
NEW
49
    rows: Row[],
×
NEW
50
    filterValue: string,
×
NEW
51
    columnOptions: Record<string, TableFilterColumnOptions<Item>>,
×
NEW
52
    { tableCellMatches, fn, includeHiddenColumns }: GetFilteredRowsOptions
×
53
): Row[] => {
×
NEW
54
    const $filteredRows = rows
×
NEW
55
        // Filter `subRows`
×
NEW
56
        .map((row) => {
×
NEW
57
            const { subRows } = row
×
NEW
58
            if (subRows === undefined) {
×
NEW
59
                return row
×
NEW
60
            }
×
NEW
61
            const filteredSubRows = getFilteredRows(subRows, filterValue, columnOptions, {
×
NEW
62
                tableCellMatches,
×
NEW
63
                fn,
×
NEW
64
                includeHiddenColumns
×
NEW
65
            })
×
NEW
66
            const clonedRow = row.clone() as Row
×
NEW
67
            clonedRow.subRows = filteredSubRows
×
NEW
68
            return clonedRow
×
NEW
69
        })
×
NEW
70
        .filter((row) => {
×
NEW
71
            if ((row.subRows?.length ?? 0) !== 0) {
×
NEW
72
                return true
×
NEW
73
            }
×
NEW
74
            // An array of booleans, true if the cell matches the filter.
×
NEW
75
            const rowCellMatches = Object.values(row.cellForId).map((cell) => {
×
NEW
76
                const options = columnOptions[cell.id] as TableFilterColumnOptions<Item> | undefined
×
NEW
77
                if (options?.exclude === true) {
×
NEW
78
                    return false
×
NEW
79
                }
×
NEW
80
                const isHidden = row.cells.find((c) => c.id === cell.id) === undefined
×
NEW
81
                if (isHidden && !includeHiddenColumns) {
×
NEW
82
                    return false
×
NEW
83
                }
×
NEW
84
                if (!cell.isData()) {
×
NEW
85
                    return false
×
NEW
86
                }
×
NEW
87
                let value = cell.value
×
NEW
88
                if (options?.getFilterValue !== undefined) {
×
NEW
89
                    value = options?.getFilterValue(value)
×
NEW
90
                }
×
NEW
91
                const matches = fn({ value: String(value), filterValue })
×
NEW
92
                if (matches) {
×
NEW
93
                    const dataRowColId = cell.dataRowColId()
×
NEW
94
                    if (dataRowColId !== undefined) {
×
NEW
95
                        tableCellMatches[dataRowColId] = matches
×
NEW
96
                    }
×
NEW
97
                }
×
NEW
98
                return matches
×
NEW
99
            })
×
NEW
100
            // If any cell matches, include in the filtered results.
×
NEW
101
            return rowCellMatches.includes(true)
×
NEW
102
        })
×
NEW
103
    return $filteredRows
×
NEW
104
}
×
105

×
106
export const addTableFilter =
×
NEW
107
    <Item>({
×
NEW
108
        fn = textPrefixFilter,
×
NEW
109
        initialFilterValue = '',
×
NEW
110
        includeHiddenColumns = false,
×
NEW
111
        serverSide = false
×
NEW
112
    }: TableFilterConfig = {}): TablePlugin<
×
NEW
113
        Item,
×
NEW
114
        TableFilterState<Item>,
×
NEW
115
        TableFilterColumnOptions<Item>,
×
NEW
116
        TableFilterPropSet
×
NEW
117
    > =>
×
NEW
118
    ({ columnOptions }) => {
×
NEW
119
        const filterValue = writable(initialFilterValue)
×
NEW
120
        const preFilteredRows = writable<BodyRow<Item>[]>([])
×
NEW
121
        const tableCellMatches = recordSetStore()
×
122

×
NEW
123
        const pluginState: TableFilterState<Item> = { filterValue, preFilteredRows }
×
124

×
NEW
125
        const deriveRows: DeriveRowsFn<Item> = (rows) => {
×
NEW
126
            return derived([rows, filterValue], ([$rows, $filterValue]) => {
×
NEW
127
                preFilteredRows.set($rows)
×
NEW
128
                tableCellMatches.clear()
×
NEW
129
                const $tableCellMatches: Record<string, boolean> = {}
×
NEW
130
                const $filteredRows = getFilteredRows($rows, $filterValue, columnOptions, {
×
NEW
131
                    tableCellMatches: $tableCellMatches,
×
NEW
132
                    fn,
×
NEW
133
                    includeHiddenColumns
×
NEW
134
                })
×
NEW
135
                tableCellMatches.set($tableCellMatches)
×
NEW
136
                if (serverSide) {
×
NEW
137
                    return $rows
×
NEW
138
                }
×
NEW
139
                return $filteredRows
×
NEW
140
            })
×
NEW
141
        }
×
142

×
NEW
143
        return {
×
NEW
144
            pluginState,
×
NEW
145
            deriveRows,
×
NEW
146
            hooks: {
×
NEW
147
                'tbody.tr.td': (cell) => {
×
NEW
148
                    const props = derived(
×
NEW
149
                        [filterValue, tableCellMatches],
×
NEW
150
                        ([$filterValue, $tableCellMatches]) => {
×
NEW
151
                            const dataRowColId = cell.dataRowColId()
×
NEW
152
                            return {
×
NEW
153
                                matches:
×
NEW
154
                                    $filterValue !== '' &&
×
NEW
155
                                    dataRowColId !== undefined &&
×
NEW
156
                                    ($tableCellMatches[dataRowColId] ?? false)
×
NEW
157
                            }
×
NEW
158
                        }
×
NEW
159
                    )
×
NEW
160
                    return { props }
×
NEW
161
                }
×
NEW
162
            }
×
NEW
163
        }
×
NEW
164
    }
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc