@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.
Files changed (118) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.json +153 -0
  3. package/.husky/pre-commit +5 -0
  4. package/.lintstagedrc.json +4 -0
  5. package/.prettierignore +7 -0
  6. package/.prettierrc.json +19 -0
  7. package/.vscode/launch.json +15 -0
  8. package/custom1.html +52 -0
  9. package/custom2.html +44 -0
  10. package/gulpfile.js +73 -0
  11. package/index.html +39 -0
  12. package/package.json +57 -0
  13. package/src/assets/icons/chevron-back.svg +4 -0
  14. package/src/assets/icons/chevron-down.svg +3 -0
  15. package/src/assets/icons/chevron-forward.svg +4 -0
  16. package/src/assets/icons/chevron-left.svg +3 -0
  17. package/src/assets/icons/chevron-right.svg +3 -0
  18. package/src/assets/icons/chevron-up.svg +3 -0
  19. package/src/assets/icons/cross.svg +3 -0
  20. package/src/assets/icons/help.svg +4 -0
  21. package/src/assets/icons/minus.svg +3 -0
  22. package/src/assets/icons/plus.svg +7 -0
  23. package/src/assets/images/icons.svg +1 -0
  24. package/src/assets/images/placeholder.png +0 -0
  25. package/src/components/autocomplete/autocomplete.component.hbs +60 -0
  26. package/src/components/autocomplete/autocomplete.component.ts +124 -0
  27. package/src/components/base.component.ts +120 -0
  28. package/src/components/facet-types/base-facet.component.ts +114 -0
  29. package/src/components/facet-types/checkbox-list-facet/checkbox-list-facet.component.hbs +6 -0
  30. package/src/components/facet-types/checkbox-list-facet/checkbox-list-facet.component.ts +73 -0
  31. package/src/components/facet-types/checkbox-list-facet/checkbox-list.partial.hbs +42 -0
  32. package/src/components/facet-types/color-facet/color-facet.component.hbs +29 -0
  33. package/src/components/facet-types/color-facet/color-facet.component.ts +26 -0
  34. package/src/components/facet-types/index.ts +6 -0
  35. package/src/components/facet-types/linked-list-facet/link-list-facet.component.hbs +17 -0
  36. package/src/components/facet-types/linked-list-facet/link-list-facet.component.ts +32 -0
  37. package/src/components/facet-types/range-slider-facet/range-slider-facet.component.hbs +11 -0
  38. package/src/components/facet-types/range-slider-facet/range-slider-facet.component.ts +107 -0
  39. package/src/components/facet-types/search-within-facet/search-within-facet.component.hbs +3 -0
  40. package/src/components/facet-types/search-within-facet/search-within-facet.component.ts +33 -0
  41. package/src/components/facet-types/size-facet/size-facet.component.hbs +29 -0
  42. package/src/components/facet-types/size-facet/size-facet.component.ts +30 -0
  43. package/src/components/facet-wrapper/facet-wrapper.component.hbs +36 -0
  44. package/src/components/facet-wrapper/facet-wrapper.component.ts +62 -0
  45. package/src/components/facets-list/facets-list.component.hbs +11 -0
  46. package/src/components/facets-list/facets-list.component.ts +92 -0
  47. package/src/components/icon/icon.component.hbs +3 -0
  48. package/src/components/icon/icon.component.ts +40 -0
  49. package/src/components/index.ts +18 -0
  50. package/src/components/modified-query/modified-query.component.hbs +7 -0
  51. package/src/components/modified-query/modified-query.component.ts +38 -0
  52. package/src/components/page-size/page-size.component.hbs +7 -0
  53. package/src/components/page-size/page-size.component.ts +35 -0
  54. package/src/components/pagination/pagination.component.hbs +34 -0
  55. package/src/components/pagination/pagination.component.ts +85 -0
  56. package/src/components/query-suggestions/query-suggestions.component.hbs +3 -0
  57. package/src/components/query-suggestions/query-suggestions.component.ts +66 -0
  58. package/src/components/range-slider/range-slider.component.hbs +9 -0
  59. package/src/components/range-slider/range-slider.component.ts +190 -0
  60. package/src/components/search/search.component.hbs +4 -0
  61. package/src/components/search/search.component.ts +93 -0
  62. package/src/components/search-results/search-results.component.hbs +29 -0
  63. package/src/components/search-results/search-results.component.ts +17 -0
  64. package/src/components/search-results-item/search-results-item.component.hbs +23 -0
  65. package/src/components/search-results-item/search-results-item.component.ts +99 -0
  66. package/src/components/search-results-list/search-results-list.component.hbs +15 -0
  67. package/src/components/search-results-list/search-results-list.component.ts +24 -0
  68. package/src/components/selected-facets/selected-facets.component.hbs +11 -0
  69. package/src/components/selected-facets/selected-facets.component.ts +81 -0
  70. package/src/components/sorting/sorting.component.hbs +7 -0
  71. package/src/components/sorting/sorting.component.ts +33 -0
  72. package/src/components/tabs/tabs.component.hbs +7 -0
  73. package/src/components/tabs/tabs.component.ts +35 -0
  74. package/src/components/tooltip/tooltip.component.hbs +6 -0
  75. package/src/components/tooltip/tooltip.component.ts +48 -0
  76. package/src/helpers/formatters.ts +15 -0
  77. package/src/helpers/index.ts +1 -0
  78. package/src/index.ts +125 -0
  79. package/src/models/autocomplete.models.ts +71 -0
  80. package/src/models/component.models.ts +27 -0
  81. package/src/models/global.models.ts +78 -0
  82. package/src/models/index.ts +7 -0
  83. package/src/models/raw-autocomplete.models.ts +46 -0
  84. package/src/models/raw-search.models.ts +130 -0
  85. package/src/models/search.models.ts +144 -0
  86. package/src/models/tracking.models.ts +33 -0
  87. package/src/scss/components/_autocomplete.scss +55 -0
  88. package/src/scss/components/_facets.scss +287 -0
  89. package/src/scss/components/_icons.scss +17 -0
  90. package/src/scss/components/_modified-query.scss +11 -0
  91. package/src/scss/components/_page-size.scss +2 -0
  92. package/src/scss/components/_pagination.scss +23 -0
  93. package/src/scss/components/_query-suggestions.scss +7 -0
  94. package/src/scss/components/_range-slider.scss +50 -0
  95. package/src/scss/components/_search-results-item.scss +64 -0
  96. package/src/scss/components/_search.scss +4 -0
  97. package/src/scss/components/_selected-facets.scss +24 -0
  98. package/src/scss/components/_sorting.scss +2 -0
  99. package/src/scss/components/_tabs.scss +51 -0
  100. package/src/scss/components/_tooltips.scss +29 -0
  101. package/src/scss/general/_forms.scss +82 -0
  102. package/src/scss/general/_grid-layout.scss +44 -0
  103. package/src/scss/general/_images.scss +3 -0
  104. package/src/scss/general/_typography.scss +15 -0
  105. package/src/scss/hawksearch.scss +35 -0
  106. package/src/scss/includes/_breakpoints.scss +51 -0
  107. package/src/scss/includes/_components.scss +14 -0
  108. package/src/scss/includes/_general.scss +4 -0
  109. package/src/scss/includes/_reset.scss +203 -0
  110. package/src/scss/includes/_utility-classes.scss +85 -0
  111. package/src/services/autocomplete.service.ts +104 -0
  112. package/src/services/base.service.ts +93 -0
  113. package/src/services/index.ts +3 -0
  114. package/src/services/search.service.ts +754 -0
  115. package/src/services/tracking.service.ts +136 -0
  116. package/tsconfig.json +22 -0
  117. package/typings.d.ts +21 -0
  118. package/webpack.config.js +75 -0
@@ -0,0 +1,124 @@
1
+ import placeholderUrl from '@assets/images/placeholder.png';
2
+ import { AutocompleteItemType, AutocompleteResponse, HawksearchGlobal, SelectedFacets } from '@models';
3
+ import { searchService, trackingService } from '@services';
4
+ import { BaseComponent } from '../base.component';
5
+ import defaultHtml from './autocomplete.component.hbs';
6
+
7
+ declare let Hawksearch: HawksearchGlobal;
8
+
9
+ export class AutocompleteComponent extends BaseComponent<AutocompleteResponse> {
10
+ protected bindEvent = 'hawksearch:bind-autocomplete';
11
+ protected customTemplatePropertyName = 'search';
12
+ protected defaultHtml = defaultHtml;
13
+
14
+ protected getContentHtml(template: HandlebarsTemplateDelegate): string {
15
+ if (!this.data || !(this.data.categories?.results?.length || this.data.content?.results?.length || this.data.products?.results?.length)) {
16
+ return '';
17
+ }
18
+
19
+ const html = template({
20
+ ...this.data,
21
+ AutocompleteItemType: AutocompleteItemType
22
+ });
23
+
24
+ return html;
25
+ }
26
+
27
+ protected bindChildElements(): void {
28
+ this.shadowRoot?.querySelectorAll('[hawksearch-image]').forEach((e) => {
29
+ e.addEventListener('error', (event: Event): void => {
30
+ const element = event.currentTarget as HTMLImageElement;
31
+
32
+ element.src = Hawksearch.config.placeholderImageUrl || placeholderUrl;
33
+ });
34
+ });
35
+
36
+ this.shadowRoot?.querySelectorAll('[hawksearch-category-field][hawksearch-category-value]').forEach((e) => {
37
+ e.addEventListener('click', ((event: PointerEvent): void => {
38
+ event.preventDefault();
39
+
40
+ const element = event.currentTarget as HTMLElement;
41
+ const field = element.getAttribute('hawksearch-category-field');
42
+ const value = element.getAttribute('hawksearch-category-value');
43
+ const category = this.data!.categories?.results?.find((r) => r.field === field && r.value === value);
44
+
45
+ if (!category) {
46
+ return;
47
+ }
48
+
49
+ trackingService.trackAutocompleteClick(this.data!.query, AutocompleteItemType.Category, category.title, category.url);
50
+
51
+ const facets: SelectedFacets = {
52
+ [category.field]: [category.value]
53
+ };
54
+
55
+ searchService.modifySearch(undefined, facets);
56
+
57
+ this.close();
58
+ }) as EventListener);
59
+ });
60
+
61
+ this.shadowRoot?.querySelectorAll('[hawksearch-content]').forEach((e) => {
62
+ e.addEventListener('click', ((event: PointerEvent): void => {
63
+ const element = event.currentTarget as HTMLElement;
64
+ const id = element.getAttribute('hawksearch-content');
65
+ const content = this.data!.content?.results?.find((r) => r.id === id);
66
+
67
+ if (!content) {
68
+ return;
69
+ }
70
+
71
+ trackingService.trackAutocompleteClick(this.data!.query, AutocompleteItemType.Content, content.title, content.url);
72
+ }) as EventListener);
73
+ });
74
+
75
+ this.shadowRoot?.querySelectorAll('[hawksearch-product]').forEach((e) => {
76
+ e.addEventListener('click', ((event: PointerEvent): void => {
77
+ const element = event.currentTarget as HTMLElement;
78
+ const id = element.getAttribute('hawksearch-product');
79
+ const product = this.data!.products?.results?.find((r) => r.id === id);
80
+
81
+ if (!product) {
82
+ return;
83
+ }
84
+
85
+ trackingService.trackAutocompleteClick(this.data!.query, AutocompleteItemType.Product, product.title, product.url);
86
+ }) as EventListener);
87
+ });
88
+
89
+ this.shadowRoot?.querySelectorAll('[hawksearch-query]').forEach((e) => {
90
+ e.addEventListener('click', (event: Event) => {
91
+ event.preventDefault();
92
+
93
+ const element = event.currentTarget as HTMLElement;
94
+ const query = element.getAttribute('hawksearch-query');
95
+
96
+ if (!query) {
97
+ return;
98
+ }
99
+
100
+ const queryResult = this.data!.queries?.results.find((q) => q.query === query);
101
+
102
+ if (queryResult) {
103
+ trackingService.trackAutocompleteClick(this.data!.query, AutocompleteItemType.Search, queryResult.query, queryResult.url);
104
+ }
105
+
106
+ searchService.query(query);
107
+ });
108
+ });
109
+
110
+ this.shadowRoot?.querySelectorAll('[hawksearch-view-all]').forEach((e) => {
111
+ e.addEventListener('click', (event: Event) => {
112
+ event.preventDefault();
113
+
114
+ searchService.query(this.data!.query);
115
+ });
116
+ });
117
+ }
118
+
119
+ private close(): void {
120
+ const event = new Event('hawksearch:close-autocomplete');
121
+
122
+ this.dispatchEvent(event);
123
+ }
124
+ }
@@ -0,0 +1,120 @@
1
+ import { HawksearchGlobal } from '../models';
2
+ import hawksearchCss from '../scss/hawksearch.scss';
3
+
4
+ declare let Hawksearch: HawksearchGlobal;
5
+
6
+ export abstract class BaseComponent<TData> extends HTMLElement {
7
+ protected handlebars = Hawksearch.handlebars;
8
+
9
+ protected abstract bindEvent: string | undefined;
10
+ protected abstract customTemplatePropertyName: string | undefined;
11
+ protected abstract defaultHtml: string;
12
+
13
+ data?: TData;
14
+
15
+ get hasCustomTemplate(): boolean {
16
+ return !!this.innerHTML.trim().length;
17
+ }
18
+
19
+ private bindEventHandler?: EventListener;
20
+ private template!: HandlebarsTemplateDelegate;
21
+
22
+ connectedCallback(): void {
23
+ this.attachShadow({
24
+ mode: 'open'
25
+ });
26
+
27
+ this.registerHelpers();
28
+
29
+ const customTemplateHtml = this.getCustomTemplateHtml();
30
+ const templateHtml = customTemplateHtml ?? this.defaultHtml;
31
+
32
+ this.template = this.handlebars.compile(templateHtml);
33
+
34
+ setTimeout(() => {
35
+ this.renderContent();
36
+ });
37
+
38
+ if (this.bindEvent) {
39
+ this.bindEventHandler = ((event: CustomEvent): void => {
40
+ this.data = event.detail;
41
+
42
+ this.renderContent();
43
+ }) as EventListener;
44
+
45
+ addEventListener(this.bindEvent, this.bindEventHandler);
46
+ }
47
+ }
48
+
49
+ disconnectedCallback(): void {
50
+ if (this.bindEvent && this.bindEventHandler) {
51
+ removeEventListener(this.bindEvent, this.bindEventHandler);
52
+ }
53
+ }
54
+
55
+ protected registerHelpers(): void {}
56
+
57
+ public renderContent(): void {
58
+ if (!this.template) {
59
+ return;
60
+ }
61
+
62
+ const elements: Array<Node> = [];
63
+
64
+ if (Hawksearch.config.defaultStyles !== false) {
65
+ const defaultStyles = document.createElement('style');
66
+
67
+ defaultStyles.innerHTML = hawksearchCss;
68
+
69
+ elements.push(defaultStyles);
70
+ }
71
+
72
+ if (Hawksearch.config.customCssUrl) {
73
+ const customStyles = document.createElement('link');
74
+
75
+ customStyles.type = 'text/css';
76
+ customStyles.rel = 'stylesheet';
77
+ customStyles.href = Hawksearch.config.customCssUrl;
78
+
79
+ elements.push(customStyles);
80
+ }
81
+
82
+ const templateElement = document.createElement('template');
83
+
84
+ templateElement.innerHTML = this.getContentHtml(this.template);
85
+
86
+ const content = templateElement.content.cloneNode(true);
87
+
88
+ elements.push(content);
89
+
90
+ this.shadowRoot?.replaceChildren(...elements);
91
+
92
+ this.bindChildElements();
93
+ }
94
+
95
+ protected abstract getContentHtml(template: HandlebarsTemplateDelegate): string;
96
+
97
+ protected bindChildElements(): void {}
98
+
99
+ private getCustomTemplateHtml(): string | undefined {
100
+ let customTemplate: string | undefined;
101
+
102
+ if (this.hasCustomTemplate) {
103
+ customTemplate = this.innerHTML;
104
+ } else {
105
+ if (this.customTemplatePropertyName) {
106
+ customTemplate = (Hawksearch.templates as any)?.[this.customTemplatePropertyName];
107
+ }
108
+
109
+ if (customTemplate) {
110
+ const customTemplateElement = document.getElementById(customTemplate);
111
+
112
+ if (customTemplateElement instanceof HTMLTemplateElement) {
113
+ customTemplate = customTemplateElement.innerHTML;
114
+ }
115
+ }
116
+ }
117
+
118
+ return customTemplate;
119
+ }
120
+ }
@@ -0,0 +1,114 @@
1
+ import { searchService } from '@services';
2
+ import { Facet, FacetState } from '../../models';
3
+ import { BaseComponent } from '../base.component';
4
+
5
+ export abstract class BaseFacetComponent extends BaseComponent<Facet> {
6
+ protected bindEvent = undefined;
7
+
8
+ state!: FacetState;
9
+
10
+ protected bindChildElements(): void {
11
+ this.shadowRoot?.querySelectorAll('input[hawksearch-facet-value]').forEach((e) => {
12
+ e.addEventListener('change', (event: Event) => {
13
+ event.stopPropagation();
14
+
15
+ const element = event.currentTarget as HTMLInputElement;
16
+ const field = this.data!.field!;
17
+ const value = element.value;
18
+
19
+ if (element.checked) {
20
+ searchService.addFacetValue(field, value);
21
+ } else {
22
+ searchService.removeFacetValue(field, value);
23
+ }
24
+ });
25
+ });
26
+
27
+ this.shadowRoot?.querySelectorAll('[hawksearch-facet-value]:not(input)').forEach((e) => {
28
+ e.addEventListener('click', (event: Event) => {
29
+ event.preventDefault();
30
+ event.stopPropagation();
31
+
32
+ const element = event.currentTarget as HTMLElement;
33
+ const value = element.getAttribute('hawksearch-facet-value');
34
+ const facetValue = this.data!.values?.find((v) => v.value === value);
35
+
36
+ if (!facetValue) {
37
+ return;
38
+ }
39
+
40
+ if (facetValue.selected) {
41
+ searchService.removeFacetValue(this.data!.field!, value!);
42
+ } else {
43
+ searchService.addFacetValue(this.data!.field!, value!);
44
+ }
45
+ });
46
+ });
47
+
48
+ this.shadowRoot?.querySelectorAll('[hawksearch-facet-value-include]').forEach((e) => {
49
+ e.addEventListener('click', (event: Event) => {
50
+ event.preventDefault();
51
+ event.stopPropagation();
52
+
53
+ const element = event.currentTarget as HTMLElement;
54
+ const field = this.data!.field!;
55
+
56
+ let value = element.getAttribute('hawksearch-facet-value-include');
57
+
58
+ if (!value) {
59
+ return;
60
+ }
61
+
62
+ value = `@${value}`;
63
+
64
+ searchService.removeFacetValue(field, value);
65
+ });
66
+ });
67
+
68
+ this.shadowRoot?.querySelectorAll('[hawksearch-facet-value-exclude]').forEach((e) => {
69
+ e.addEventListener('click', (event: Event) => {
70
+ event.preventDefault();
71
+ event.stopPropagation();
72
+
73
+ const element = event.currentTarget as HTMLElement;
74
+ const field = this.data!.field!;
75
+ const value = element.getAttribute('hawksearch-facet-value-exclude');
76
+
77
+ if (!value) {
78
+ return;
79
+ }
80
+
81
+ searchService.excludeFacetValue(field, value);
82
+ });
83
+ });
84
+
85
+ this.shadowRoot?.querySelectorAll('[hawksearch-facet-value-toggle]').forEach((e) => {
86
+ e.addEventListener('click', (event: Event) => {
87
+ event.preventDefault();
88
+ event.stopPropagation();
89
+
90
+ const element = event.currentTarget as HTMLElement;
91
+ const value = element.getAttribute('hawksearch-facet-value-toggle');
92
+
93
+ if (!value) {
94
+ return;
95
+ }
96
+
97
+ this.state.values![value].toggled = !this.state.values![value].toggled;
98
+
99
+ this.renderContent();
100
+ });
101
+ });
102
+
103
+ this.shadowRoot?.querySelectorAll('[hawksearch-facet-toggle]').forEach((e) => {
104
+ e.addEventListener('click', (event: Event) => {
105
+ event.preventDefault();
106
+ event.stopPropagation();
107
+
108
+ this.state.toggled = !this.state.toggled;
109
+
110
+ this.renderContent();
111
+ });
112
+ });
113
+ }
114
+ }
@@ -0,0 +1,6 @@
1
+ <div class="checkbox-list-facet">
2
+ {{> facet-checkbox-list values}}
3
+ {{#if showToggle}}
4
+ <a hawksearch-facet-toggle class='facet__toggle'>{{toggleText}}</a>
5
+ {{/if}}
6
+ </div>
@@ -0,0 +1,73 @@
1
+ import { FacetValue } from '@models';
2
+ import { BaseFacetComponent } from '../base-facet.component';
3
+ import defaultHtml from './checkbox-list-facet.component.hbs';
4
+ import checkboxListPartialHtml from './checkbox-list.partial.hbs';
5
+
6
+ interface FacetValueWithState extends FacetValue {
7
+ visible: boolean;
8
+ toggled: boolean;
9
+ hasChildren: boolean;
10
+ children?: Array<FacetValueWithState>;
11
+ }
12
+
13
+ export class CheckboxListFacetComponent extends BaseFacetComponent {
14
+ protected customTemplatePropertyName = 'checkboxListFacet';
15
+ protected defaultHtml = defaultHtml;
16
+
17
+ protected registerHelpers(): void {
18
+ super.registerHelpers();
19
+
20
+ this.handlebars.registerPartial('facet-checkbox-list', checkboxListPartialHtml);
21
+ }
22
+
23
+ protected getContentHtml(template: HandlebarsTemplateDelegate): string {
24
+ if (!this.data || !this.data.values?.length) {
25
+ return '';
26
+ }
27
+
28
+ const filterRegex = this.state.filter ? new RegExp(this.state.filter, 'i') : undefined;
29
+
30
+ const filtered = (value: FacetValue): boolean => {
31
+ if (!filterRegex) {
32
+ return false;
33
+ }
34
+
35
+ return filterRegex.test(value.title);
36
+ };
37
+
38
+ const containsFiltered = (value: FacetValue): boolean => {
39
+ return value.children?.some((v) => filtered(v)) || false;
40
+ };
41
+
42
+ const getValues = (values: Array<FacetValue> | undefined, parent: FacetValueWithState | undefined): Array<FacetValueWithState> | undefined => {
43
+ return values?.map((v, i) => {
44
+ const valueWithState = {
45
+ ...v,
46
+ visible:
47
+ (!parent && !filterRegex && (this.state.toggled || !this.data!.truncateThreshold || i < this.data!.truncateThreshold!)) ||
48
+ filtered(v) ||
49
+ containsFiltered(v) ||
50
+ (!filterRegex && !!parent?.toggled),
51
+ hasChildren: !!v.children?.length,
52
+ children: undefined,
53
+ toggled: (v.value && v.children?.length ? this.state.values?.[v.value!]?.toggled ?? false : false) || containsFiltered(v)
54
+ };
55
+
56
+ (valueWithState as any).children = getValues(v.children, valueWithState);
57
+
58
+ return valueWithState;
59
+ });
60
+ };
61
+
62
+ const html = template({
63
+ id: this.data.id,
64
+ field: this.data.field,
65
+ values: getValues(this.data.values, undefined),
66
+ showToggle: !!this.data.truncateThreshold && (this.data.values?.length ?? 0) > this.data.truncateThreshold && !this.state.filter,
67
+ toggleText: this.state.toggled ? 'Show fewer' : 'Show more',
68
+ expanded: this.state.toggled
69
+ });
70
+
71
+ return html;
72
+ }
73
+ }
@@ -0,0 +1,42 @@
1
+ {{#if length}}
2
+ <ul class='checkbox-list-facet__list'>
3
+ {{#each this}}
4
+ {{#if visible}}
5
+ <li class='checkbox-list-facet__list__item{{attribute ' checkbox-list-facet__list__item--excluded' excluded}}{{attribute ' checkbox-list-facet__list__item--collapsible' hasChildren}}'>
6
+ <div class='checkbox-list-facet__list__item__content'>
7
+ <input
8
+ id='{{@root.id}}-{{value}}'
9
+ type='checkbox'
10
+ {{attribute 'checked' selected}}
11
+ hawksearch-facet-value
12
+ value='{{value}}'
13
+ />
14
+ <label for='{{@root.id}}-{{value}}' class='facet__value'>
15
+ <span class='facet__value__title checkbox-list-facet__list__item__title'>{{title}}</span>
16
+ <span class='facet__value__count checkbox-list-facet__list__item__count'>({{count}})</span>
17
+ </label>
18
+ {{#if toggled}}
19
+ {{> facet-checkbox-list children}}
20
+ {{/if}}
21
+ </div>
22
+ <div class='checkbox-list-facet__list__item__actions'>
23
+ {{#if excluded}}
24
+ <span hawksearch-facet-value-include='{{value}}' class='checkbox-list-facet__list__item__actions__item' title='Include'>
25
+ <hawksearch-icon name='plus' class='facet__heading__actions__item'></hawksearch-icon>
26
+ </span>
27
+ {{else}}
28
+ <span hawksearch-facet-value-exclude='{{value}}' class='checkbox-list-facet__list__item__actions__item' title='Exclude'>
29
+ <hawksearch-icon name='minus' class='facet__heading__actions__item'></hawksearch-icon>
30
+ </span>
31
+ {{/if}}
32
+ {{#if hasChildren}}
33
+ <span hawksearch-facet-value-toggle='{{value}}' class='checkbox-list-facet__list__item__actions__item' title='Toggle'>
34
+ <hawksearch-icon name='{{if-else toggled 'chevron-down' 'chevron-right'}}' class='facet__heading__toggle'></hawksearch-icon>
35
+ </span>
36
+ {{/if}}
37
+ </div>
38
+ </li>
39
+ {{/if}}
40
+ {{/each}}
41
+ </ul>
42
+ {{/if}}
@@ -0,0 +1,29 @@
1
+ <div class='color-facet'>
2
+ <div class='row row--tight color-facet__items'>
3
+ {{#each values}}
4
+ {{#if visible}}
5
+ <div hawksearch-facet-value='{{value}}' class='column column--2 color-facet__item{{attribute ' color-facet__item--selected' selected}}'>
6
+ {{#if color.imageUrl}}
7
+ <img src='{{color.imageUrl}}' class='color-facet__item__image' alt='{{color.name}}' title='{{color.name}}' />
8
+ {{else}}
9
+ <div class='color-facet__item__box' style='background: {{color.hex}};'></div>
10
+ {{/if}}
11
+ <span class='color-facet__item__actions'>
12
+ {{#if excluded}}
13
+ <span hawksearch-facet-value-include='{{value}}' class='color-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='color-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,26 @@
1
+ import { BaseFacetComponent } from '../base-facet.component';
2
+ import defaultHtml from './color-facet.component.hbs';
3
+
4
+ export class ColorFacetComponent 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 html = template({
14
+ values:
15
+ this.data.values?.map((v, i) => ({
16
+ ...v,
17
+ visible: this.state.toggled || !this.data?.truncateThreshold || i < this.data?.truncateThreshold
18
+ })) ?? [],
19
+ showToggle: !!this.data.truncateThreshold && (this.data.values?.length ?? 0) > this.data.truncateThreshold,
20
+ toggleText: this.state.toggled ? 'Show fewer' : 'Show more',
21
+ expanded: this.state.toggled
22
+ });
23
+
24
+ return html;
25
+ }
26
+ }
@@ -0,0 +1,6 @@
1
+ export * from './checkbox-list-facet/checkbox-list-facet.component';
2
+ export * from './color-facet/color-facet.component';
3
+ export * from './linked-list-facet/link-list-facet.component';
4
+ export * from './range-slider-facet/range-slider-facet.component';
5
+ export * from './search-within-facet/search-within-facet.component';
6
+ export * from './size-facet/size-facet.component';
@@ -0,0 +1,17 @@
1
+ <div class='link-list-facet'>
2
+ <ul class='link-list-facet__list'>
3
+ {{#each values}}
4
+ {{#if visible}}
5
+ <li class='link-list-facet__list__item'>
6
+ <a hawksearch-facet-value='{{value}}' class='facet__value'>
7
+ <span class='facet__value__title'>{{title}}</span>
8
+ <span class='facet__value__count'>({{count}})</span>
9
+ </a>
10
+ </li>
11
+ {{/if}}
12
+ {{/each}}
13
+ </ul>
14
+ {{#if showToggle}}
15
+ <a hawksearch-facet-toggle class='facet__toggle'>{{toggleText}}</a>
16
+ {{/if}}
17
+ </div>
@@ -0,0 +1,32 @@
1
+ import { BaseFacetComponent } from '../base-facet.component';
2
+ import defaultHtml from './link-list-facet.component.hbs';
3
+
4
+ export class LinkListFacetComponent extends BaseFacetComponent {
5
+ protected customTemplatePropertyName = 'linkListFacet';
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
+ id: this.data.id,
17
+ field: this.data.field,
18
+ values:
19
+ this.data.values?.map((v, i) => ({
20
+ ...v,
21
+ visible:
22
+ (!filterRegex && (this.state.toggled || !this.data!.truncateThreshold || i < this.data!.truncateThreshold!)) ||
23
+ (!!filterRegex && filterRegex.test(v.title ?? ''))
24
+ })) ?? [],
25
+ showToggle: !!this.data.truncateThreshold && (this.data.values?.length ?? 0) > this.data.truncateThreshold && !this.state.filter,
26
+ toggleText: this.state.toggled ? 'Show fewer' : 'Show more',
27
+ expanded: this.state.toggled
28
+ });
29
+
30
+ return html;
31
+ }
32
+ }
@@ -0,0 +1,11 @@
1
+ <div class='range-slider-facet'>
2
+ <div class='row'>
3
+ <div class='column column--6'>
4
+ <input type='number' hawksearch-min value='{{minValue}}' />
5
+ </div>
6
+ <div class='column column--6'>
7
+ <input type='number' hawksearch-max value='{{maxValue}}' />
8
+ </div>
9
+ </div>
10
+ <hawksearch-range-slider min='{{sliderMin}}' max={{sliderMax}}></hawksearch-range-slider>
11
+ </div>