@hitgrab/finder 0.0.26-alpha → 0.1.3-alpha
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 +7 -1106
- package/dist/core/__tests__/test-types.d.ts +2 -2
- package/dist/core/core-constants.d.ts +39 -0
- package/dist/core/effect-book.d.ts +12 -0
- package/dist/core/{events/event-emitter.d.ts → event-emitter.d.ts} +3 -2
- package/dist/core/filters.d.ts +28 -0
- package/dist/core/finder-core-implementation.d.ts +44 -0
- package/dist/core/finder-core.d.ts +63 -74
- package/dist/core/finder-error.d.ts +3 -0
- package/dist/core/group-by.d.ts +23 -0
- package/dist/core/{pagination/pagination.d.ts → pagination.d.ts} +5 -4
- package/dist/core/rule-book.d.ts +14 -0
- package/dist/core/search/calculate-character-match-indexes.d.ts +1 -0
- package/dist/core/search/calculate-string-match-segments.d.ts +9 -0
- package/dist/core/search/search-score.d.ts +4 -0
- package/dist/core/search/search-string-transform.d.ts +8 -0
- package/dist/core/search/{haystack.d.ts → string-match-haystack.d.ts} +3 -3
- package/dist/core/search.d.ts +18 -0
- package/dist/core/sort-by.d.ts +23 -0
- package/dist/core/tester.d.ts +8 -0
- package/dist/core/types/core-types.d.ts +93 -0
- package/dist/core/types/effect-types.d.ts +20 -0
- package/dist/core/types/event-types.d.ts +44 -0
- package/dist/core/types/rule-types.d.ts +104 -0
- package/dist/core/types/string-match-types.d.ts +10 -0
- package/dist/core/utils/rule-type-enforcers.d.ts +12 -6
- package/dist/core/utils/rule-utils.d.ts +5 -10
- package/dist/index.d.ts +8 -14
- package/dist/index.js +3321 -3595
- package/dist/index.umd.cjs +30 -30
- package/dist/react/components/finder-content-empty.d.ts +6 -0
- package/dist/react/components/finder-content-groups.d.ts +7 -0
- package/dist/react/components/finder-content-items.d.ts +7 -0
- package/dist/react/components/finder-content-loading.d.ts +6 -0
- package/dist/react/components/finder-content-no-matches.d.ts +6 -0
- package/dist/react/components/finder-content.d.ts +20 -8
- package/dist/react/components/finder-search-term-haystack.d.ts +8 -0
- package/dist/react/components/finder.d.ts +7 -1
- package/dist/react/components/string-match.d.ts +12 -0
- package/dist/react/hooks/use-finder-ref.d.ts +1 -1
- package/dist/react/hooks/use-finder.d.ts +1 -5
- package/dist/react/providers/finder-core-context.d.ts +3 -0
- package/dist/react/types/react-types.d.ts +15 -16
- package/package.json +11 -7
- package/dist/core/__tests__/selected-items.test.d.ts +0 -1
- package/dist/core/filters/filters-interface.d.ts +0 -32
- package/dist/core/filters/filters.d.ts +0 -29
- package/dist/core/group-by/group-by-interface.d.ts +0 -21
- package/dist/core/group-by/group-by.d.ts +0 -20
- package/dist/core/layout/layout-interface.d.ts +0 -19
- package/dist/core/layout/layout.d.ts +0 -21
- package/dist/core/meta/meta-interface.d.ts +0 -18
- package/dist/core/meta/meta.d.ts +0 -16
- package/dist/core/pagination/pagination-interface.d.ts +0 -23
- package/dist/core/plugins/plugin-mediator.d.ts +0 -14
- package/dist/core/plugins/plugin-super-class.d.ts +0 -8
- package/dist/core/search/algorithms/sequential-characters.d.ts +0 -2
- package/dist/core/search/algorithms/sequential-string.d.ts +0 -2
- package/dist/core/search/algorithms/unordered-characters.d.ts +0 -2
- package/dist/core/search/result-segments/result-segment-types.d.ts +0 -18
- package/dist/core/search/result-segments/search-result-segments.d.ts +0 -17
- package/dist/core/search/search-interface.d.ts +0 -14
- package/dist/core/search/search.d.ts +0 -16
- package/dist/core/selected-items/selected-items-interface.d.ts +0 -22
- package/dist/core/selected-items/selected-items.d.ts +0 -23
- package/dist/core/sort-by/sort-by-interface.d.ts +0 -23
- package/dist/core/sort-by/sort-by.d.ts +0 -18
- package/dist/core/types/internal-types.d.ts +0 -15
- package/dist/core/utils/finder-utils.d.ts +0 -3
- package/dist/core/utils/string-compare-utils.d.ts +0 -13
- package/dist/react/components/finder-empty.d.ts +0 -6
- package/dist/react/components/finder-groups.d.ts +0 -7
- package/dist/react/components/finder-items.d.ts +0 -7
- package/dist/react/components/finder-loading.d.ts +0 -6
- package/dist/react/components/finder-no-matches.d.ts +0 -6
- package/dist/react/components/finder-search-term.d.ts +0 -7
- package/dist/react/hooks/use-finder-context.d.ts +0 -2
- package/dist/react/providers/finder-context.d.ts +0 -3
- package/dist/types.d.ts +0 -221
- /package/dist/core/__tests__/{layout.test.d.ts → effects.test.d.ts} +0 -0
- /package/dist/core/__tests__/{plugins.test.d.ts → events.test.d.ts} +0 -0
- /package/dist/core/{debounce-callback-registry/debounce-callback-registry.d.ts → debounce-callback-registry.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,1112 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
# finder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Headless datatable management for things that aren't tables.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use static rules to effortlessly sort, filter, search, and group data.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# Installation
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
<div align="center">
|
|
9
|
+
`npm i @hitgrab/finder`
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
[![Filter][Filter]](#filters)
|
|
14
|
-
[![sortBy][sortBy]](#sortby)
|
|
15
|
-
[![groupBy][groupBy]](#groupby)
|
|
16
|
-
[![Pagination][Pagination]](#pagination)
|
|
17
|
-
[![onChangeonInit][onChangeonInit]](#life-cycle-events)
|
|
18
|
-
[![Metadata][Metadata]](#meta)
|
|
11
|
+
# Documentation
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
<details>
|
|
22
|
-
<summary>Table of Contents</summary>
|
|
23
|
-
<ul>
|
|
24
|
-
<li><a href="#basic-usage">Basic Usage</a></li>
|
|
25
|
-
<li><a href="#search">Search</a></li>
|
|
26
|
-
<li><a href="#filters">Filter</a></li>
|
|
27
|
-
<li><a href="#sortby">Sort</a></li>
|
|
28
|
-
<li><a href="#groupby">Group</a></li>
|
|
29
|
-
<li><a href="#events">Events</a></li>
|
|
30
|
-
<li><a href="#pagination">Pagination</a></li>
|
|
31
|
-
<li><a href="#components-and-interfaces">Components and Interfaces</a></li>
|
|
32
|
-
<li><a href="#meta">Meta Mixin</a></li>
|
|
33
|
-
<li><a href="#layout">Layout Mixin</a></li>
|
|
34
|
-
<li><a href="#hooks">Custom Hooks</a></li>
|
|
35
|
-
<li><a href="#utils">Utils</a></li>
|
|
36
|
-
</ul>
|
|
37
|
-
</details>
|
|
38
|
-
|
|
39
|
-
[Search]: https://img.shields.io/badge/Search-20232A?style=for-the-badge
|
|
40
|
-
[Filter]: https://img.shields.io/badge/Filter-ffffff?style=for-the-badge
|
|
41
|
-
[sortBy]: https://img.shields.io/badge/sort-333333?style=for-the-badge
|
|
42
|
-
[groupBy]: https://img.shields.io/badge/group-222222?style=for-the-badge
|
|
43
|
-
[StickyResults]: https://img.shields.io/badge/Sticky_Results-555555?style=for-the-badge
|
|
44
|
-
[onChangeonInit]: https://img.shields.io/badge/Events-444444?style=for-the-badge
|
|
45
|
-
[Pagination]: https://img.shields.io/badge/Paginate-888888?style=for-the-badge
|
|
46
|
-
[Metadata]: https://img.shields.io/badge/Metadata-888888?style=for-the-badge
|
|
47
|
-
|
|
48
|
-
## Basic Usage
|
|
49
|
-
|
|
50
|
-
Finder accepts an array of items, and processes them according to static rules. The matches and current rule state are passed to React context for easy consumption. A robust imperative API is provided to allow developers to build their control components in the theming library of their choice.
|
|
51
|
-
|
|
52
|
-
### Tutorial examples:
|
|
53
|
-
|
|
54
|
-
1. [Kicking Rad Shoe Store](https://stackblitz.com/edit/vitejs-vite-vsu7v2pg?file=src%2Fapp.tsx)
|
|
55
|
-
Demonstrates filtering, sorting, and displays active rules as toggleable chips.
|
|
56
|
-
2. [Ancient Armory](https://stackblitz.com/edit/vitejs-vite-x4ng7k3m?file=src%2Fapp.tsx) Demonstrates searching, filtering, grouping, and the event life cycle.
|
|
57
|
-
|
|
58
|
-
### To all things a pattern
|
|
59
|
-
|
|
60
|
-
Finder processes rules in the following order:
|
|
61
|
-
|
|
62
|
-
1. [Search](#search)
|
|
63
|
-
2. [Filters](#filters)
|
|
64
|
-
3. [SortBy](#sortby)
|
|
65
|
-
4. [Paginate](#pagination)
|
|
66
|
-
5. [GroupBy](#groupby)
|
|
67
|
-
|
|
68
|
-
#### Basic Search Example
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
function SearchControlComponent() {
|
|
72
|
-
const finder = useFinderContext();
|
|
73
|
-
return <input type="text" onInput={(e) => finder.search.setSearchTerm(e.currentTarget.value)} />
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function Gallery() {
|
|
77
|
-
const items = [...]
|
|
78
|
-
const rules = [
|
|
79
|
-
searchRule({
|
|
80
|
-
searchFn: (item, searchTerm) => item.name.includes(searchTerm)
|
|
81
|
-
})
|
|
82
|
-
];
|
|
83
|
-
return(
|
|
84
|
-
<Finder items={items} rules={rules}>
|
|
85
|
-
<SearchControlComponent />
|
|
86
|
-
<FinderItems>
|
|
87
|
-
{(items) =>
|
|
88
|
-
<div>
|
|
89
|
-
{items.map((item) => <ItemDisplay item={item} />)}
|
|
90
|
-
</div>
|
|
91
|
-
</FinderItems>
|
|
92
|
-
</Finder>
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
98
|
-
|
|
99
|
-
## Rules
|
|
100
|
-
|
|
101
|
-
All rules ( with the exception of SearchRules ) must have a unique id for their namespace ( filters, groupBy, etc. ). Finder will throw an error if a rule is missing an id, or two filter rules have the same id.
|
|
102
|
-
|
|
103
|
-
### Search
|
|
104
|
-
|
|
105
|
-
Match text against your item properties.
|
|
106
|
-
|
|
107
|
-
Only a single search rule can be defined per Finder instance. If you need to do multiple kinds of text searches, consider a Filter!
|
|
108
|
-
|
|
109
|
-
```ts
|
|
110
|
-
searchRule({
|
|
111
|
-
searchFn: (item: FItem, searchTerm: string, meta?: FinderMeta) => boolean;
|
|
112
|
-
debounceMilliseconds?: number;
|
|
113
|
-
label?: string;
|
|
114
|
-
hidden?: boolean;
|
|
115
|
-
})
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Pro-tips:
|
|
119
|
-
|
|
120
|
-
- You can use `searchRule()` to ensure your rule has a valid shape.
|
|
121
|
-
- Use `finderStringCompare` to do a case-insensitive search that removes whitespace and line breaks.
|
|
122
|
-
- If you have a large volume of data to process, you can add a `debounceMilliseconds`.
|
|
123
|
-
|
|
124
|
-
<!--
|
|
125
|
-
Buttons for linking to implimentation in the example repo and the docs, we can add these later when we're finalized with the structure!
|
|
126
|
-
[![source][search-source]](/src/classes/mixins/search.ts)
|
|
127
|
-
[![implementation][search-implementation]](https://github.com/HitGrab/finder/blob/7af28570f85b946e173072ebf4e3dcaf706ec02b/examples/react/src/app.tsx#L26)
|
|
128
|
-
|
|
129
|
-
[search-source]: https://img.shields.io/badge/Source_Code-555555?style=for-the-badge
|
|
130
|
-
[search-implementation]: https://img.shields.io/badge/example_implementation-555555?style=for-the-badge
|
|
131
|
-
-->
|
|
132
|
-
|
|
133
|
-
### Filters
|
|
134
|
-
|
|
135
|
-
Define a filter predicate that will return a boolean for each item. If multiple filters are active, _all_ filters must match for an item to be returned.
|
|
136
|
-
|
|
137
|
-
```ts
|
|
138
|
-
filterRule({
|
|
139
|
-
id: string;
|
|
140
|
-
filterFn: (item: FItem, value: FValue, meta?: FinderMeta) => boolean;
|
|
141
|
-
options?: FilterOption<FValue>[] | ((items: FItem[], meta?: FinderMeta) => FilterOption<FValue>[]);
|
|
142
|
-
multiple?: boolean;
|
|
143
|
-
required?: boolean;
|
|
144
|
-
isBoolean?: boolean;
|
|
145
|
-
defaultValue?: FValue;
|
|
146
|
-
label?: string;
|
|
147
|
-
hidden?: boolean;
|
|
148
|
-
debounceMilliseconds?: number;
|
|
149
|
-
})
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
| Prop | Description | Default | Required |
|
|
153
|
-
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
|
|
154
|
-
| id | Every filter rule must have a unique string id. | | ✓ |
|
|
155
|
-
| filterFn | A predicate that returns a boolean. Note that it receives the Meta mixin, which can contain instance-wide arbitrary data. | | ✓ |
|
|
156
|
-
| options | Either an array of form options `[{label: 'Thing', value: 'thing'}]`, or an option generator function that returns options. `(items, meta) => [{label: 'Thing', value: 'thing'}]`. | | |
|
|
157
|
-
| multiple | If this filter has a single value or an array of values. | false | |
|
|
158
|
-
| required | Whether this filter must always have a value. If the rule provides options, the first option will be selected by default. | false | |
|
|
159
|
-
| isBoolean | If this filter has a true/false value. Useful for checkboxes! | false | |
|
|
160
|
-
| defaultValue | If the filter has a preset value. | | |
|
|
161
|
-
| label | Optional label for your client to display. | | |
|
|
162
|
-
| hidden | Optional display value for your client to display. | false | |
|
|
163
|
-
| debounceMilliseconds | If you want to debounce value changes, enter a time in milliseconds. | | |
|
|
164
|
-
|
|
165
|
-
Pro-tips:
|
|
166
|
-
|
|
167
|
-
- If a rule uses an option generator function, Finder will hydrate the rule with a stable options array before emitting it to context.
|
|
168
|
-
|
|
169
|
-
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
170
|
-
|
|
171
|
-
### SortBy
|
|
172
|
-
|
|
173
|
-
This option sorts the items passed in and accepts either a simple value or a function to run on each item to determine the order of return.
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
sortByRule({
|
|
177
|
-
id: string;
|
|
178
|
-
sortFn: FinderPropertySelector<FItem> | FinderPropertySelector<FItem>[];
|
|
179
|
-
defaultSortDirection?: SortDirection;
|
|
180
|
-
label?: string;
|
|
181
|
-
hidden?: boolean;
|
|
182
|
-
})
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
| Prop | Description | Default | Required |
|
|
186
|
-
| -------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
|
|
187
|
-
| id | Every sortBy rule must have a unique string id. | | ✓ |
|
|
188
|
-
| sortFn | A predicate that returns a boolean. Note that it receives the Meta mixin, which can contain instance-wide arbitrary data. | | ✓ |
|
|
189
|
-
| defaultSortDirection | 'asc' or 'desc'. | 'asc' | |
|
|
190
|
-
| label | Optional label for your client to display. | | |
|
|
191
|
-
| hidden | Optional display value for your client to display. | false | |
|
|
192
|
-
|
|
193
|
-
Pro-tips:
|
|
194
|
-
|
|
195
|
-
- Only a single sortBy rule can be active at one time.
|
|
196
|
-
- If any sortBy rules are provided and no specific rule is set, the first sortBy rule in the stack will be considered active.
|
|
197
|
-
|
|
198
|
-
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
199
|
-
|
|
200
|
-
### GroupBy
|
|
201
|
-
|
|
202
|
-
Takes the returned items and groups them by the passed in paramaters.
|
|
203
|
-
|
|
204
|
-
```ts
|
|
205
|
-
groupByRule({
|
|
206
|
-
id: string;
|
|
207
|
-
groupFn: (item) => string | number;
|
|
208
|
-
sortGroupIdFn?: (group) => string | number;
|
|
209
|
-
groupIdSortDirection?: SortDirection;
|
|
210
|
-
sticky?: {
|
|
211
|
-
header?: string | string[];
|
|
212
|
-
footer?: string | string[];
|
|
213
|
-
};
|
|
214
|
-
label?: string;
|
|
215
|
-
hidden?: boolean;
|
|
216
|
-
})
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
| Prop | Description | Default | Required |
|
|
220
|
-
| ------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
|
|
221
|
-
| id | Every groupBy rule must have a unique string id. | | ✓ |
|
|
222
|
-
| groupFn | A predicate operating on the item object that returns a string or number. Items with the same value will be grouped together. | | ✓ |
|
|
223
|
-
| sortGroupIdFn | While any sortBy rules will operate on the items first, sortGroupIdFn allows you to sort the groups themselves. | | |
|
|
224
|
-
| sticky | Specify group ids that should be stickied to the top or bottom of the results. | | |
|
|
225
|
-
| label | Optional label for your client to display. | | |
|
|
226
|
-
| hidden | Optional display value for your client to display. | false | |
|
|
227
|
-
|
|
228
|
-
Pro-tips:
|
|
229
|
-
|
|
230
|
-
- Only a single groupBy rule can be active at one time.
|
|
231
|
-
- If the Finder instance has `requireGroup` enabled and no groupBy rule is active, the first rule in the stack will be considered active.
|
|
232
|
-
- If you want a group of premium items to always appear first in the results, you can set `sticky: {header: 'premium_id' }`. If an array is provided, the order will be preserved ( i.e: `sticky: { header: ['premium_id', 'subpremium_id']})`.
|
|
233
|
-
|
|
234
|
-
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
235
|
-
|
|
236
|
-
## Meta Mixin
|
|
237
|
-
|
|
238
|
-
The Meta mixin allows you to provide additional context that doesn't belong to either items or rules. For example, a filter might be affected by a user's purchase history or browser preferences.
|
|
239
|
-
|
|
240
|
-
```ts
|
|
241
|
-
function MetaComponent() {
|
|
242
|
-
const items = [...];
|
|
243
|
-
|
|
244
|
-
const rules = [
|
|
245
|
-
filterRule({
|
|
246
|
-
id: 'user_specific_filter'
|
|
247
|
-
filterFn: (items, value, meta) => {
|
|
248
|
-
if (meta.get('user') === CoolUser) {
|
|
249
|
-
return true;
|
|
250
|
-
}
|
|
251
|
-
return false;
|
|
252
|
-
})
|
|
253
|
-
];
|
|
254
|
-
|
|
255
|
-
const initialMeta = {'User': CoolUser}
|
|
256
|
-
|
|
257
|
-
return (
|
|
258
|
-
<Finder items={items} rules={rules} initialMeta={initialMeta}>
|
|
259
|
-
// contents
|
|
260
|
-
</Finder>
|
|
261
|
-
)
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
266
|
-
|
|
267
|
-
## Layout Mixin
|
|
268
|
-
|
|
269
|
-
The Layout mixin can be used to inform rendering logic inside your React components.
|
|
270
|
-
|
|
271
|
-
```ts
|
|
272
|
-
function MetaComponent() {
|
|
273
|
-
const items = [...];
|
|
274
|
-
const rules = [...];
|
|
275
|
-
const layoutVariants: LayoutVariant[] = [
|
|
276
|
-
{
|
|
277
|
-
id: 'gallery'
|
|
278
|
-
}
|
|
279
|
-
{
|
|
280
|
-
id: 'table'
|
|
281
|
-
}
|
|
282
|
-
]
|
|
283
|
-
|
|
284
|
-
return (
|
|
285
|
-
<Finder items={items} rules={rules} layoutVariants={layoutVariants} initialLayout='table'>
|
|
286
|
-
<FinderItems>
|
|
287
|
-
{
|
|
288
|
-
({items, layout}) => {
|
|
289
|
-
return layout.is('table') ? <TableComponent items={items} /> : <GalleryComponent items={items} />
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
</FinderItems>
|
|
293
|
-
</Finder>
|
|
294
|
-
)
|
|
295
|
-
}
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
299
|
-
|
|
300
|
-
## Pagination
|
|
301
|
-
|
|
302
|
-
_Please note that pagination is not the final option considered in the functionality of the filtering / sorting process, therefore groupBy options could return unexpected results._
|
|
303
|
-
|
|
304
|
-
If the `numItemsPerPage` property is set, Finder will paginate items and groups.
|
|
305
|
-
|
|
306
|
-
```ts
|
|
307
|
-
function DeclarativePaginationComponent() {
|
|
308
|
-
const items = [...]
|
|
309
|
-
const rules = [...]
|
|
310
|
-
|
|
311
|
-
const page = 1;
|
|
312
|
-
const numItemsPerPage = 10;
|
|
313
|
-
|
|
314
|
-
return (
|
|
315
|
-
<Finder items={items} rules={rules} page={page} numItemsPerPage={numItemsPerPage}>
|
|
316
|
-
...contents
|
|
317
|
-
</Finder>
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// the values can also be changed after Finder is initialized.
|
|
322
|
-
function ImperativePaginationControl() {
|
|
323
|
-
const finder = useFinderContext();
|
|
324
|
-
|
|
325
|
-
return (
|
|
326
|
-
<>
|
|
327
|
-
// pager bar
|
|
328
|
-
{finder.pagination.isPaginated && range(1, finder.pagination.lastPage).map((index) => {
|
|
329
|
-
<button type="button" oncClick={() => finder.pagination.setPage(index)}>
|
|
330
|
-
{index}
|
|
331
|
-
</button>
|
|
332
|
-
})}
|
|
333
|
-
|
|
334
|
-
Results per page:
|
|
335
|
-
<select onChange((e) => finder.pagination.setNumItemsPerPage(Number(e.target.value))) >
|
|
336
|
-
<option value="10">10</option>
|
|
337
|
-
<option value="100">100</option>
|
|
338
|
-
<option value="1000">1000</option>
|
|
339
|
-
</select>
|
|
340
|
-
</>
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
</details>
|
|
346
|
-
|
|
347
|
-
## Life Cycle Events
|
|
348
|
-
|
|
349
|
-
Finder exposes an event emitter.
|
|
350
|
-
|
|
351
|
-
Declarative events:
|
|
352
|
-
|
|
353
|
-
```ts
|
|
354
|
-
<Finder items={[...items]} rules={[...]} onChange={(e) => myChangeListener(e)} />
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
Imperative events:
|
|
358
|
-
|
|
359
|
-
```ts
|
|
360
|
-
const finder = useFinderContext();
|
|
361
|
-
finder.events.on(eventName, listener);
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### onInit
|
|
365
|
-
|
|
366
|
-
Triggered a single time when Finder is first initialized.
|
|
367
|
-
|
|
368
|
-
```ts
|
|
369
|
-
// FinderInitEvent
|
|
370
|
-
{
|
|
371
|
-
source: "core";
|
|
372
|
-
event: "init";
|
|
373
|
-
snapshot: FinderSnapshot,
|
|
374
|
-
timestamp: number;
|
|
375
|
-
}
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
### onFirstUserInteraction
|
|
379
|
-
|
|
380
|
-
Triggered once when the user interacts with any rule.
|
|
381
|
-
|
|
382
|
-
```ts
|
|
383
|
-
// FinderFirstUserInteractionEvent
|
|
384
|
-
{
|
|
385
|
-
source: "core";
|
|
386
|
-
event: "firstUserInteraction";
|
|
387
|
-
snapshot: FinderSnapshot,
|
|
388
|
-
timestamp: number;
|
|
389
|
-
}
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### onReady
|
|
393
|
-
|
|
394
|
-
Triggered once after an items array is set and `isLoading` is false.
|
|
395
|
-
|
|
396
|
-
```ts
|
|
397
|
-
// FinderReadyEvent
|
|
398
|
-
{
|
|
399
|
-
source: "core";
|
|
400
|
-
event: "ready";
|
|
401
|
-
snapshot: FinderSnapshot,
|
|
402
|
-
timestamp: number;
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### onChange
|
|
407
|
-
|
|
408
|
-
Triggered whenever a rule's state changes.
|
|
409
|
-
|
|
410
|
-
```ts
|
|
411
|
-
// FinderChangeEvent
|
|
412
|
-
{
|
|
413
|
-
source: "core" | "filters" | "groupBy" | "meta" | "pagination" | "plugin" | "search" | "selectedItems" | "sortBy" | "layout";
|
|
414
|
-
event: "change";
|
|
415
|
-
current: any;
|
|
416
|
-
initial: any;
|
|
417
|
-
snapshot: FinderSnapshot,
|
|
418
|
-
timestamp: number;
|
|
419
|
-
}
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
Pro-tips:
|
|
423
|
-
|
|
424
|
-
- For change events, you can subscribe to the broad 'change' event, or a narrower event like `change.search` or `change.search.setSearchTerm`.
|
|
425
|
-
|
|
426
|
-
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
427
|
-
|
|
428
|
-
## Components
|
|
429
|
-
|
|
430
|
-
### Core Components
|
|
431
|
-
|
|
432
|
-
<details open>
|
|
433
|
-
<summary><i><Finder /></i> The root query container</summary>
|
|
434
|
-
Props:
|
|
435
|
-
|
|
436
|
-
```ts
|
|
437
|
-
items: FItem[];
|
|
438
|
-
rules?: FinderRule<FItem>[];
|
|
439
|
-
initialSearchTerm?: string;
|
|
440
|
-
initialSortBy?: string;
|
|
441
|
-
initialSortDirection?: SortDirection;
|
|
442
|
-
initialGroupBy?: string;
|
|
443
|
-
initialFilters?: Record<string, any>;
|
|
444
|
-
initialMeta?: Record<string, any>;
|
|
445
|
-
initialSelectedItems?: FItem[];
|
|
446
|
-
maxSelectedItems?: number;
|
|
447
|
-
isLoading?: boolean;
|
|
448
|
-
disabled?: boolean;
|
|
449
|
-
page?: number;
|
|
450
|
-
numItemsPerPage?: number;
|
|
451
|
-
requireGroup?: boolean;
|
|
452
|
-
layoutVariants?: LayoutVariant[];
|
|
453
|
-
initialLayout?: string;
|
|
454
|
-
plugins?: FinderPlugin[];
|
|
455
|
-
onInit?: (event: FinderInitEvent) => void;
|
|
456
|
-
onReady?: (event: FinderReadyEvent) => void
|
|
457
|
-
onFirstUserInteraction?: (event: FinderFirstUserInteractionEvent) => void;
|
|
458
|
-
onChange?: (diff: FinderDiff, ref: FinderInstance<FItem>) => void
|
|
459
|
-
state: 'loading' | 'empty' | 'groups' | 'items' | 'noMatches';
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
</details>
|
|
463
|
-
|
|
464
|
-
<details>
|
|
465
|
-
<summary><i><FinderContent /></i> A convenience component to handle Finder states.</summary>
|
|
466
|
-
|
|
467
|
-
It accepts an array of renderProps and will determine the most appropriate state to display. Only a single state can be active at a time.
|
|
468
|
-
```ts
|
|
469
|
-
<FinderContent>
|
|
470
|
-
{{
|
|
471
|
-
// Displayed while Finder's isLoading property is true.
|
|
472
|
-
loading: ReactNode,
|
|
473
|
-
|
|
474
|
-
// Finder received an empty items array.
|
|
475
|
-
empty: ReactNode,
|
|
476
|
-
|
|
477
|
-
// No items were found that matched the current rules.
|
|
478
|
-
noMatches: ReactNode
|
|
479
|
-
|
|
480
|
-
// Items were found that matched the rules.
|
|
481
|
-
items: ({items: FItem[], meta: MetaMixin, pagination: FinderPagination, selectedItems: FinderSelectedItems, layout: LayoutMixin}) => ReactNode,
|
|
482
|
-
|
|
483
|
-
// An active GroupBy rule grouped items together.
|
|
484
|
-
groups: ({groups: FinderResultGroup<FItem>, meta: MetaMixin, pagination: FinderPagination, selectedItems: FinderSelectedItems, layout: LayoutMixin}) => ReactNode,
|
|
485
|
-
|
|
486
|
-
}}
|
|
487
|
-
|
|
488
|
-
</FinderContent>
|
|
489
|
-
```
|
|
490
|
-
Pro-tips:
|
|
491
|
-
|
|
492
|
-
- If pagination is enabled, the items and groups components will receive only the current page's slice.
|
|
493
|
-
</details>
|
|
494
|
-
|
|
495
|
-
<details>
|
|
496
|
-
<summary><i><FinderLoading /></i></summary>
|
|
497
|
-
|
|
498
|
-
Only visible when `isLoading` is true.
|
|
499
|
-
|
|
500
|
-
```ts
|
|
501
|
-
<FinderLoading>
|
|
502
|
-
Requesting data: [██████__________]
|
|
503
|
-
</FinderLoading>
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
</details>
|
|
507
|
-
|
|
508
|
-
<details>
|
|
509
|
-
<summary><i><FinderEmpty /></i></summary>
|
|
510
|
-
|
|
511
|
-
Only visible when `isLoading` is false, and the `items` array is empty.
|
|
512
|
-
|
|
513
|
-
```ts
|
|
514
|
-
<FinderEmpty>
|
|
515
|
-
Nothing here!
|
|
516
|
-
</FinderEmpty>
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
</details>
|
|
520
|
-
|
|
521
|
-
<details>
|
|
522
|
-
<summary><i><FinderNoMatches /></i></summary>
|
|
523
|
-
|
|
524
|
-
Only visible when no items were found that matched the current rules.
|
|
525
|
-
|
|
526
|
-
```ts
|
|
527
|
-
<FinderNoMatches>
|
|
528
|
-
No items were found for that search.
|
|
529
|
-
</FinderNoMatches>
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
</details>
|
|
533
|
-
|
|
534
|
-
<details>
|
|
535
|
-
<summary><i><FinderItems /></i></summary>
|
|
536
|
-
|
|
537
|
-
Only visible when items were found that matched the rules.
|
|
538
|
-
|
|
539
|
-
```ts
|
|
540
|
-
<FinderItems>
|
|
541
|
-
{({items: FItem[], meta: MetaMixin, pagination: FinderPagination, selectedItems: FinderSelectedItems, layout: LayoutMixin }) => ReactNode}
|
|
542
|
-
</FinderItems>
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
</details>
|
|
546
|
-
|
|
547
|
-
<details>
|
|
548
|
-
<summary><i><FinderGroups /></i></summary>
|
|
549
|
-
|
|
550
|
-
Only visible when an active GroupBy rule grouped items together.
|
|
551
|
-
|
|
552
|
-
```ts
|
|
553
|
-
<FinderItems>
|
|
554
|
-
{({groups: FinderResultGroup<FItem>[], meta: MetaMixin, pagination: FinderPagination, selectedItems: FinderSelectedItems, layout: LayoutMixin}) => ReactNode}
|
|
555
|
-
</FinderItems>
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
</details>
|
|
559
|
-
|
|
560
|
-
### Matches
|
|
561
|
-
|
|
562
|
-
Finder match results are snapshotted, and are recalculated when an internal onChange event is triggered.
|
|
563
|
-
|
|
564
|
-
As a reminder, Finder processes rules in the following order:
|
|
565
|
-
|
|
566
|
-
1. Search
|
|
567
|
-
2. Filters
|
|
568
|
-
3. SortBy
|
|
569
|
-
4. Paginate
|
|
570
|
-
5. GroupBy
|
|
571
|
-
|
|
572
|
-
This means that a group might be split across multiple pages as pagination is not the last option considered.
|
|
573
|
-
|
|
574
|
-
<details>
|
|
575
|
-
<summary><i><MatchesComponent /></i></summary>
|
|
576
|
-
|
|
577
|
-
```ts
|
|
578
|
-
function MatchesComponent() {
|
|
579
|
-
const finder = useFinderContext();
|
|
580
|
-
|
|
581
|
-
return (
|
|
582
|
-
<>
|
|
583
|
-
|
|
584
|
-
// if no groupBy rule is set, the `finder.matches.items` property will be an array.
|
|
585
|
-
{finder.matches.items?.map((item) => <Photo item={item} />)}
|
|
586
|
-
|
|
587
|
-
// if a groupBy rule IS set, the `finder.matches.groups` property will be an array of result groups.
|
|
588
|
-
{finder.matches.groups?.map((group) => (
|
|
589
|
-
<>
|
|
590
|
-
{group.id}
|
|
591
|
-
{group.items.map((item) => {
|
|
592
|
-
return <Photo item={item} />
|
|
593
|
-
})}
|
|
594
|
-
</>)
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// total items that Finder iterated through.
|
|
598
|
-
{finder.matches.numTotalItems}
|
|
599
|
-
</>
|
|
600
|
-
);
|
|
601
|
-
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
````
|
|
605
|
-
</details>
|
|
606
|
-
|
|
607
|
-
<details>
|
|
608
|
-
<summary><i><SearchComponent /></i></summary>
|
|
609
|
-
|
|
610
|
-
```ts
|
|
611
|
-
function SearchComponent() {
|
|
612
|
-
const finder = useFinderContext();
|
|
613
|
-
const handleInput = (e) => finder.search.setSearchTerm(e.currentTarget.value);
|
|
614
|
-
return (
|
|
615
|
-
<>
|
|
616
|
-
{finder.search.hasSearchTerm && `Searching for: "${finder.search.searchTerm}"`}
|
|
617
|
-
<input type="text" onInput={handleInput} />
|
|
618
|
-
<button type="button" onClick={() => finder.search.reset()}>
|
|
619
|
-
Clear
|
|
620
|
-
</button>
|
|
621
|
-
</>
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
</details>
|
|
627
|
-
|
|
628
|
-
<details>
|
|
629
|
-
<summary><i><FilterComponent /></i></summary>
|
|
630
|
-
|
|
631
|
-
```ts
|
|
632
|
-
function FilterComponent({rule} : {rule: HydratedFilterRule}}) {
|
|
633
|
-
const finder = useFinderContext();
|
|
634
|
-
|
|
635
|
-
const luckyOption = rule.options.find(({value}) => value === 'lucky');
|
|
636
|
-
|
|
637
|
-
// You can test the results of a filter with a specific value without applying it.
|
|
638
|
-
const numMatches = finder.filters.test(rule, 'purple').length;
|
|
639
|
-
|
|
640
|
-
// You can also test the results of *all* options for a filter with defined options. This returns a Map keyed by the Option object.
|
|
641
|
-
const numOptionMatches = finder.filters.testOptions(rule);
|
|
642
|
-
|
|
643
|
-
return (
|
|
644
|
-
<>
|
|
645
|
-
|
|
646
|
-
// retrieve the filter value
|
|
647
|
-
{finder.filters.has(rule) && `The current value is ${finder.filters.get(rule)}`}
|
|
648
|
-
|
|
649
|
-
// Check if a specific filter option is active
|
|
650
|
-
{finder.filters.has(rule, luckyOption) && 'Super lucky!'}
|
|
651
|
-
|
|
652
|
-
// filters with isBoolean can be toggled
|
|
653
|
-
<input type="checkbox" onChange={() => finder.filters.toggle(rule)}>
|
|
654
|
-
|
|
655
|
-
<select>
|
|
656
|
-
// a blank string is considered to be setting a filter value to undefined.
|
|
657
|
-
{rule.required === false && <option value=''>None</option>}
|
|
658
|
-
|
|
659
|
-
// Rules
|
|
660
|
-
{rule.options.map((option) => {
|
|
661
|
-
return <option value={option.value}>{option.label}</option>
|
|
662
|
-
})}
|
|
663
|
-
</select>
|
|
664
|
-
|
|
665
|
-
// rules that accept multiple values can toggle individual options
|
|
666
|
-
{rule.options.map((option) => {
|
|
667
|
-
return (
|
|
668
|
-
<label>
|
|
669
|
-
<input type="checkbox" value={option.value} onChange(() => finder.filters.toggleOption(rule, option)>
|
|
670
|
-
{option.label}
|
|
671
|
-
</label>
|
|
672
|
-
);
|
|
673
|
-
})}
|
|
674
|
-
|
|
675
|
-
// filters will accept any value.
|
|
676
|
-
<input type="text" onInput={(e) => finder.filters.set(rule, e.currentTarget.value))} />
|
|
677
|
-
|
|
678
|
-
// reset a specific filter
|
|
679
|
-
<button type="button" onClick={() => finder.filters.delete(rule)}>
|
|
680
|
-
Clear
|
|
681
|
-
</button>
|
|
682
|
-
|
|
683
|
-
<button type="button" onClick={() => finder.filters.reset()}>
|
|
684
|
-
Reset all filters
|
|
685
|
-
</button>
|
|
686
|
-
</>
|
|
687
|
-
);
|
|
688
|
-
}
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
</details>
|
|
692
|
-
|
|
693
|
-
<details>
|
|
694
|
-
<summary><i><SortByComponent /></i></summary>
|
|
695
|
-
|
|
696
|
-
```ts
|
|
697
|
-
function SortByComponent() {
|
|
698
|
-
const finder = useFinderContext();
|
|
699
|
-
|
|
700
|
-
return (
|
|
701
|
-
<>
|
|
702
|
-
<select value={finder.sortBy.activeRuleId} onChange={(e) => finder.sortBy.set(e.currentTarget.value)}>
|
|
703
|
-
{finder.sortBy.rules.map((rule) => {
|
|
704
|
-
return <option value={rule.id}>{rule.label}</option>
|
|
705
|
-
})}
|
|
706
|
-
</select>
|
|
707
|
-
|
|
708
|
-
// Toggle sort direction between asc / desc
|
|
709
|
-
<button type="button" onClick={() => finder.sortBy.toggleSortDirection()}>
|
|
710
|
-
Toggle
|
|
711
|
-
</button>
|
|
712
|
-
|
|
713
|
-
// Cycle sort direction through default / asc / desc
|
|
714
|
-
<button type="button" onClick={() => finder.sortBy.cycleSortDirection()}>
|
|
715
|
-
Cycle
|
|
716
|
-
</button>
|
|
717
|
-
|
|
718
|
-
// reset
|
|
719
|
-
<button type="button" onClick={() => finder.filters.delete(rule)}>
|
|
720
|
-
Clear
|
|
721
|
-
</button>
|
|
722
|
-
|
|
723
|
-
</>
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
````
|
|
728
|
-
|
|
729
|
-
</details>
|
|
730
|
-
|
|
731
|
-
<details>
|
|
732
|
-
<summary><i><GroupByComponent /></i></summary>
|
|
733
|
-
|
|
734
|
-
```ts
|
|
735
|
-
function GroupByComponent() {
|
|
736
|
-
const finder = useFinderContext();
|
|
737
|
-
|
|
738
|
-
return (
|
|
739
|
-
<>
|
|
740
|
-
<select value={finder.groupBy.activeRuleId} onChange={(e) => finder.groupBy.set(e.currentTarget.value)}>
|
|
741
|
-
|
|
742
|
-
// a blank string is considered to be setting the groupBy value to undefined.
|
|
743
|
-
{finder.groupBy.requireGroup === false && <option value=''>None</option>}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
{finder.groupBy.rules.map((rule) => {
|
|
747
|
-
return <option value={rule.id}>{rule.label}</option>
|
|
748
|
-
})}
|
|
749
|
-
</select>
|
|
750
|
-
|
|
751
|
-
// Set group sort direction
|
|
752
|
-
<button type="button" onClick={() => finder.sortBy.setGroupIdSortDirection('asc')}>
|
|
753
|
-
Asc
|
|
754
|
-
</button>
|
|
755
|
-
<button type="button" onClick={() => finder.sortBy.setGroupIdSortDirection('desc')}>
|
|
756
|
-
Desc
|
|
757
|
-
</button>
|
|
758
|
-
|
|
759
|
-
// reset
|
|
760
|
-
<button type="button" onClick={() => finder.groupBy.reset()}>
|
|
761
|
-
Clear
|
|
762
|
-
</button>
|
|
763
|
-
|
|
764
|
-
</>
|
|
765
|
-
);
|
|
766
|
-
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
</details>
|
|
772
|
-
|
|
773
|
-
<details>
|
|
774
|
-
<summary><i><MySelectedItemsComponent /></i></summary>
|
|
775
|
-
|
|
776
|
-
```ts
|
|
777
|
-
function MySelectedItemsComponent() {
|
|
778
|
-
|
|
779
|
-
const finder = useFinderContext();
|
|
780
|
-
|
|
781
|
-
return <>
|
|
782
|
-
Selected items:
|
|
783
|
-
{finder.selectedItems.items.map((item) => {
|
|
784
|
-
return <SelectedItem item={item} />
|
|
785
|
-
})}
|
|
786
|
-
|
|
787
|
-
{finder.matches.items.map((item) => {
|
|
788
|
-
return (
|
|
789
|
-
<>
|
|
790
|
-
// select an item
|
|
791
|
-
<button type="button" onClick={() => finder.selectedItems.select(item)}>Add</button>
|
|
792
|
-
|
|
793
|
-
// remove a selected item
|
|
794
|
-
<button type="button" onClick={() => finder.selectedItems.delete(item)}>Remove</button>
|
|
795
|
-
|
|
796
|
-
// toggle a selected item
|
|
797
|
-
<label>
|
|
798
|
-
<input type="checkbox" checked={finder.selectedItems.isSelected(item)} onChange(() => finder.selectedItems.toggle(item)) />
|
|
799
|
-
</label>
|
|
800
|
-
</>
|
|
801
|
-
);
|
|
802
|
-
})}
|
|
803
|
-
|
|
804
|
-
// remove all selected items
|
|
805
|
-
<button type="button" onClick={() => finder.selectedItems.reset()}>Clear All</button>
|
|
806
|
-
</>
|
|
807
|
-
}
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
</details>
|
|
811
|
-
|
|
812
|
-
## Hooks
|
|
813
|
-
|
|
814
|
-
### useFinder(items, options)
|
|
815
|
-
|
|
816
|
-
Create a new Finder instance.
|
|
817
|
-
|
|
818
|
-
### useFinderContext()
|
|
819
|
-
|
|
820
|
-
Consume a parent Finder context.
|
|
821
|
-
|
|
822
|
-
### useFinderRef()
|
|
823
|
-
|
|
824
|
-
A convenience hook for controlling Finder from the same scope it's created in.
|
|
825
|
-
|
|
826
|
-
```ts
|
|
827
|
-
function MyComponent() {
|
|
828
|
-
|
|
829
|
-
const ref = useFinderRef();
|
|
830
|
-
ref.current.events.on(eventName, eventListener);
|
|
831
|
-
return <Finder controllerRef={ref}>...</Finder>
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
```
|
|
835
|
-
|
|
836
|
-
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
837
|
-
|
|
838
|
-
## Imperative Interfaces
|
|
839
|
-
|
|
840
|
-
All interfaces can be accessed from the `useFinderContext()` hook.
|
|
841
|
-
|
|
842
|
-
### Matches
|
|
843
|
-
|
|
844
|
-
```ts
|
|
845
|
-
finder.matches = {
|
|
846
|
-
items?: FItem[];
|
|
847
|
-
groups?: FinderResultGroup<FItem>[];
|
|
848
|
-
numMatchedItems: number;
|
|
849
|
-
numTotalItems: number;
|
|
850
|
-
hasGroupByRule: boolean;
|
|
851
|
-
}
|
|
852
|
-
```
|
|
853
|
-
|
|
854
|
-
### Search
|
|
855
|
-
|
|
856
|
-
```ts
|
|
857
|
-
finder.search = {
|
|
858
|
-
searchTerm: string;
|
|
859
|
-
activeRule?: SearchRule<FItem>;
|
|
860
|
-
hasSearchTerm: boolean;
|
|
861
|
-
setSearchTerm: (value: string) => void;
|
|
862
|
-
reset: () => void;
|
|
863
|
-
};
|
|
864
|
-
```
|
|
865
|
-
|
|
866
|
-
### Filters
|
|
867
|
-
|
|
868
|
-
```ts
|
|
869
|
-
finder.filters = {
|
|
870
|
-
filters: Record<string, any>; /* Formatted and type-cast values including default values, etc. */
|
|
871
|
-
raw: Record<string, any>; /* Raw mixin state without default values or formatting */
|
|
872
|
-
activeRules: HydratedFilterRule<FItem>[];
|
|
873
|
-
rules: HydratedFilterRule<FItem>[];
|
|
874
|
-
isActive: (identifier: string | FilterRule | HydratedFilterRule, value?: any) => boolean;
|
|
875
|
-
set: (identifier: string | FilterRule | HydratedFilterRule, value?: any) => void;
|
|
876
|
-
get: (identifier: string | FilterRule | HydratedFilterRule) => any;
|
|
877
|
-
has: (identifier: string | FilterRule | HydratedFilterRule) => boolean;
|
|
878
|
-
delete: (identifier: string | FilterRule | HydratedFilterRule) => void;
|
|
879
|
-
toggle: (identifier: string | FilterRule | HydratedFilterRule) => void;
|
|
880
|
-
toggleOption: (identifier: string | FilterRule | HydratedFilterRule, optionValue: FinderOption | any) => void;
|
|
881
|
-
getRule: (identifier:string) => HydratedFilterRule | undefined;
|
|
882
|
-
test: (rule: FilterTestOptions) => FItem[];
|
|
883
|
-
testRule: (identifier: string | FilterRule | HydratedFilterRule, filterValue: any, meta?: FinderMeta) => FItem[];
|
|
884
|
-
testOptions: (identifier: string | FilterRule | HydratedFilterRule, meta?: FinderMeta) => Map<FinderOption | boolean, FItem[] | undefined>;
|
|
885
|
-
};
|
|
886
|
-
```
|
|
887
|
-
|
|
888
|
-
### SortBy
|
|
889
|
-
|
|
890
|
-
```ts
|
|
891
|
-
finder.sortBy = {
|
|
892
|
-
activeRule?: HydratedSortByRule<FItem>;
|
|
893
|
-
activeRuleId?: string;
|
|
894
|
-
rules: HydratedSortByRule<FItem>[];
|
|
895
|
-
sortDirection?: string;
|
|
896
|
-
set: (identifier?: string | SortByRule | HydratedSortByRule, sortDirection?: string) => void;
|
|
897
|
-
setSortDirection: (sortDirection?: string) => void;
|
|
898
|
-
cycleSortDirection: () => void;
|
|
899
|
-
toggleSortDirection: () => void;
|
|
900
|
-
};
|
|
901
|
-
```
|
|
902
|
-
|
|
903
|
-
### GroupBy
|
|
904
|
-
|
|
905
|
-
```ts
|
|
906
|
-
finder.groupBy = {
|
|
907
|
-
activeRule?: GroupByRule<FItem>;
|
|
908
|
-
activeRuleId?: string;
|
|
909
|
-
rules: GroupByRule<FItem>[];
|
|
910
|
-
requireGroup: boolean;
|
|
911
|
-
groupIdSortDirection?: string;
|
|
912
|
-
set: (identifier?: GroupByRule | string, value?: string) => void;
|
|
913
|
-
toggle: (identifier: GroupByRule | string) => void;
|
|
914
|
-
setGroupIdSortDirection: (direction?: string) => void;
|
|
915
|
-
reset: () => void;
|
|
916
|
-
};
|
|
917
|
-
```
|
|
918
|
-
|
|
919
|
-
### Meta
|
|
920
|
-
|
|
921
|
-
```ts
|
|
922
|
-
finder.meta = {
|
|
923
|
-
value?: FinderMeta;
|
|
924
|
-
set: (metaIdentifier: any, metaValue: any) => void;
|
|
925
|
-
get: (metaIdentifier: any) => any;
|
|
926
|
-
has: (metaIdentifier: any) => boolean;
|
|
927
|
-
delete: (metaIdentifier: any) => void;
|
|
928
|
-
reset: () => void;
|
|
929
|
-
};
|
|
930
|
-
```
|
|
931
|
-
|
|
932
|
-
### Layout
|
|
933
|
-
|
|
934
|
-
```ts
|
|
935
|
-
finder.layout = {
|
|
936
|
-
variants?: LayoutVariant[];
|
|
937
|
-
activeLayout?: LayoutVariant;
|
|
938
|
-
raw?: LayoutVariant;
|
|
939
|
-
is: (layoutIdentifier: string | LayoutVariant) => boolean;
|
|
940
|
-
set: (layoutIdentifier: string | LayoutVariant) => void;
|
|
941
|
-
reset: () => void;
|
|
942
|
-
};
|
|
943
|
-
```
|
|
944
|
-
|
|
945
|
-
### Pagination
|
|
946
|
-
|
|
947
|
-
```ts
|
|
948
|
-
finder.pagination = {
|
|
949
|
-
page: number;
|
|
950
|
-
offset: number;
|
|
951
|
-
numItemsPerPage?: number;
|
|
952
|
-
numTotalItems: number;
|
|
953
|
-
lastPage?: number;
|
|
954
|
-
isPaginated: boolean;
|
|
955
|
-
setPage: (page: number) => void;
|
|
956
|
-
setNumItemsPerPage: (numItemsPerPage: number) => void;
|
|
957
|
-
};
|
|
958
|
-
```
|
|
959
|
-
|
|
960
|
-
### Selected Items
|
|
961
|
-
|
|
962
|
-
```ts
|
|
963
|
-
finder.selectedItems = {
|
|
964
|
-
items?: FItem[];
|
|
965
|
-
select: (item: FItem) => void;
|
|
966
|
-
delete: (item: FItem) => void;
|
|
967
|
-
toggle: (item: FItem) => void;
|
|
968
|
-
selectOnly: (item: FItem) => void;
|
|
969
|
-
toggleOnly: (item: FItem) => void;
|
|
970
|
-
isSelected: (item: FItem) => boolean;
|
|
971
|
-
reset: () => void;
|
|
972
|
-
};
|
|
973
|
-
```
|
|
974
|
-
|
|
975
|
-
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
976
|
-
|
|
977
|
-
## Type Guards
|
|
978
|
-
|
|
979
|
-
Ensures that rules have the correct shape.
|
|
980
|
-
|
|
981
|
-
### searchRule
|
|
982
|
-
|
|
983
|
-
```ts
|
|
984
|
-
const rule = searchRule<FItem>({
|
|
985
|
-
searchFn: (item, searchTerm) => boolean,
|
|
986
|
-
});
|
|
987
|
-
```
|
|
988
|
-
|
|
989
|
-
### filterRule
|
|
990
|
-
|
|
991
|
-
```ts
|
|
992
|
-
const rule = filterRule<FItem, FValue>({
|
|
993
|
-
id: "unique_identifier",
|
|
994
|
-
filterFn: (item, value, meta) => boolean,
|
|
995
|
-
});
|
|
996
|
-
```
|
|
997
|
-
|
|
998
|
-
### sortByRule()
|
|
999
|
-
|
|
1000
|
-
```ts
|
|
1001
|
-
const rule = sortByRule<FItem>({
|
|
1002
|
-
id: "unique_identifier",
|
|
1003
|
-
sortFn: (item) => string | number,
|
|
1004
|
-
});
|
|
1005
|
-
```
|
|
1006
|
-
|
|
1007
|
-
### groupByRule
|
|
1008
|
-
|
|
1009
|
-
```ts
|
|
1010
|
-
const rule = groupByRule<FItem>({
|
|
1011
|
-
id: "unique_identifier",
|
|
1012
|
-
groupFn: (item) => string | number,
|
|
1013
|
-
});
|
|
1014
|
-
```
|
|
1015
|
-
|
|
1016
|
-
### finderRuleset
|
|
1017
|
-
|
|
1018
|
-
Ensures that an array of rules all have the correct shape.
|
|
1019
|
-
|
|
1020
|
-
```ts
|
|
1021
|
-
const rule = finderRules<FItem>([
|
|
1022
|
-
{
|
|
1023
|
-
id: "unique_identifier",
|
|
1024
|
-
sortFn: (item) => string | number,
|
|
1025
|
-
},
|
|
1026
|
-
{
|
|
1027
|
-
id: "unique_identifier",
|
|
1028
|
-
groupFn: (item) => string | number,
|
|
1029
|
-
},
|
|
1030
|
-
]);
|
|
1031
|
-
```
|
|
1032
|
-
|
|
1033
|
-
## String Comparison Utils
|
|
1034
|
-
|
|
1035
|
-
### finderStringCompare
|
|
1036
|
-
|
|
1037
|
-
```ts
|
|
1038
|
-
finderStringCompare(haystack: string, needle: string, aliases?: string[])
|
|
1039
|
-
```
|
|
1040
|
-
|
|
1041
|
-
Performs a case-insensitive search that removes whitespace and line breaks. If an `aliases` array is provided, the needle will be compared against all alias entries as well.
|
|
1042
|
-
|
|
1043
|
-
```ts
|
|
1044
|
-
// with mangled search term with line breaks and too much white space
|
|
1045
|
-
finderStringCompare("guava", "g u a \n v \r"); // true
|
|
1046
|
-
|
|
1047
|
-
// with aliases
|
|
1048
|
-
finderStringCompare("guava", "gu", ["guajava", "guayaba"]); // true
|
|
1049
|
-
```
|
|
1050
|
-
|
|
1051
|
-
### finderCharacterCompare
|
|
1052
|
-
|
|
1053
|
-
```ts
|
|
1054
|
-
finderCharacterCompare(haystack: string, needle: string, aliases?: string[])
|
|
1055
|
-
```
|
|
1056
|
-
|
|
1057
|
-
Performs a case-insensitive comparison of needle characters against the haystack. The characters can appear in any order.
|
|
1058
|
-
|
|
1059
|
-
```ts
|
|
1060
|
-
finderCharacterCompare("e d c b a", "AB C\nD\r E"); // true
|
|
1061
|
-
```
|
|
1062
|
-
|
|
1063
|
-
### finderCharacterCompare
|
|
1064
|
-
|
|
1065
|
-
```ts
|
|
1066
|
-
finderCharacterCompare(haystack: string, needle: string, aliases?: string[])
|
|
1067
|
-
```
|
|
1068
|
-
|
|
1069
|
-
Performs a case-insensitive comparison of needle characters against the haystack. The characters can appear in any order.
|
|
1070
|
-
|
|
1071
|
-
```ts
|
|
1072
|
-
finderSequentialCharacterCompare("aabciop[cde", "AB C\nD\r E"); // true
|
|
1073
|
-
|
|
1074
|
-
finderSequentialCharacterCompare("e d c b a", "AB C\nD\r E"); // false
|
|
1075
|
-
```
|
|
1076
|
-
|
|
1077
|
-
Performs a case-insensitive comparison of needle characters against the haystack. The characters must appear in the same order as the needle.
|
|
1078
|
-
|
|
1079
|
-
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
1080
|
-
|
|
1081
|
-
## Plugins
|
|
1082
|
-
|
|
1083
|
-
```ts
|
|
1084
|
-
const plugin = FinderPluginInterface<FItem> {
|
|
1085
|
-
id: string;
|
|
1086
|
-
register: (finder: FinderCore<FItem>, touch: FinderTouchCallback) => void;
|
|
1087
|
-
}
|
|
1088
|
-
```
|
|
1089
|
-
|
|
1090
|
-
For power users, Finder accepts plugins that can attach event listeners and trigger internal actions.
|
|
1091
|
-
|
|
1092
|
-
```ts
|
|
1093
|
-
// example
|
|
1094
|
-
const plugin = FinderPluginInterface<FItem> {
|
|
1095
|
-
id: 'my_cool_plugin';
|
|
1096
|
-
register: (finder: FinderCore<FItem>, touch: FinderTouchCallback) => {
|
|
1097
|
-
finder.events.on('change.filters.set', (event) => {
|
|
1098
|
-
// modify something
|
|
1099
|
-
...
|
|
1100
|
-
|
|
1101
|
-
// trigger a state update in Finder
|
|
1102
|
-
touch();
|
|
1103
|
-
})
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
return <Finder items={[...]} plugins={[plugin]}>...</Finder>
|
|
1108
|
-
```
|
|
1109
|
-
|
|
1110
|
-
A convenience `FinderPlugin` class is exported for users who prefer an object-oriented approach.
|
|
1111
|
-
|
|
1112
|
-
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
13
|
+
Find complete documentation at https://hitgrab.github.io/finder/
|