@nu-art/search 0.400.7

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.
Files changed (58) hide show
  1. package/_core/SearchAddOn.d.ts +27 -0
  2. package/_core/SearchAddOn.js +1 -0
  3. package/_core/SearchContext.d.ts +58 -0
  4. package/_core/SearchContext.js +147 -0
  5. package/_core/SearchItem.d.ts +26 -0
  6. package/_core/SearchItem.js +1 -0
  7. package/_core/SearchSorter.d.ts +5 -0
  8. package/_core/SearchSorter.js +1 -0
  9. package/_core/index.d.ts +3 -0
  10. package/_core/index.js +3 -0
  11. package/_ui/add-ons/entity-filter/Component_AddOn_EntityFilter.d.ts +22 -0
  12. package/_ui/add-ons/entity-filter/Component_AddOn_EntityFilter.js +41 -0
  13. package/_ui/add-ons/entity-filter/Component_AddOn_EntityFilter.scss +14 -0
  14. package/_ui/add-ons/entity-filter/index.d.ts +2 -0
  15. package/_ui/add-ons/entity-filter/index.js +2 -0
  16. package/_ui/add-ons/entity-filter/types.d.ts +3 -0
  17. package/_ui/add-ons/entity-filter/types.js +8 -0
  18. package/_ui/add-ons/index.d.ts +3 -0
  19. package/_ui/add-ons/index.js +3 -0
  20. package/_ui/add-ons/search-term/Component_AddOn_SearchTerm.d.ts +17 -0
  21. package/_ui/add-ons/search-term/Component_AddOn_SearchTerm.js +16 -0
  22. package/_ui/add-ons/search-term/Component_AddOn_SearchTerm.scss +21 -0
  23. package/_ui/add-ons/search-term/index.d.ts +2 -0
  24. package/_ui/add-ons/search-term/index.js +2 -0
  25. package/_ui/add-ons/search-term/types.d.ts +3 -0
  26. package/_ui/add-ons/search-term/types.js +21 -0
  27. package/_ui/add-ons/search-terms/Component_AddOn_SearchTerms.d.ts +17 -0
  28. package/_ui/add-ons/search-terms/Component_AddOn_SearchTerms.js +16 -0
  29. package/_ui/add-ons/search-terms/Component_AddOn_SearchTerms.scss +21 -0
  30. package/_ui/add-ons/search-terms/index.d.ts +2 -0
  31. package/_ui/add-ons/search-terms/index.js +2 -0
  32. package/_ui/add-ons/search-terms/types.d.ts +3 -0
  33. package/_ui/add-ons/search-terms/types.js +31 -0
  34. package/_ui/components/Component_SearchAddOn.d.ts +16 -0
  35. package/_ui/components/Component_SearchAddOn.js +20 -0
  36. package/_ui/components/Component_SearchMeta/Component_SearchMeta.d.ts +16 -0
  37. package/_ui/components/Component_SearchMeta/Component_SearchMeta.js +66 -0
  38. package/_ui/components/Component_SearchMeta/Component_SearchMeta.scss +17 -0
  39. package/_ui/components/Component_SearchMeta/index.d.ts +1 -0
  40. package/_ui/components/Component_SearchMeta/index.js +1 -0
  41. package/_ui/components/Component_SearchResults/Component_SearchResults.d.ts +22 -0
  42. package/_ui/components/Component_SearchResults/Component_SearchResults.js +46 -0
  43. package/_ui/components/Component_SearchResults/Component_SearchResults.scss +10 -0
  44. package/_ui/components/Component_SearchResults/index.d.ts +1 -0
  45. package/_ui/components/Component_SearchResults/index.js +1 -0
  46. package/_ui/components/index.d.ts +3 -0
  47. package/_ui/components/index.js +3 -0
  48. package/_ui/index.d.ts +3 -0
  49. package/_ui/index.js +3 -0
  50. package/_ui/sorters/index.d.ts +2 -0
  51. package/_ui/sorters/index.js +2 -0
  52. package/_ui/sorters/search-term/SearchSorter_SearchTerm.d.ts +3 -0
  53. package/_ui/sorters/search-term/SearchSorter_SearchTerm.js +8 -0
  54. package/_ui/sorters/search-terms/SearchSorter_SearchTerms.d.ts +3 -0
  55. package/_ui/sorters/search-terms/SearchSorter_SearchTerms.js +8 -0
  56. package/index.d.ts +2 -0
  57. package/index.js +2 -0
  58. package/package.json +62 -0
@@ -0,0 +1,27 @@
1
+ import { DBPointer } from '@nu-art/ts-common';
2
+ export type SearchResult = DBPointer & {
3
+ filterResults: {
4
+ [k: string]: {
5
+ value: any;
6
+ score?: number;
7
+ };
8
+ };
9
+ };
10
+ export type SearchAddOnDef<Key extends string, //The addon key
11
+ ValueType extends any, //The type of the value held in the filter dictionary
12
+ MethodName extends string, //The name of the method that the search item needs to implement
13
+ ItemValueType extends any> = {
14
+ key: Key;
15
+ valueType: ValueType;
16
+ methodName: MethodName;
17
+ itemValueType: ItemValueType;
18
+ };
19
+ export type SearchAddOn<Def extends SearchAddOnDef<any, any, any, any>> = {
20
+ key: Def['key'];
21
+ methodName: Def['methodName'];
22
+ resultFilter: (value: NonNullable<Def['valueType']>, item: SearchResult) => {
23
+ pass: boolean;
24
+ score?: number;
25
+ };
26
+ isActive: (param: Def['valueType']) => boolean;
27
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ import { Logger } from '@nu-art/ts-common';
2
+ import { SearchAddOn, SearchAddOnDef, SearchResult } from './SearchAddOn.js';
3
+ import { SearchItem } from './SearchItem.js';
4
+ import { SearchSorter } from './SearchSorter.js';
5
+ export interface SearchAddOnRenderer {
6
+ __onSearchFilterChanged: VoidFunction;
7
+ readonly addOn: SearchAddOn<any>;
8
+ }
9
+ export interface SearchResultsRenderer {
10
+ __onSearchResultsChanged: VoidFunction;
11
+ }
12
+ export declare class SearchContext extends Logger {
13
+ private searchItems;
14
+ private activeSearchItems;
15
+ private addOns;
16
+ private activeAddOns;
17
+ private sorters;
18
+ private minimumRequiredActiveAddons;
19
+ private readonly searchDebouncer;
20
+ private readonly filterDictionary;
21
+ private readonly _filterChangeListeners;
22
+ private searchResults?;
23
+ private searchTime?;
24
+ private readonly _searchResultChangeListeners;
25
+ constructor(key: string);
26
+ setSearchItems: (items: SearchItem<any, any>[]) => this;
27
+ setAddOns: (addOns: SearchAddOn<any>[]) => this;
28
+ setSorters: (sorters: SearchSorter<any>[]) => this;
29
+ setMinimumRequiredActiveAddOns: (num: number) => this;
30
+ private setActiveAddOns;
31
+ private setActiveSearchItems;
32
+ private getInitialSearchResults;
33
+ private search;
34
+ getActiveSearchItems: () => Readonly<{
35
+ module: import("@nu-art/thunderstorm-frontend").ModuleFE_BaseDB<any, import("@nu-art/thunderstorm-frontend/core/db-api-gen/db-def").DBApiFEConfig<any>>;
36
+ entityLabel: string;
37
+ addOnMethods: Readonly<{
38
+ [x: string]: (result: SearchResult) => unknown;
39
+ }>;
40
+ compatibleAddOnKeys: readonly PropertyKey[];
41
+ resultRenderer: (result: SearchResult, style?: import("react").CSSProperties) => import("react").ReactNode;
42
+ labelResolver: (result: SearchResult) => string;
43
+ }>[];
44
+ getSearchResults: () => SearchResult[] | undefined;
45
+ getSearchTime: () => number | undefined;
46
+ filter: {
47
+ set: <AddOn extends SearchAddOnDef<string, any, any, any>>(key: AddOn["key"], value: AddOn["valueType"]) => void;
48
+ get: <AddOn extends SearchAddOnDef<string, any, any, any>>(key: AddOn["key"]) => AddOn["valueType"] | undefined;
49
+ };
50
+ filterChangeListeners: {
51
+ register: (renderer: SearchAddOnRenderer) => void;
52
+ unregister: (renderer: SearchAddOnRenderer) => void;
53
+ };
54
+ searchResultChangeListeners: {
55
+ register: (renderer: SearchResultsRenderer) => void;
56
+ unregister: (renderer: SearchResultsRenderer) => void;
57
+ };
58
+ }
@@ -0,0 +1,147 @@
1
+ import { arrayIncludesAll, BadImplementationException, Debounce, Logger, removeItemFromArray } from '@nu-art/ts-common';
2
+ export class SearchContext extends Logger {
3
+ searchItems;
4
+ activeSearchItems;
5
+ addOns;
6
+ activeAddOns;
7
+ sorters;
8
+ minimumRequiredActiveAddons = 0;
9
+ searchDebouncer = new Debounce(() => this.search(), 200, 500);
10
+ filterDictionary = {};
11
+ _filterChangeListeners = [];
12
+ searchResults;
13
+ searchTime;
14
+ _searchResultChangeListeners = [];
15
+ constructor(key) {
16
+ super(`SearchContext-${key}`);
17
+ this.searchItems = [];
18
+ this.activeSearchItems = [];
19
+ this.addOns = [];
20
+ this.activeAddOns = [];
21
+ this.sorters = [];
22
+ }
23
+ //######################### Factory Logic #########################
24
+ setSearchItems = (items) => {
25
+ this.searchItems = items;
26
+ this.setActiveSearchItems();
27
+ this.logInfo('Set Search Items', this.searchItems);
28
+ return this;
29
+ };
30
+ setAddOns = (addOns) => {
31
+ this.addOns = addOns;
32
+ this.setActiveAddOns();
33
+ this.setActiveSearchItems();
34
+ return this;
35
+ };
36
+ setSorters = (sorters) => {
37
+ this.sorters = sorters;
38
+ return this;
39
+ };
40
+ setMinimumRequiredActiveAddOns = (num) => {
41
+ this.minimumRequiredActiveAddons = num;
42
+ return this;
43
+ };
44
+ //######################### Internal Logic #########################
45
+ setActiveAddOns = () => {
46
+ this.activeAddOns = this.addOns.filter(addOn => {
47
+ const currentValue = this.filterDictionary[addOn.key];
48
+ return addOn.isActive(currentValue);
49
+ });
50
+ };
51
+ setActiveSearchItems = () => {
52
+ const activeAddOnKeys = this.activeAddOns.map(addOn => addOn.key);
53
+ if (!activeAddOnKeys.length)
54
+ return this.activeSearchItems = [...this.searchItems];
55
+ this.activeSearchItems = this.searchItems.filter(searchItem => arrayIncludesAll(searchItem.compatibleAddOnKeys, activeAddOnKeys));
56
+ };
57
+ getInitialSearchResults = () => {
58
+ const results = [];
59
+ this.activeSearchItems.forEach(searchItem => {
60
+ const pointers = searchItem.module.cache.all().map(i => ({ dbKey: searchItem.module.dbDef.dbKey, id: i._id, filterResults: {} }));
61
+ results.push(...pointers);
62
+ });
63
+ return results;
64
+ };
65
+ search = () => {
66
+ if (this.activeAddOns.length < this.minimumRequiredActiveAddons) {
67
+ if (this.addOns.length) {
68
+ delete this.searchResults;
69
+ delete this.searchTime;
70
+ this._searchResultChangeListeners.forEach(listener => listener.__onSearchResultsChanged());
71
+ }
72
+ return;
73
+ }
74
+ const startTime = Date.now();
75
+ let searchResults = this.getInitialSearchResults();
76
+ //Map active search item to dbKeys for O(1) access
77
+ const searchItemMap = this.activeSearchItems.reduce((map, item) => {
78
+ map[item.module.dbDef.dbKey] = item;
79
+ return map;
80
+ }, {});
81
+ //Cycle filter the results by active add-ons
82
+ this.activeAddOns.forEach(addOn => {
83
+ const filterValue = this.filterDictionary[addOn.key];
84
+ searchResults = searchResults.filter(result => {
85
+ const searchItem = searchItemMap[result.dbKey];
86
+ result.filterResults[addOn.key] = { value: searchItem.addOnMethods[addOn.methodName](result) };
87
+ const filterResult = addOn.resultFilter(filterValue, result);
88
+ result.filterResults[addOn.key].score = filterResult.score;
89
+ return filterResult.pass;
90
+ });
91
+ });
92
+ //Sort results
93
+ this.sorters.forEach(sorter => {
94
+ const addOn = this.activeAddOns.find(addOn => addOn.key === sorter.key);
95
+ if (!addOn) //No connected active addon was found, do not run sorter
96
+ return;
97
+ sorter.sortFunction(searchResults);
98
+ });
99
+ this.searchResults = searchResults;
100
+ this.searchTime = Date.now() - startTime;
101
+ this._searchResultChangeListeners.forEach(listener => listener.__onSearchResultsChanged());
102
+ };
103
+ //######################### Public Logic #########################
104
+ getActiveSearchItems = () => [...this.activeSearchItems];
105
+ getSearchResults = () => this.searchResults;
106
+ getSearchTime = () => this.searchTime;
107
+ filter = {
108
+ set: (key, value) => {
109
+ this.filterDictionary[key] = value;
110
+ //Re-calculate active addons and search items
111
+ this.setActiveAddOns();
112
+ this.setActiveSearchItems();
113
+ //Notify all listeners that filters have changed
114
+ this._filterChangeListeners.forEach(listener => listener.__onSearchFilterChanged());
115
+ //Trigger search
116
+ this.searchDebouncer.trigger();
117
+ },
118
+ get: (key) => {
119
+ return this.filterDictionary[key];
120
+ },
121
+ };
122
+ filterChangeListeners = {
123
+ register: (renderer) => {
124
+ if (this._filterChangeListeners.includes(renderer))
125
+ return;
126
+ const addOn = this.addOns.find(addOn => addOn.key === renderer.addOn.key);
127
+ if (!addOn) {
128
+ this.logError(renderer, this.addOns);
129
+ throw new BadImplementationException('Trying to register a listener that is not connected to an addon');
130
+ }
131
+ this._filterChangeListeners.push(renderer);
132
+ },
133
+ unregister: (renderer) => {
134
+ removeItemFromArray(this._filterChangeListeners, renderer);
135
+ }
136
+ };
137
+ searchResultChangeListeners = {
138
+ register: (renderer) => {
139
+ if (this._searchResultChangeListeners.includes(renderer))
140
+ return;
141
+ this._searchResultChangeListeners.push(renderer);
142
+ },
143
+ unregister: (renderer) => {
144
+ removeItemFromArray(this._searchResultChangeListeners, renderer);
145
+ }
146
+ };
147
+ }
@@ -0,0 +1,26 @@
1
+ import { CSSProperties, ReactNode } from 'react';
2
+ import { DBProto } from '@nu-art/ts-common';
3
+ import { SearchAddOnDef, SearchResult } from './SearchAddOn.js';
4
+ import { ModuleFE_BaseDB } from '@nu-art/thunderstorm-frontend';
5
+ type AddOnTuple = readonly SearchAddOnDef<any, any, any, any>[];
6
+ type SearchAddOnsMethodExtractor<A extends AddOnTuple> = {
7
+ [M in A[number]['methodName']]: Extract<A[number], {
8
+ methodName: M;
9
+ }> extends {
10
+ itemValueType: infer IP;
11
+ } ? (result: SearchResult) => IP : never;
12
+ };
13
+ type SearchAddOnsKeyExtractor<A extends AddOnTuple> = {
14
+ readonly [K in keyof A]: A[K] extends {
15
+ key: infer Key extends PropertyKey;
16
+ } ? Key : never;
17
+ };
18
+ export type SearchItem<Proto extends DBProto<any>, A extends AddOnTuple> = Readonly<{
19
+ module: ModuleFE_BaseDB<Proto>;
20
+ entityLabel: string;
21
+ addOnMethods: Readonly<SearchAddOnsMethodExtractor<A>>;
22
+ compatibleAddOnKeys: Readonly<SearchAddOnsKeyExtractor<A>>;
23
+ resultRenderer: (result: SearchResult, style?: CSSProperties) => ReactNode;
24
+ labelResolver: (result: SearchResult) => string;
25
+ }>;
26
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { SearchAddOnDef, SearchResult } from './SearchAddOn.js';
2
+ export type SearchSorter<AddOnDef extends SearchAddOnDef<string, any, string, any>> = {
3
+ key: AddOnDef['key'];
4
+ sortFunction: (results: SearchResult[]) => void;
5
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export * from './SearchAddOn.js';
2
+ export * from './SearchItem.js';
3
+ export * from './SearchContext.js';
package/_core/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './SearchAddOn.js';
2
+ export * from './SearchItem.js';
3
+ export * from './SearchContext.js';
@@ -0,0 +1,22 @@
1
+ import { Component_SearchAddOn } from '../../components/Component_SearchAddOn.js';
2
+ import { AddOnDef_EntityFilter } from './types.js';
3
+ import { InferProps, InferState } from '@nu-art/thunderstorm-frontend';
4
+ import './Component_AddOn_EntityFilter.scss';
5
+ import { SearchAddOn, SearchItem } from '../../../_core/index.js';
6
+ type Props = {
7
+ label?: string;
8
+ };
9
+ type State = {
10
+ label: string;
11
+ activeSearchItems: SearchItem<any, any>[];
12
+ };
13
+ export declare class Component_AddOn_EntityFilter extends Component_SearchAddOn<AddOnDef_EntityFilter, Props, State> {
14
+ addOn: SearchAddOn<AddOnDef_EntityFilter>;
15
+ protected deriveStateFromProps(nextProps: InferProps<this>, state: InferState<this>): InferState<this>;
16
+ private onItemSelected;
17
+ private onItemRemoved;
18
+ render(): import("react/jsx-runtime").JSX.Element | undefined;
19
+ private render_SelectedItem;
20
+ private render_ItemSelector;
21
+ }
22
+ export {};
@@ -0,0 +1,41 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Component_SearchAddOn } from '../../components/Component_SearchAddOn.js';
3
+ import { AddOn_EntityFilter } from './types.js';
4
+ import { LL_H_C, SimpleListAdapter, TS_DropDown, TS_PropRenderer } from '@nu-art/thunderstorm-frontend';
5
+ import { TS_Icons } from '@nu-art/ts-styles';
6
+ import './Component_AddOn_EntityFilter.scss';
7
+ export class Component_AddOn_EntityFilter extends Component_SearchAddOn {
8
+ addOn = AddOn_EntityFilter;
9
+ // ######################### Life Cycle #########################
10
+ deriveStateFromProps(nextProps, state) {
11
+ state.label = nextProps.label ?? 'By Entity';
12
+ state.activeSearchItems = nextProps.context.getActiveSearchItems();
13
+ return state;
14
+ }
15
+ // ######################### Logic #########################
16
+ onItemSelected = (searchItem) => {
17
+ const items = this.state.value ? [...this.state.value] : [];
18
+ items.push(searchItem.module.dbDef.dbKey);
19
+ this.setValue(items);
20
+ };
21
+ onItemRemoved = (searchItem) => {
22
+ const items = (this.state.value ?? []).filter(item => item !== searchItem.module.dbDef.dbKey);
23
+ this.setValue(items);
24
+ };
25
+ // ######################### Render #########################
26
+ render() {
27
+ if (!this.state.activeSearchItems)
28
+ return;
29
+ return _jsx(TS_PropRenderer.Vertical, { label: this.state.label, className: 'search-add-on__entity-filter', children: _jsxs(LL_H_C, { className: 'search-add-on__entity-filter__item-list', children: [this.state.value?.map(this.render_SelectedItem), this.render_ItemSelector()] }) });
30
+ }
31
+ render_SelectedItem = (itemKey) => {
32
+ const searchItem = this.state.activeSearchItems.find(item => item.module.dbDef.dbKey === itemKey);
33
+ if (!searchItem)
34
+ return void this.logWarning(`Could not find a search item for key ${itemKey}`);
35
+ return _jsxs(LL_H_C, { className: 'search-add-on__entity-filter__selected-item', children: [searchItem.entityLabel, _jsx(TS_Icons.x.component, { onClick: () => this.onItemRemoved(searchItem) })] }, searchItem.module.dbDef.dbKey);
36
+ };
37
+ render_ItemSelector = () => {
38
+ const adapter = SimpleListAdapter(this.state.activeSearchItems, item => _jsx(_Fragment, { children: item.item.entityLabel }));
39
+ return _jsx(TS_DropDown, { adapter: adapter, selected: undefined, onSelected: this.onItemSelected, placeholder: 'Select an entity', queryFilter: item => !this.state.value?.includes(item.module.dbDef.dbKey) });
40
+ };
41
+ }
@@ -0,0 +1,14 @@
1
+ .search-add-on__entity-filter {
2
+ padding: 16px;
3
+ flex-shrink: 0;
4
+
5
+ .search-add-on__entity-filter__item-list {
6
+ width: 100%;
7
+ flex-wrap: wrap;
8
+ gap: 8px;
9
+
10
+ .ts-dropdown {
11
+ max-width: 150px;
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ export * from './Component_AddOn_EntityFilter.js';
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ export * from './Component_AddOn_EntityFilter.js';
@@ -0,0 +1,3 @@
1
+ import { SearchAddOn, SearchAddOnDef } from '../../../_core/index.js';
2
+ export type AddOnDef_EntityFilter = SearchAddOnDef<'entity', string[] | undefined, 'getEntityKey', string>;
3
+ export declare const AddOn_EntityFilter: SearchAddOn<AddOnDef_EntityFilter>;
@@ -0,0 +1,8 @@
1
+ export const AddOn_EntityFilter = {
2
+ key: 'entity',
3
+ methodName: 'getEntityKey',
4
+ resultFilter: (entities, result) => {
5
+ return { pass: entities.includes(result.filterResults['entity'].value) };
6
+ },
7
+ isActive: (entities) => !!entities?.length,
8
+ };
@@ -0,0 +1,3 @@
1
+ export * from './entity-filter/index.js';
2
+ export * from './search-term/index.js';
3
+ export * from './search-terms/index.js';
@@ -0,0 +1,3 @@
1
+ export * from './entity-filter/index.js';
2
+ export * from './search-term/index.js';
3
+ export * from './search-terms/index.js';
@@ -0,0 +1,17 @@
1
+ import { SearchAddOn } from '../../../_core/index.js';
2
+ import { Component_SearchAddOn } from '../../components/Component_SearchAddOn.js';
3
+ import { AddOnDef_SearchTerm } from './types.js';
4
+ import './Component_AddOn_SearchTerm.scss';
5
+ import { InferProps, InferState } from '@nu-art/thunderstorm-frontend';
6
+ type Props = {
7
+ placeholder?: string;
8
+ };
9
+ type State = {
10
+ placeholder?: string;
11
+ };
12
+ export declare class Component_AddOn_SearchTerm extends Component_SearchAddOn<AddOnDef_SearchTerm, Props, State> {
13
+ protected deriveStateFromProps(nextProps: InferProps<this>, state: InferState<this>): InferState<this>;
14
+ addOn: SearchAddOn<AddOnDef_SearchTerm>;
15
+ render(): import("react/jsx-runtime").JSX.Element;
16
+ }
17
+ export {};
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { LL_H_C, TS_Input } from '@nu-art/thunderstorm-frontend';
3
+ import { Component_SearchAddOn } from '../../components/Component_SearchAddOn.js';
4
+ import { AddOn_SearchTerm } from './types.js';
5
+ import './Component_AddOn_SearchTerm.scss';
6
+ import { TS_Icons } from '@nu-art/ts-styles';
7
+ export class Component_AddOn_SearchTerm extends Component_SearchAddOn {
8
+ deriveStateFromProps(nextProps, state) {
9
+ state.placeholder = nextProps.placeholder;
10
+ return state;
11
+ }
12
+ addOn = AddOn_SearchTerm;
13
+ render() {
14
+ return _jsxs(LL_H_C, { className: 'search-add-on__search-term', children: [_jsx(TS_Input, { type: 'text', value: this.state.value, placeholder: this.state.placeholder, onChange: val => this.setValue(val.trimStart()) }), _jsx(TS_Icons.Search.component, {})] });
15
+ }
16
+ }
@@ -0,0 +1,21 @@
1
+ .search-add-on__search-term {
2
+ width: 100%;
3
+ min-width: 300px;
4
+ height: 28px;
5
+ gap: 16px;
6
+ background: #fff;
7
+ border-radius: 10px;
8
+ padding: 0 16px;
9
+
10
+ .icon--wrapper {
11
+ flex-shrink: 0;
12
+ }
13
+
14
+ .ts-input {
15
+ width: 0;
16
+ flex: 1;
17
+ border: none;
18
+ max-width: unset;
19
+ outline: none;
20
+ }
21
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ export * from './Component_AddOn_SearchTerm.js';
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ export * from './Component_AddOn_SearchTerm.js';
@@ -0,0 +1,3 @@
1
+ import { SearchAddOn, SearchAddOnDef } from '../../../_core/index.js';
2
+ export type AddOnDef_SearchTerm = SearchAddOnDef<'searchTerm', string | undefined, 'getSearchTerm', string>;
3
+ export declare const AddOn_SearchTerm: SearchAddOn<AddOnDef_SearchTerm>;
@@ -0,0 +1,21 @@
1
+ var SearchTermSortScore;
2
+ (function (SearchTermSortScore) {
3
+ SearchTermSortScore[SearchTermSortScore["ExactMatch"] = 0] = "ExactMatch";
4
+ SearchTermSortScore[SearchTermSortScore["StartsWith"] = 1] = "StartsWith";
5
+ SearchTermSortScore[SearchTermSortScore["Includes"] = 2] = "Includes";
6
+ })(SearchTermSortScore || (SearchTermSortScore = {}));
7
+ export const AddOn_SearchTerm = {
8
+ key: 'searchTerm',
9
+ methodName: 'getSearchTerm',
10
+ resultFilter: (value, result) => {
11
+ const itemValue = result.filterResults[AddOn_SearchTerm.key].value;
12
+ if (itemValue === value)
13
+ return { pass: true, score: SearchTermSortScore.ExactMatch };
14
+ if (itemValue.startsWith(value))
15
+ return { pass: true, score: SearchTermSortScore.StartsWith };
16
+ if (itemValue.includes(value))
17
+ return { pass: true, score: SearchTermSortScore.Includes };
18
+ return { pass: false };
19
+ },
20
+ isActive: (param) => !!param && param.length >= 3,
21
+ };
@@ -0,0 +1,17 @@
1
+ import { SearchAddOn } from '../../../_core/index.js';
2
+ import { Component_SearchAddOn } from '../../components/Component_SearchAddOn.js';
3
+ import { AddOnDef_SearchTerms } from './types.js';
4
+ import './Component_AddOn_SearchTerms.scss';
5
+ import { InferProps, InferState } from '@nu-art/thunderstorm-frontend';
6
+ type Props = {
7
+ placeholder?: string;
8
+ };
9
+ type State = {
10
+ placeholder?: string;
11
+ };
12
+ export declare class Component_AddOn_SearchTerms extends Component_SearchAddOn<AddOnDef_SearchTerms, Props, State> {
13
+ protected deriveStateFromProps(nextProps: InferProps<this>, state: InferState<this>): InferState<this>;
14
+ addOn: SearchAddOn<AddOnDef_SearchTerms>;
15
+ render(): import("react/jsx-runtime").JSX.Element;
16
+ }
17
+ export {};
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { LL_H_C, TS_Input } from '@nu-art/thunderstorm-frontend';
3
+ import { Component_SearchAddOn } from '../../components/Component_SearchAddOn.js';
4
+ import { AddOn_SearchTerms } from './types.js';
5
+ import './Component_AddOn_SearchTerms.scss';
6
+ import { TS_Icons } from '@nu-art/ts-styles';
7
+ export class Component_AddOn_SearchTerms extends Component_SearchAddOn {
8
+ deriveStateFromProps(nextProps, state) {
9
+ state.placeholder = nextProps.placeholder;
10
+ return state;
11
+ }
12
+ addOn = AddOn_SearchTerms;
13
+ render() {
14
+ return _jsxs(LL_H_C, { className: 'search-add-on__search-terms', children: [_jsx(TS_Input, { type: 'text', value: this.state.value, placeholder: this.state.placeholder, onChange: val => this.setValue(val.trimStart()) }), _jsx(TS_Icons.Search.component, {})] });
15
+ }
16
+ }
@@ -0,0 +1,21 @@
1
+ .search-add-on__search-terms {
2
+ width: 100%;
3
+ min-width: 300px;
4
+ height: 28px;
5
+ gap: 16px;
6
+ background: #fff;
7
+ border-radius: 10px;
8
+ padding: 0 16px;
9
+
10
+ .icon--wrapper {
11
+ flex-shrink: 0;
12
+ }
13
+
14
+ .ts-input {
15
+ width: 0;
16
+ flex: 1;
17
+ border: none;
18
+ max-width: unset;
19
+ outline: none;
20
+ }
21
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ export * from './Component_AddOn_SearchTerms.js';
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ export * from './Component_AddOn_SearchTerms.js';
@@ -0,0 +1,3 @@
1
+ import { SearchAddOn, SearchAddOnDef } from '../../../_core/index.js';
2
+ export type AddOnDef_SearchTerms = SearchAddOnDef<'searchTerms', string | undefined, 'getSearchTerms', string[]>;
3
+ export declare const AddOn_SearchTerms: SearchAddOn<AddOnDef_SearchTerms>;
@@ -0,0 +1,31 @@
1
+ var BaseScore;
2
+ (function (BaseScore) {
3
+ BaseScore[BaseScore["ExactMatch"] = 1] = "ExactMatch";
4
+ BaseScore[BaseScore["StartsWith"] = 2] = "StartsWith";
5
+ BaseScore[BaseScore["Includes"] = 3] = "Includes";
6
+ })(BaseScore || (BaseScore = {}));
7
+ const calcScore = (base, index) => {
8
+ return base - (1 / Math.pow(2, index));
9
+ };
10
+ export const AddOn_SearchTerms = {
11
+ key: 'searchTerms',
12
+ methodName: 'getSearchTerms',
13
+ isActive: (param) => !!param && param.length >= 3,
14
+ resultFilter: (value, result) => {
15
+ const terms = result.filterResults[AddOn_SearchTerms.key].value;
16
+ let index;
17
+ //Find exact match
18
+ index = terms.findIndex(term => term === value);
19
+ if (index !== -1)
20
+ return { pass: true, score: calcScore(BaseScore.ExactMatch, index) };
21
+ //Find StatsWith
22
+ index = terms.findIndex(term => term.startsWith(value));
23
+ if (index !== -1)
24
+ return { pass: true, score: calcScore(BaseScore.StartsWith, index) };
25
+ //Find Includes
26
+ index = terms.findIndex(term => term.includes(value));
27
+ if (index !== -1)
28
+ return { pass: true, score: calcScore(BaseScore.Includes, index) };
29
+ return { pass: false };
30
+ }
31
+ };
@@ -0,0 +1,16 @@
1
+ import { ComponentSync } from '@nu-art/thunderstorm-frontend';
2
+ import { SearchAddOn, SearchAddOnDef, SearchAddOnRenderer, SearchContext } from '../../_core/index.js';
3
+ type Props = {
4
+ context: SearchContext;
5
+ };
6
+ type State<AddOnDef extends SearchAddOnDef<string, any, any, any>> = {
7
+ value?: AddOnDef['valueType'];
8
+ };
9
+ export declare abstract class Component_SearchAddOn<AddOnDef extends SearchAddOnDef<string, any, any, any>, _Props extends {} = {}, _State extends {} = {}, P extends _Props & Props = _Props & Props, S extends _State & State<AddOnDef> = _State & State<AddOnDef>> extends ComponentSync<P, S> implements SearchAddOnRenderer {
10
+ abstract readonly addOn: SearchAddOn<AddOnDef>;
11
+ __onSearchFilterChanged(): void;
12
+ componentDidMount(): void;
13
+ componentWillUnmount(): void;
14
+ protected setValue: (val?: AddOnDef["valueType"]) => void;
15
+ }
16
+ export {};
@@ -0,0 +1,20 @@
1
+ import { ComponentSync } from '@nu-art/thunderstorm-frontend';
2
+ import { compare } from '@nu-art/ts-common';
3
+ export class Component_SearchAddOn extends ComponentSync {
4
+ //######################### Life Cycle #########################
5
+ __onSearchFilterChanged() {
6
+ const nextValue = this.props.context.filter.get(this.addOn.key);
7
+ if (!compare(this.state.value, nextValue))
8
+ this.setState({ value: nextValue });
9
+ }
10
+ componentDidMount() {
11
+ this.props.context.filterChangeListeners.register(this);
12
+ }
13
+ componentWillUnmount() {
14
+ this.props.context.filterChangeListeners.unregister(this);
15
+ }
16
+ //######################### Logic #########################
17
+ setValue = (val) => {
18
+ this.props.context.filter.set(this.addOn.key, val);
19
+ };
20
+ }
@@ -0,0 +1,16 @@
1
+ import { ComponentSync } from '@nu-art/thunderstorm-frontend';
2
+ import './Component_SearchMeta.scss';
3
+ import { SearchContext, SearchResultsRenderer } from '../../../_core/SearchContext.js';
4
+ type Props = {
5
+ context: SearchContext;
6
+ };
7
+ export declare class Component_SearchMeta extends ComponentSync<Props> implements SearchResultsRenderer {
8
+ __onSearchResultsChanged: () => void;
9
+ componentDidMount(): void;
10
+ componentWillUnmount(): void;
11
+ private printResults;
12
+ private exportResults;
13
+ render(): import("react/jsx-runtime").JSX.Element | undefined;
14
+ private renderTime;
15
+ }
16
+ export {};
@@ -0,0 +1,66 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { ComponentSync, LL_H_C, ModuleFE_Thunderstorm, stopPropagation } from '@nu-art/thunderstorm-frontend';
3
+ import './Component_SearchMeta.scss';
4
+ import { filterInstances, formatTimestamp } from '@nu-art/ts-common';
5
+ import { ModuleFE_CSVParser } from '@nu-art/thunderstorm-frontend/modules/ModuleFE_CSVParser';
6
+ export class Component_SearchMeta extends ComponentSync {
7
+ //######################### Life Cycle #########################
8
+ __onSearchResultsChanged = () => {
9
+ this.forceUpdate();
10
+ };
11
+ componentDidMount() {
12
+ this.props.context.searchResultChangeListeners.register(this);
13
+ }
14
+ componentWillUnmount() {
15
+ this.props.context.searchResultChangeListeners.unregister(this);
16
+ }
17
+ //######################### Logic #########################
18
+ printResults = (e) => {
19
+ if (!e.metaKey)
20
+ return;
21
+ stopPropagation(e);
22
+ this.logInfo('SearchResults', this.props.context.getSearchResults());
23
+ };
24
+ exportResults = (e) => {
25
+ const results = this.props.context.getSearchResults();
26
+ if (!results?.length)
27
+ return;
28
+ stopPropagation(e);
29
+ const searchItemMap = this.props.context.getActiveSearchItems().reduce((map, searchItem) => {
30
+ map[searchItem.module.dbDef.dbKey] = searchItem;
31
+ return map;
32
+ }, {});
33
+ //Map results csv ready objects
34
+ const objects = filterInstances(results.map(result => {
35
+ const searchItem = searchItemMap[result.dbKey];
36
+ if (!searchItem)
37
+ return;
38
+ return {
39
+ collection: result.dbKey,
40
+ id: result.id,
41
+ label: searchItem.labelResolver(result),
42
+ };
43
+ }));
44
+ const str = ModuleFE_CSVParser.toString(objects);
45
+ const fileName = `Search Results - [${formatTimestamp('DD/MM/YYYY-HH:mm:ss')}]`;
46
+ ModuleFE_Thunderstorm.downloadFile({
47
+ fileName,
48
+ content: str,
49
+ mimeType: 'text/csv',
50
+ });
51
+ };
52
+ //######################### Render #########################
53
+ render() {
54
+ const searchResults = this.props.context.getSearchResults();
55
+ if (!searchResults?.length)
56
+ return;
57
+ return _jsxs(LL_H_C, { className: 'c__search-meta', onClick: this.printResults, children: [_jsxs("div", { className: 'c__search-meta__results', onClick: this.exportResults, children: [searchResults.length, " Results"] }), this.renderTime()] });
58
+ }
59
+ renderTime = () => {
60
+ const searchTime = this.props.context.getSearchTime();
61
+ if (!searchTime)
62
+ return;
63
+ const str = searchTime >= 1000 ? `${searchTime / 1000}s` : `${searchTime}ms`;
64
+ return _jsx("div", { className: 'c__search-meta__time', children: str });
65
+ };
66
+ }
@@ -0,0 +1,17 @@
1
+ @use '@nu-art/ts-styles' as S;
2
+
3
+ .c__search-meta {
4
+ width: 100%;
5
+ height: 28px;
6
+ justify-content: space-between;
7
+
8
+ .c__search-meta__time {
9
+ color: inherit;
10
+ font: inherit;
11
+ }
12
+
13
+ .c__search-meta__results {
14
+ font: inherit;
15
+ @include S.mouse-interactive-text(inherit, S.dark-blue(4), S.dark-blue(3));
16
+ }
17
+ }
@@ -0,0 +1 @@
1
+ export * from './Component_SearchMeta.js';
@@ -0,0 +1 @@
1
+ export * from './Component_SearchMeta.js';
@@ -0,0 +1,22 @@
1
+ import { ComponentSync } from '@nu-art/thunderstorm-frontend';
2
+ import { SearchContext, SearchResult, SearchResultsRenderer } from '../../../_core/index.js';
3
+ import './Component_SearchResults.scss';
4
+ type Props = {
5
+ context: SearchContext;
6
+ itemHeight: number;
7
+ maxItemsOnScreen: number;
8
+ };
9
+ type State = {
10
+ searchResults?: SearchResult[];
11
+ maxHeight: number;
12
+ };
13
+ export declare class Component_SearchResults extends ComponentSync<Props, State> implements SearchResultsRenderer {
14
+ protected deriveStateFromProps(nextProps: Props, state: State): State;
15
+ __onSearchResultsChanged: () => void;
16
+ componentDidMount(): void;
17
+ componentWillUnmount(): void;
18
+ private getList;
19
+ render(): import("react/jsx-runtime").JSX.Element;
20
+ private render_NoResults;
21
+ }
22
+ export {};
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ComponentSync, LL_V_L, VirtualizedList } from '@nu-art/thunderstorm-frontend';
3
+ import './Component_SearchResults.scss';
4
+ import { filterInstances } from '@nu-art/ts-common';
5
+ export class Component_SearchResults extends ComponentSync {
6
+ //######################### Life Cycle #########################
7
+ deriveStateFromProps(nextProps, state) {
8
+ state.maxHeight = nextProps.maxItemsOnScreen * nextProps.itemHeight;
9
+ return state;
10
+ }
11
+ __onSearchResultsChanged = () => {
12
+ this.setState({ searchResults: this.props.context.getSearchResults() });
13
+ };
14
+ componentDidMount() {
15
+ this.props.context.searchResultChangeListeners.register(this);
16
+ }
17
+ componentWillUnmount() {
18
+ this.props.context.searchResultChangeListeners.unregister(this);
19
+ }
20
+ //######################### Render #########################
21
+ getList = () => {
22
+ if (!this.state.searchResults?.length)
23
+ return [];
24
+ const activeSearchItemMap = this.props.context.getActiveSearchItems().reduce((map, item) => {
25
+ map[item.module.dbDef.dbKey] = item;
26
+ return map;
27
+ }, {});
28
+ return filterInstances(this.state.searchResults.map(result => {
29
+ const item = activeSearchItemMap[result.dbKey];
30
+ if (!item)
31
+ return;
32
+ return (style) => item.resultRenderer(result, style);
33
+ }));
34
+ };
35
+ //######################### Render #########################
36
+ render() {
37
+ if (!this.state.searchResults?.length)
38
+ return this.render_NoResults();
39
+ const list = this.getList();
40
+ const height = Math.min(list.length * this.props.itemHeight, this.state.maxHeight);
41
+ return _jsx(VirtualizedList, { className: 'c__search-results', width: 0, height: height, itemHeight: this.props.itemHeight, listToRender: list, omitWrapper: true });
42
+ }
43
+ render_NoResults = () => {
44
+ return _jsx(LL_V_L, { className: 'c__search-results no-results', children: "No results to show" });
45
+ };
46
+ }
@@ -0,0 +1,10 @@
1
+ .c__search-results {
2
+ width: 100% !important;
3
+
4
+ &.no-results {
5
+ font: {
6
+ size: 24px;
7
+ weight: 600;
8
+ }
9
+ }
10
+ }
@@ -0,0 +1 @@
1
+ export * from './Component_SearchResults.js';
@@ -0,0 +1 @@
1
+ export * from './Component_SearchResults.js';
@@ -0,0 +1,3 @@
1
+ export * from './Component_SearchMeta/index.js';
2
+ export * from './Component_SearchResults/index.js';
3
+ export * from './Component_SearchAddOn.js';
@@ -0,0 +1,3 @@
1
+ export * from './Component_SearchMeta/index.js';
2
+ export * from './Component_SearchResults/index.js';
3
+ export * from './Component_SearchAddOn.js';
package/_ui/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './add-ons/index.js';
2
+ export * from './components/index.js';
3
+ export * from './sorters/index.js';
package/_ui/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './add-ons/index.js';
2
+ export * from './components/index.js';
3
+ export * from './sorters/index.js';
@@ -0,0 +1,2 @@
1
+ export * from './search-term/SearchSorter_SearchTerm.js';
2
+ export * from './search-terms/SearchSorter_SearchTerms.js';
@@ -0,0 +1,2 @@
1
+ export * from './search-term/SearchSorter_SearchTerm.js';
2
+ export * from './search-terms/SearchSorter_SearchTerms.js';
@@ -0,0 +1,3 @@
1
+ import { SearchSorter } from '../../../_core/SearchSorter.js';
2
+ import { AddOnDef_SearchTerm } from '../../add-ons/search-term/index.js';
3
+ export declare const SearchSorter_SearchTerm: SearchSorter<AddOnDef_SearchTerm>;
@@ -0,0 +1,8 @@
1
+ import { sortArray } from '@nu-art/ts-common';
2
+ export const SearchSorter_SearchTerm = {
3
+ key: 'searchTerm',
4
+ sortFunction: (results) => {
5
+ sortArray(results, result => result.filterResults[SearchSorter_SearchTerm.key].value.length);
6
+ sortArray(results, result => result.filterResults[SearchSorter_SearchTerm.key].score);
7
+ }
8
+ };
@@ -0,0 +1,3 @@
1
+ import { SearchSorter } from '../../../_core/SearchSorter.js';
2
+ import { AddOnDef_SearchTerms } from '../../add-ons/search-terms/types.js';
3
+ export declare const SearchSorter_SearchTerms: SearchSorter<AddOnDef_SearchTerms>;
@@ -0,0 +1,8 @@
1
+ import { sortArray } from '@nu-art/ts-common';
2
+ export const SearchSorter_SearchTerms = {
3
+ key: 'searchTerms',
4
+ sortFunction: (results) => {
5
+ sortArray(results, result => result.filterResults[SearchSorter_SearchTerms.key].value.length);
6
+ sortArray(results, result => result.filterResults[SearchSorter_SearchTerms.key].score);
7
+ }
8
+ };
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './_core/index.js';
2
+ export * from './_ui/index.js';
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './_core/index.js';
2
+ export * from './_ui/index.js';
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@nu-art/search",
3
+ "version": "0.400.7",
4
+ "description": "Search",
5
+ "keywords": [
6
+ "TacB0sS",
7
+ "express",
8
+ "infra",
9
+ "nu-art",
10
+ "thunderstorm",
11
+ "typescript"
12
+ ],
13
+ "homepage": "https://github.com/nu-art-js/thunderstorm",
14
+ "bugs": {
15
+ "url": "https://github.com/nu-art-js/thunderstorm/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+ssh://git@github.com:nu-art-js/thunderstorm.git"
20
+ },
21
+ "license": "Apache-2.0",
22
+ "author": "TacB0sS",
23
+ "scripts": {
24
+ "build": "tsc"
25
+ },
26
+ "contributors": [
27
+ {
28
+ "name": "TacB0sS"
29
+ },
30
+ {
31
+ "name": "Cipher",
32
+ "url": "https://www.linkedin.com/in/itay-leybovich-470b87229/"
33
+ }
34
+ ],
35
+ "publishConfig": {
36
+ "directory": "dist",
37
+ "linkDirectory": true
38
+ },
39
+ "dependencies": {
40
+ "@nu-art/ts-common": "0.400.7",
41
+ "@nu-art/ts-styles": "0.400.7",
42
+ "@nu-art/thunderstorm-frontend": "0.400.7",
43
+ "react": "^18.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/react": "^18.0.0"
47
+ },
48
+ "unitConfig": {
49
+ "type": "typescript-lib"
50
+ },
51
+ "type": "module",
52
+ "exports": {
53
+ ".": {
54
+ "types": "./index.d.ts",
55
+ "import": "./index.js"
56
+ },
57
+ "./*": {
58
+ "types": "./*.d.ts",
59
+ "import": "./*.js"
60
+ }
61
+ }
62
+ }