@bridgeline-digital/hawksearch-handlebars-ui 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -0
- package/.eslintrc.json +153 -0
- package/.husky/pre-commit +5 -0
- package/.lintstagedrc.json +4 -0
- package/.prettierignore +7 -0
- package/.prettierrc.json +19 -0
- package/.vscode/launch.json +15 -0
- package/custom1.html +52 -0
- package/custom2.html +44 -0
- package/gulpfile.js +73 -0
- package/index.html +39 -0
- package/package.json +57 -0
- package/src/assets/icons/chevron-back.svg +4 -0
- package/src/assets/icons/chevron-down.svg +3 -0
- package/src/assets/icons/chevron-forward.svg +4 -0
- package/src/assets/icons/chevron-left.svg +3 -0
- package/src/assets/icons/chevron-right.svg +3 -0
- package/src/assets/icons/chevron-up.svg +3 -0
- package/src/assets/icons/cross.svg +3 -0
- package/src/assets/icons/help.svg +4 -0
- package/src/assets/icons/minus.svg +3 -0
- package/src/assets/icons/plus.svg +7 -0
- package/src/assets/images/icons.svg +1 -0
- package/src/assets/images/placeholder.png +0 -0
- package/src/components/autocomplete/autocomplete.component.hbs +60 -0
- package/src/components/autocomplete/autocomplete.component.ts +124 -0
- package/src/components/base.component.ts +120 -0
- package/src/components/facet-types/base-facet.component.ts +114 -0
- package/src/components/facet-types/checkbox-list-facet/checkbox-list-facet.component.hbs +6 -0
- package/src/components/facet-types/checkbox-list-facet/checkbox-list-facet.component.ts +73 -0
- package/src/components/facet-types/checkbox-list-facet/checkbox-list.partial.hbs +42 -0
- package/src/components/facet-types/color-facet/color-facet.component.hbs +29 -0
- package/src/components/facet-types/color-facet/color-facet.component.ts +26 -0
- package/src/components/facet-types/index.ts +6 -0
- package/src/components/facet-types/linked-list-facet/link-list-facet.component.hbs +17 -0
- package/src/components/facet-types/linked-list-facet/link-list-facet.component.ts +32 -0
- package/src/components/facet-types/range-slider-facet/range-slider-facet.component.hbs +11 -0
- package/src/components/facet-types/range-slider-facet/range-slider-facet.component.ts +107 -0
- package/src/components/facet-types/search-within-facet/search-within-facet.component.hbs +3 -0
- package/src/components/facet-types/search-within-facet/search-within-facet.component.ts +33 -0
- package/src/components/facet-types/size-facet/size-facet.component.hbs +29 -0
- package/src/components/facet-types/size-facet/size-facet.component.ts +30 -0
- package/src/components/facet-wrapper/facet-wrapper.component.hbs +36 -0
- package/src/components/facet-wrapper/facet-wrapper.component.ts +62 -0
- package/src/components/facets-list/facets-list.component.hbs +11 -0
- package/src/components/facets-list/facets-list.component.ts +92 -0
- package/src/components/icon/icon.component.hbs +3 -0
- package/src/components/icon/icon.component.ts +40 -0
- package/src/components/index.ts +18 -0
- package/src/components/modified-query/modified-query.component.hbs +7 -0
- package/src/components/modified-query/modified-query.component.ts +38 -0
- package/src/components/page-size/page-size.component.hbs +7 -0
- package/src/components/page-size/page-size.component.ts +35 -0
- package/src/components/pagination/pagination.component.hbs +34 -0
- package/src/components/pagination/pagination.component.ts +85 -0
- package/src/components/query-suggestions/query-suggestions.component.hbs +3 -0
- package/src/components/query-suggestions/query-suggestions.component.ts +66 -0
- package/src/components/range-slider/range-slider.component.hbs +9 -0
- package/src/components/range-slider/range-slider.component.ts +190 -0
- package/src/components/search/search.component.hbs +4 -0
- package/src/components/search/search.component.ts +93 -0
- package/src/components/search-results/search-results.component.hbs +29 -0
- package/src/components/search-results/search-results.component.ts +17 -0
- package/src/components/search-results-item/search-results-item.component.hbs +23 -0
- package/src/components/search-results-item/search-results-item.component.ts +99 -0
- package/src/components/search-results-list/search-results-list.component.hbs +15 -0
- package/src/components/search-results-list/search-results-list.component.ts +24 -0
- package/src/components/selected-facets/selected-facets.component.hbs +11 -0
- package/src/components/selected-facets/selected-facets.component.ts +81 -0
- package/src/components/sorting/sorting.component.hbs +7 -0
- package/src/components/sorting/sorting.component.ts +33 -0
- package/src/components/tabs/tabs.component.hbs +7 -0
- package/src/components/tabs/tabs.component.ts +35 -0
- package/src/components/tooltip/tooltip.component.hbs +6 -0
- package/src/components/tooltip/tooltip.component.ts +48 -0
- package/src/helpers/formatters.ts +15 -0
- package/src/helpers/index.ts +1 -0
- package/src/index.ts +125 -0
- package/src/models/autocomplete.models.ts +71 -0
- package/src/models/component.models.ts +27 -0
- package/src/models/global.models.ts +78 -0
- package/src/models/index.ts +7 -0
- package/src/models/raw-autocomplete.models.ts +46 -0
- package/src/models/raw-search.models.ts +130 -0
- package/src/models/search.models.ts +144 -0
- package/src/models/tracking.models.ts +33 -0
- package/src/scss/components/_autocomplete.scss +55 -0
- package/src/scss/components/_facets.scss +287 -0
- package/src/scss/components/_icons.scss +17 -0
- package/src/scss/components/_modified-query.scss +11 -0
- package/src/scss/components/_page-size.scss +2 -0
- package/src/scss/components/_pagination.scss +23 -0
- package/src/scss/components/_query-suggestions.scss +7 -0
- package/src/scss/components/_range-slider.scss +50 -0
- package/src/scss/components/_search-results-item.scss +64 -0
- package/src/scss/components/_search.scss +4 -0
- package/src/scss/components/_selected-facets.scss +24 -0
- package/src/scss/components/_sorting.scss +2 -0
- package/src/scss/components/_tabs.scss +51 -0
- package/src/scss/components/_tooltips.scss +29 -0
- package/src/scss/general/_forms.scss +82 -0
- package/src/scss/general/_grid-layout.scss +44 -0
- package/src/scss/general/_images.scss +3 -0
- package/src/scss/general/_typography.scss +15 -0
- package/src/scss/hawksearch.scss +35 -0
- package/src/scss/includes/_breakpoints.scss +51 -0
- package/src/scss/includes/_components.scss +14 -0
- package/src/scss/includes/_general.scss +4 -0
- package/src/scss/includes/_reset.scss +203 -0
- package/src/scss/includes/_utility-classes.scss +85 -0
- package/src/services/autocomplete.service.ts +104 -0
- package/src/services/base.service.ts +93 -0
- package/src/services/index.ts +3 -0
- package/src/services/search.service.ts +754 -0
- package/src/services/tracking.service.ts +136 -0
- package/tsconfig.json +22 -0
- package/typings.d.ts +21 -0
- package/webpack.config.js +75 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { RangeSliderEventData } from '@models';
|
|
2
|
+
import { searchService } from '@services';
|
|
3
|
+
import { BaseFacetComponent } from '../base-facet.component';
|
|
4
|
+
import defaultHtml from './range-slider-facet.component.hbs';
|
|
5
|
+
|
|
6
|
+
export class RangeSliderFacetComponent extends BaseFacetComponent {
|
|
7
|
+
protected customTemplatePropertyName = 'rangeSliderFacet';
|
|
8
|
+
protected defaultHtml = defaultHtml;
|
|
9
|
+
|
|
10
|
+
protected getContentHtml(template: HandlebarsTemplateDelegate): string {
|
|
11
|
+
if (!this.data || this.data.rangeMin === undefined || this.data.rangeMax === undefined) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const range = this.data.rangeMax - this.data.rangeMin;
|
|
16
|
+
const minValue = Math.floor(this.data.rangeStart || this.data.rangeMin || 0);
|
|
17
|
+
const maxValue = Math.ceil(this.data.rangeEnd || this.data.rangeMax || 0);
|
|
18
|
+
|
|
19
|
+
const html = template({
|
|
20
|
+
id: this.data.id,
|
|
21
|
+
field: this.data.field,
|
|
22
|
+
minValue: minValue,
|
|
23
|
+
maxValue: maxValue,
|
|
24
|
+
sliderMin: Math.floor(((minValue - this.data.rangeMin ?? 0) / range) * 100),
|
|
25
|
+
sliderMax: Math.ceil((maxValue / range) * 100)
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return html;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected bindChildElements(): void {
|
|
32
|
+
this.shadowRoot?.querySelectorAll('[hawksearch-min]').forEach((e) => {
|
|
33
|
+
e.addEventListener('blur', (event: Event) => {
|
|
34
|
+
const element = event.currentTarget as HTMLInputElement;
|
|
35
|
+
let value = parseFloat(element.value);
|
|
36
|
+
|
|
37
|
+
if (isNaN(value)) {
|
|
38
|
+
value = 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (value != this.data!.rangeStart && value <= this.data!.rangeEnd!) {
|
|
42
|
+
this.setFacetValue(value, this.data!.rangeEnd!);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.shadowRoot?.querySelectorAll('[hawksearch-max]').forEach((e) => {
|
|
48
|
+
e.addEventListener('blur', (event: Event) => {
|
|
49
|
+
const element = event.currentTarget as HTMLInputElement;
|
|
50
|
+
let value = parseFloat(element.value);
|
|
51
|
+
|
|
52
|
+
if (isNaN(value)) {
|
|
53
|
+
value = 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (value != this.data!.rangeEnd && value >= this.data!.rangeStart!) {
|
|
57
|
+
this.setFacetValue(this.data!.rangeStart!, value);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.shadowRoot?.querySelectorAll('hawksearch-range-slider').forEach((e) => {
|
|
63
|
+
e.addEventListener('hawksearch:range-slider-dragging', ((event: CustomEvent): void => {
|
|
64
|
+
const data: RangeSliderEventData = event.detail;
|
|
65
|
+
const range = this.getRange(data.min, data.max);
|
|
66
|
+
|
|
67
|
+
this.updateInputElements(range.min, range.max);
|
|
68
|
+
}) as EventListener);
|
|
69
|
+
|
|
70
|
+
e.addEventListener('hawksearch:range-slider-changed', ((event: CustomEvent): void => {
|
|
71
|
+
const data: RangeSliderEventData = event.detail;
|
|
72
|
+
const range = this.getRange(data.min, data.max);
|
|
73
|
+
|
|
74
|
+
this.setFacetValue(range.min, range.max);
|
|
75
|
+
}) as EventListener);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private getRange(sliderMin: number, sliderMax: number): { min: number; max: number } {
|
|
80
|
+
const rangeMin = Math.floor(this.data?.rangeMin ?? 0);
|
|
81
|
+
const rangeMax = Math.ceil(this.data?.rangeMax ?? 0);
|
|
82
|
+
const range = rangeMax - rangeMin;
|
|
83
|
+
const min = Math.floor((range * sliderMin) / 100 + rangeMin);
|
|
84
|
+
const max = Math.ceil((rangeMax * sliderMax) / 100);
|
|
85
|
+
|
|
86
|
+
return { min: min, max: max };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private updateInputElements(min: number, max: number): void {
|
|
90
|
+
const minElement = this.shadowRoot?.querySelector('[hawksearch-min]') as HTMLInputElement | null;
|
|
91
|
+
const maxElement = this.shadowRoot?.querySelector('[hawksearch-max]') as HTMLInputElement | null;
|
|
92
|
+
|
|
93
|
+
if (minElement) {
|
|
94
|
+
minElement.value = min.toString();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (maxElement) {
|
|
98
|
+
maxElement.value = max.toString();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private setFacetValue(min: number, max: number): void {
|
|
103
|
+
this.updateInputElements(min, max);
|
|
104
|
+
|
|
105
|
+
searchService.setFacetValue(this.data!.field!, `${min.toFixed(2)},${max.toFixed(2)}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { searchService } from '@services';
|
|
2
|
+
import { BaseFacetComponent } from '../base-facet.component';
|
|
3
|
+
import defaultHtml from './search-within-facet.component.hbs';
|
|
4
|
+
|
|
5
|
+
export class SearchWithinFacetComponent extends BaseFacetComponent {
|
|
6
|
+
protected customTemplatePropertyName = 'searchWithinFacet';
|
|
7
|
+
protected defaultHtml = defaultHtml;
|
|
8
|
+
|
|
9
|
+
protected getContentHtml(template: HandlebarsTemplateDelegate): string {
|
|
10
|
+
if (!this.data) {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const html = template({
|
|
15
|
+
value: this.data.searchWithin ?? ''
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return html;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected bindChildElements(): void {
|
|
22
|
+
this.shadowRoot?.querySelectorAll('[hawksearch-input]').forEach((e) => {
|
|
23
|
+
e.addEventListener('change', (event: Event) => {
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
|
|
26
|
+
const element = event.currentTarget as HTMLInputElement;
|
|
27
|
+
const value = element.value.trim();
|
|
28
|
+
|
|
29
|
+
searchService.searchWithin(value);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<div class='size-facet'>
|
|
2
|
+
<div class='row row--tight size-facet__items'>
|
|
3
|
+
{{#each values}}
|
|
4
|
+
{{#if visible}}
|
|
5
|
+
<div class='column column--3 size-facet__item{{attribute ' size-facet__item--selected' selected}}'>
|
|
6
|
+
<div hawksearch-facet-value='{{value}}' class='size-facet__item__outer'>
|
|
7
|
+
<div hawksearch-facet-value='{{value}}' class='size-facet__item__inner'>
|
|
8
|
+
{{title}}
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
<span class='size-facet__item__actions'>
|
|
12
|
+
{{#if excluded}}
|
|
13
|
+
<span hawksearch-facet-value-include='{{value}}' class='size-facet__item__actions__item' title='Include'>
|
|
14
|
+
<hawksearch-icon name='plus'></hawksearch-icon>
|
|
15
|
+
</span>
|
|
16
|
+
{{else}}
|
|
17
|
+
<span hawksearch-facet-value-exclude='{{value}}' class='size-facet__item__actions__item' title='Exclude'>
|
|
18
|
+
<hawksearch-icon name='minus'></hawksearch-icon>
|
|
19
|
+
</span>
|
|
20
|
+
{{/if}}
|
|
21
|
+
</span>
|
|
22
|
+
</div>
|
|
23
|
+
{{/if}}
|
|
24
|
+
{{/each}}
|
|
25
|
+
</div>
|
|
26
|
+
{{#if showToggle}}
|
|
27
|
+
<a hawksearch-facet-toggle class='facet__toggle'>{{toggleText}}</a>
|
|
28
|
+
{{/if}}
|
|
29
|
+
</div>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BaseFacetComponent } from '../base-facet.component';
|
|
2
|
+
import defaultHtml from './size-facet.component.hbs';
|
|
3
|
+
|
|
4
|
+
export class SizeFacetComponent extends BaseFacetComponent {
|
|
5
|
+
protected customTemplatePropertyName = 'colorFacet';
|
|
6
|
+
protected defaultHtml = defaultHtml;
|
|
7
|
+
|
|
8
|
+
protected getContentHtml(template: HandlebarsTemplateDelegate): string {
|
|
9
|
+
if (!this.data) {
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const filterRegex = this.state.filter ? new RegExp(this.state.filter, 'i') : undefined;
|
|
14
|
+
|
|
15
|
+
const html = template({
|
|
16
|
+
values:
|
|
17
|
+
this.data.values?.map((v, i) => ({
|
|
18
|
+
...v,
|
|
19
|
+
visible:
|
|
20
|
+
(!filterRegex && (this.state.toggled || !this.data!.truncateThreshold || i < this.data!.truncateThreshold!)) ||
|
|
21
|
+
(!!filterRegex && filterRegex.test(v.title ?? ''))
|
|
22
|
+
})) ?? [],
|
|
23
|
+
showToggle: !!this.data.truncateThreshold && (this.data.values?.length ?? 0) > this.data.truncateThreshold,
|
|
24
|
+
toggleText: this.state.toggled ? 'Show fewer' : 'Show more',
|
|
25
|
+
expanded: this.state.toggled
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return html;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<div class='facet'>
|
|
2
|
+
<div hawksearch-facet-heading class='facet__heading{{attribute " facet__heading--collapsible" collapsible}}'>
|
|
3
|
+
{{title}}
|
|
4
|
+
{{#if tooltip}}
|
|
5
|
+
<hawksearch-tooltip text='{{tooltip}}'></hawksearch-tooltip>
|
|
6
|
+
{{/if}}
|
|
7
|
+
{{#if collapsible}}
|
|
8
|
+
<hawksearch-icon name='{{if-else collapsed "chevron-right" "chevron-down"}}' size='1.5em' class='facet__heading__toggle'></hawksearch-icon>
|
|
9
|
+
{{/if}}
|
|
10
|
+
</div>
|
|
11
|
+
{{#unless collapsed}}
|
|
12
|
+
<div class='facet__content'>
|
|
13
|
+
{{#if searchable}}
|
|
14
|
+
<input type='text' placeholder='Quick Lookup' hawksearch-facet-search value='{{filter}}' class='facet__search' />
|
|
15
|
+
{{/if}}
|
|
16
|
+
{{#if (eq type 'checkbox')}}
|
|
17
|
+
<hawksearch-checkbox-list-facet hawksearch-facet></hawksearch-checkbox-list-facet>
|
|
18
|
+
{{/if}}
|
|
19
|
+
{{#if (eq type 'color')}}
|
|
20
|
+
<hawksearch-color-facet hawksearch-facet></hawksearch-color-facet>
|
|
21
|
+
{{/if}}
|
|
22
|
+
{{#if (eq type 'link')}}
|
|
23
|
+
<hawksearch-link-list-facet hawksearch-facet></hawksearch-link-list-facet>
|
|
24
|
+
{{/if}}
|
|
25
|
+
{{#if (eq type 'range-slider')}}
|
|
26
|
+
<hawksearch-range-slider-facet hawksearch-facet></hawksearch-range-slider-facet>
|
|
27
|
+
{{/if}}
|
|
28
|
+
{{#if (eq type 'search')}}
|
|
29
|
+
<hawksearch-search-within-facet hawksearch-facet></hawksearch-search-within-facet>
|
|
30
|
+
{{/if}}
|
|
31
|
+
{{#if (eq type 'size')}}
|
|
32
|
+
<hawksearch-size-facet hawksearch-facet></hawksearch-size-facet>
|
|
33
|
+
{{/if}}
|
|
34
|
+
</div>
|
|
35
|
+
{{/unless}}
|
|
36
|
+
</div>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Facet, FacetState } from '@models';
|
|
2
|
+
import { BaseComponent } from '../base.component';
|
|
3
|
+
import { BaseFacetComponent } from '../facet-types/base-facet.component';
|
|
4
|
+
import defaultHtml from './facet-wrapper.component.hbs';
|
|
5
|
+
|
|
6
|
+
export class FacetWrapperComponent extends BaseComponent<Facet> {
|
|
7
|
+
protected bindEvent = undefined;
|
|
8
|
+
protected customTemplatePropertyName = 'facetWrapper';
|
|
9
|
+
protected defaultHtml = defaultHtml;
|
|
10
|
+
|
|
11
|
+
state!: FacetState;
|
|
12
|
+
|
|
13
|
+
protected getContentHtml(template: HandlebarsTemplateDelegate): string {
|
|
14
|
+
if (!this.data) {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const html = template({
|
|
19
|
+
...this.data,
|
|
20
|
+
collapsed: this.state.collapsed ?? this.data.collapsed ?? false,
|
|
21
|
+
filter: this.state.filter ?? ''
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return html;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected bindChildElements(): void {
|
|
28
|
+
this.shadowRoot?.querySelectorAll('[hawksearch-facet-heading]').forEach((e) => {
|
|
29
|
+
e.addEventListener('click', (event: Event) => {
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
|
|
32
|
+
if (this.data?.collapsible) {
|
|
33
|
+
this.state.collapsed = !this.state.collapsed;
|
|
34
|
+
|
|
35
|
+
this.renderContent();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
this.shadowRoot?.querySelectorAll('[hawksearch-facet-search]').forEach((e) => {
|
|
41
|
+
e.addEventListener('keyup', (event: Event) => {
|
|
42
|
+
const element = event.currentTarget as HTMLInputElement;
|
|
43
|
+
const value = element.value?.trim();
|
|
44
|
+
|
|
45
|
+
this.state.filter = value ? value : undefined;
|
|
46
|
+
|
|
47
|
+
this.rebindChildElements();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.shadowRoot?.querySelectorAll<BaseFacetComponent>('[hawksearch-facet]').forEach((component) => {
|
|
52
|
+
component.data = this.data;
|
|
53
|
+
component.state = this.state;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private rebindChildElements(): void {
|
|
58
|
+
this.shadowRoot?.querySelectorAll<BaseFacetComponent>('[hawksearch-facet]').forEach((component) => {
|
|
59
|
+
component.renderContent();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<div class='facets{{attribute " facets--expanded" expanded}}'>
|
|
2
|
+
<div hawksearch-mobile-toggle class='facets__heading'>
|
|
3
|
+
Narrow Results
|
|
4
|
+
<hawksearch-icon name='{{if-else expanded "chevron-down" "chevron-right"}}' size='1.5rem' class='facets__heading__mobile-toggle'></hawksearch-icon>
|
|
5
|
+
</div>
|
|
6
|
+
<div class='facets__content'>
|
|
7
|
+
{{#each facets}}
|
|
8
|
+
<hawksearch-facet-wrapper hawksearch-facet-id='{{id}}'></hawksearch-facet-wrapper>
|
|
9
|
+
{{/each}}
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Facet, FacetState, FacetType, FacetValue } from '@models';
|
|
2
|
+
import { BaseComponent } from '../base.component';
|
|
3
|
+
import { FacetWrapperComponent } from '../facet-wrapper/facet-wrapper.component';
|
|
4
|
+
import defaultHtml from './facets-list.component.hbs';
|
|
5
|
+
|
|
6
|
+
export class FacetsListComponent extends BaseComponent<Array<Facet>> {
|
|
7
|
+
protected bindEvent = 'hawksearch:bind-facets-list';
|
|
8
|
+
protected customTemplatePropertyName = 'facetsList';
|
|
9
|
+
protected defaultHtml = defaultHtml;
|
|
10
|
+
|
|
11
|
+
private readonly state: {
|
|
12
|
+
[key: number]: FacetState;
|
|
13
|
+
expanded: boolean;
|
|
14
|
+
} = {
|
|
15
|
+
expanded: false
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
protected getContentHtml(template: HandlebarsTemplateDelegate): string {
|
|
19
|
+
if (!this.data) {
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const supportedTypes: Array<FacetType> = ['checkbox', 'color', 'link', 'range-slider', 'search', 'size'];
|
|
24
|
+
|
|
25
|
+
const html = template({
|
|
26
|
+
facets: this.data.filter((f) => supportedTypes.includes(f.type)),
|
|
27
|
+
expanded: this.state.expanded
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return html;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected bindChildElements(): void {
|
|
34
|
+
this.shadowRoot?.querySelectorAll('[hawksearch-mobile-toggle]').forEach((e) => {
|
|
35
|
+
e.addEventListener('click', (event: Event) => {
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
|
|
38
|
+
this.state.expanded = !this.state.expanded;
|
|
39
|
+
|
|
40
|
+
this.renderContent();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.shadowRoot?.querySelectorAll<FacetWrapperComponent>('hawksearch-facet-wrapper[hawksearch-facet-id]').forEach((component) => {
|
|
45
|
+
const id = parseInt(component.getAttribute('hawksearch-facet-id') ?? '');
|
|
46
|
+
const facet = this.data?.find((f) => f.id === id);
|
|
47
|
+
|
|
48
|
+
let facetValues: Array<string> | undefined;
|
|
49
|
+
|
|
50
|
+
if (facet?.values) {
|
|
51
|
+
facetValues = [];
|
|
52
|
+
|
|
53
|
+
const addValues = (values: Array<FacetValue> | undefined): void => {
|
|
54
|
+
values?.forEach((v) => {
|
|
55
|
+
if (v.value) {
|
|
56
|
+
facetValues!.push(v.value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
addValues(v.children);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
addValues(facet.values);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let facetState = this.state[id];
|
|
67
|
+
|
|
68
|
+
if (!facetState) {
|
|
69
|
+
facetState = {
|
|
70
|
+
id: id,
|
|
71
|
+
toggled: false,
|
|
72
|
+
values: {}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
this.state[id] = facetState;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
facetValues?.forEach((v) => {
|
|
79
|
+
const state = facetState.values![v];
|
|
80
|
+
|
|
81
|
+
if (!state) {
|
|
82
|
+
facetState.values![v] = {
|
|
83
|
+
toggled: false
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
component.data = facet;
|
|
89
|
+
component.state = facetState;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import iconsUrl from '@assets/images/icons.svg';
|
|
2
|
+
import { IconName } from '@models';
|
|
3
|
+
import { BaseComponent } from '../base.component';
|
|
4
|
+
import defaultHtml from './icon.component.hbs';
|
|
5
|
+
|
|
6
|
+
export class IconComponent extends BaseComponent<never> {
|
|
7
|
+
protected bindEvent = undefined;
|
|
8
|
+
protected customTemplatePropertyName = 'icon';
|
|
9
|
+
protected defaultHtml = defaultHtml;
|
|
10
|
+
|
|
11
|
+
private get size(): string {
|
|
12
|
+
const size = this.getAttribute('size');
|
|
13
|
+
|
|
14
|
+
if (!size) {
|
|
15
|
+
return '1em';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (isNaN(Number(size))) {
|
|
19
|
+
return size;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return `${size}px`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected getContentHtml(template: HandlebarsTemplateDelegate): string {
|
|
26
|
+
const name = this.getAttribute('name') as IconName | null;
|
|
27
|
+
|
|
28
|
+
if (!name) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const html = template({
|
|
33
|
+
url: `${iconsUrl}#${name}`,
|
|
34
|
+
height: this.size,
|
|
35
|
+
width: this.size
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return html;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from './autocomplete/autocomplete.component';
|
|
2
|
+
export * from './facet-types';
|
|
3
|
+
export * from './facet-wrapper/facet-wrapper.component';
|
|
4
|
+
export * from './facets-list/facets-list.component';
|
|
5
|
+
export * from './icon/icon.component';
|
|
6
|
+
export * from './modified-query/modified-query.component';
|
|
7
|
+
export * from './page-size/page-size.component';
|
|
8
|
+
export * from './pagination/pagination.component';
|
|
9
|
+
export * from './query-suggestions/query-suggestions.component';
|
|
10
|
+
export * from './range-slider/range-slider.component';
|
|
11
|
+
export * from './search/search.component';
|
|
12
|
+
export * from './search-results-item/search-results-item.component';
|
|
13
|
+
export * from './search-results-list/search-results-list.component';
|
|
14
|
+
export * from './search-results/search-results.component';
|
|
15
|
+
export * from './selected-facets/selected-facets.component';
|
|
16
|
+
export * from './sorting/sorting.component';
|
|
17
|
+
export * from './tabs/tabs.component';
|
|
18
|
+
export * from './tooltip/tooltip.component';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ModifiedQueryData } from '@models';
|
|
2
|
+
import { searchService } from '@services';
|
|
3
|
+
import { BaseComponent } from '../base.component';
|
|
4
|
+
import defaultHtml from './modified-query.component.hbs';
|
|
5
|
+
|
|
6
|
+
export class ModifiedQueryComponent extends BaseComponent<ModifiedQueryData> {
|
|
7
|
+
protected bindEvent = 'hawksearch:bind-modified-query';
|
|
8
|
+
protected customTemplatePropertyName = 'modifiedQuery';
|
|
9
|
+
protected defaultHtml = defaultHtml;
|
|
10
|
+
|
|
11
|
+
protected getContentHtml(template: HandlebarsTemplateDelegate): string {
|
|
12
|
+
if (!this.data || !this.data.display || !this.data.query || !this.data.modifiedQuery) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const html = template({
|
|
17
|
+
query: this.data.query,
|
|
18
|
+
modifiedQuery: this.data.modifiedQuery
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return html;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected bindChildElements(): void {
|
|
25
|
+
this.shadowRoot?.querySelectorAll('[hawksearch-query]').forEach((e) => {
|
|
26
|
+
e.addEventListener('click', (event: Event) => {
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
|
|
29
|
+
const element = event.currentTarget as HTMLElement;
|
|
30
|
+
const query = element.getAttribute('hawksearch-query');
|
|
31
|
+
|
|
32
|
+
if (query) {
|
|
33
|
+
searchService.query(query, true);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { PaginationOption } from '@models';
|
|
2
|
+
import { searchService } from '@services';
|
|
3
|
+
import { BaseComponent } from '../base.component';
|
|
4
|
+
import defaultHtml from './page-size.component.hbs';
|
|
5
|
+
|
|
6
|
+
export class PageSizeComponent extends BaseComponent<Array<PaginationOption>> {
|
|
7
|
+
protected bindEvent = 'hawksearch:bind-page-size';
|
|
8
|
+
protected customTemplatePropertyName = 'pageSize';
|
|
9
|
+
protected defaultHtml = defaultHtml;
|
|
10
|
+
|
|
11
|
+
protected getContentHtml(template: HandlebarsTemplateDelegate): string {
|
|
12
|
+
if (!this.data?.length) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const html = template({
|
|
17
|
+
options: this.data
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return html;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
protected bindChildElements(): void {
|
|
24
|
+
this.shadowRoot?.querySelectorAll('[hawksearch-page-size]').forEach((e) => {
|
|
25
|
+
e.addEventListener('change', (event: Event) => {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
|
|
28
|
+
const element = event.currentTarget as HTMLSelectElement;
|
|
29
|
+
const pageSize = parseInt(element.value);
|
|
30
|
+
|
|
31
|
+
searchService.setPageSize(pageSize);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<div class='pagination'>
|
|
2
|
+
{{#if summary}}
|
|
3
|
+
<div class='pagination__summary'>{{summary}}</div>
|
|
4
|
+
{{/if}}
|
|
5
|
+
<div class='pagination__pages'>
|
|
6
|
+
{{#if displayFirstLink}}
|
|
7
|
+
<a hawksearch-page='1' class='pagination__page pagination__page--first' title='First'>
|
|
8
|
+
<hawksearch-icon name='chevron-back' size='1.2em'></hawksearch-icon>
|
|
9
|
+
</a>
|
|
10
|
+
{{/if}}
|
|
11
|
+
{{#if displayPreviousLink}}
|
|
12
|
+
<a hawksearch-page='{{subtract page 1}}' class='pagination__page pagination__page--previous' title='Previous'>
|
|
13
|
+
<hawksearch-icon name='chevron-left' size='1.2em'></hawksearch-icon>
|
|
14
|
+
</a>
|
|
15
|
+
{{/if}}
|
|
16
|
+
{{#each pages}}
|
|
17
|
+
{{#if (eq this @root.page)}}
|
|
18
|
+
<span class='pagination__page pagination__page--selected'>{{this}}</span>
|
|
19
|
+
{{else}}
|
|
20
|
+
<a hawksearch-page='{{this}}' class='pagination__page'>{{this}}</a>
|
|
21
|
+
{{/if}}
|
|
22
|
+
{{/each}}
|
|
23
|
+
{{#if displayNextLink}}
|
|
24
|
+
<a hawksearch-page='{{add page 1}}' class='pagination__page pagination__page--next' title='Next'>
|
|
25
|
+
<hawksearch-icon name='chevron-right' size='1.2em'></hawksearch-icon>
|
|
26
|
+
</a>
|
|
27
|
+
{{/if}}
|
|
28
|
+
{{#if displayLastLink}}
|
|
29
|
+
<a hawksearch-page='{{totalPages}}' class='pagination__page pagination__page--last' title='Last'>
|
|
30
|
+
<hawksearch-icon name='chevron-forward' size='1.2em'></hawksearch-icon>
|
|
31
|
+
</a>
|
|
32
|
+
{{/if}}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|