@miso.ai/client-sdk-ui 1.10.3-beta.0 → 1.10.4-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/dist/cjs/actor/autocomplete.js +4 -1
- package/dist/cjs/actor/data.js +15 -6
- package/dist/cjs/actor/fields.js +10 -10
- package/dist/cjs/actor/filters.js +114 -0
- package/dist/cjs/actor/hub.js +2 -1
- package/dist/cjs/actor/sessions.js +3 -5
- package/dist/cjs/actor/view.js +19 -6
- package/dist/cjs/actor/views.js +32 -19
- package/dist/cjs/constants.js +9 -2
- package/dist/cjs/defaults/ask/constants.js +3 -2
- package/dist/cjs/defaults/ask/templates.js +112 -219
- package/dist/cjs/defaults/common/templates.js +234 -0
- package/dist/cjs/defaults/hybrid-search/answer-box.js +115 -0
- package/dist/cjs/defaults/hybrid-search/constants.js +8 -0
- package/dist/cjs/defaults/hybrid-search/controls.js +17 -0
- package/dist/cjs/defaults/hybrid-search/index.js +31 -0
- package/dist/cjs/defaults/hybrid-search/phrases.js +27 -0
- package/dist/cjs/defaults/hybrid-search/templates.js +127 -0
- package/dist/cjs/defaults/index.js +4 -2
- package/dist/cjs/element/container/index.js +7 -0
- package/dist/cjs/element/container/miso-hybrid-search.js +21 -0
- package/dist/cjs/element/role/index.js +21 -0
- package/dist/cjs/element/role/miso-facets.js +20 -0
- package/dist/cjs/element/role/miso-hits.js +20 -0
- package/dist/cjs/element/role/miso-keywords.js +20 -0
- package/dist/cjs/layout/index.js +7 -0
- package/dist/cjs/layout/input/facets.js +294 -0
- package/dist/cjs/layout/input/option-list.js +4 -4
- package/dist/cjs/layout/input/search-box.js +6 -8
- package/dist/cjs/layout/list/collection.js +1 -2
- package/dist/cjs/layout/raf.js +1 -0
- package/dist/cjs/layout/template.js +3 -1
- package/dist/cjs/layout/templates.js +15 -0
- package/dist/cjs/layout/text/text.js +38 -5
- package/dist/cjs/layout/trackable.js +2 -16
- package/dist/cjs/plugin.js +4 -1
- package/dist/cjs/{source/index.js → source.js} +7 -4
- package/dist/cjs/util/bindings.js +3 -0
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/workflow/answer-based.js +335 -0
- package/dist/cjs/workflow/ask.js +98 -271
- package/dist/cjs/workflow/base.js +69 -35
- package/dist/cjs/workflow/explore.js +10 -0
- package/dist/cjs/workflow/hybrid-search.js +140 -0
- package/dist/cjs/workflow/index.js +7 -0
- package/dist/cjs/workflow/processors.js +113 -0
- package/dist/cjs/workflow/recommendation.js +12 -3
- package/dist/cjs/workflow/search.js +34 -12
- package/package.json +2 -2
- package/src/actor/autocomplete.js +4 -2
- package/src/actor/data.js +8 -2
- package/src/actor/fields.js +10 -10
- package/src/actor/filters.js +103 -0
- package/src/actor/hub.js +2 -1
- package/src/actor/sessions.js +3 -4
- package/src/actor/view.js +19 -2
- package/src/actor/views.js +30 -16
- package/src/constants.js +10 -0
- package/src/defaults/ask/constants.js +1 -0
- package/src/defaults/ask/templates.js +70 -179
- package/src/defaults/common/templates.js +173 -0
- package/src/defaults/hybrid-search/answer-box.js +118 -0
- package/src/defaults/hybrid-search/constants.js +2 -0
- package/src/defaults/hybrid-search/controls.js +11 -0
- package/src/defaults/hybrid-search/index.js +9 -0
- package/src/defaults/hybrid-search/phrases.js +22 -0
- package/src/defaults/hybrid-search/templates.js +83 -0
- package/src/defaults/index.js +2 -0
- package/src/element/container/index.js +1 -0
- package/src/element/container/miso-hybrid-search.js +19 -0
- package/src/element/role/index.js +3 -0
- package/src/element/role/miso-facets.js +16 -0
- package/src/element/role/miso-hits.js +16 -0
- package/src/element/role/miso-keywords.js +16 -0
- package/src/layout/index.js +1 -0
- package/src/layout/input/facets.js +242 -0
- package/src/layout/input/option-list.js +3 -4
- package/src/layout/input/search-box.js +6 -7
- package/src/layout/list/collection.js +1 -2
- package/src/layout/raf.js +1 -0
- package/src/layout/template.js +2 -1
- package/src/layout/templates.js +15 -0
- package/src/layout/text/text.js +28 -3
- package/src/layout/trackable.js +2 -15
- package/src/plugin.js +6 -2
- package/src/{source/index.js → source.js} +7 -4
- package/src/util/bindings.js +4 -0
- package/src/version.js +1 -1
- package/src/workflow/answer-based.js +280 -0
- package/src/workflow/ask.js +82 -204
- package/src/workflow/base.js +64 -27
- package/src/workflow/explore.js +10 -1
- package/src/workflow/hybrid-search.js +125 -0
- package/src/workflow/index.js +1 -0
- package/src/workflow/processors.js +95 -0
- package/src/workflow/recommendation.js +9 -2
- package/src/workflow/search.js +35 -7
- package/dist/cjs/source/api.js +0 -23
- package/src/source/api.js +0 -12
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as _constants from './constants.js';
|
|
2
|
+
import * as _phrases from './phrases.js';
|
|
3
|
+
import * as _templates from './templates.js';
|
|
4
|
+
|
|
5
|
+
export * from './controls.js';
|
|
6
|
+
|
|
7
|
+
export const constants = Object.freeze(_constants);
|
|
8
|
+
export const phrases = Object.freeze(_phrases);
|
|
9
|
+
export const templates = Object.freeze(_templates);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The phrase displayed right before <miso-question>
|
|
3
|
+
*/
|
|
4
|
+
export const question = 'You asked...';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The phrase displayed right before <miso-sources>
|
|
8
|
+
*/
|
|
9
|
+
export const sources = 'Sources';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The phrase to display <miso-keywords>
|
|
13
|
+
*/
|
|
14
|
+
export const keywords = 'You searched for “${value}”';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The phrase to display <miso-hits>
|
|
18
|
+
*/
|
|
19
|
+
export const hits = '${value} Results';
|
|
20
|
+
|
|
21
|
+
export const showMore = 'Show more';
|
|
22
|
+
export const showLess = 'Show less';
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { externalize } from '@miso.ai/commons';
|
|
2
|
+
import * as CONSTANTS from './constants.js';
|
|
3
|
+
import * as DEFAULT_PHRASES from './phrases.js';
|
|
4
|
+
import { Helpers, Sections as _Sections } from '../common/templates.js';
|
|
5
|
+
|
|
6
|
+
class Sections extends _Sections {
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
super({ helpers });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
answerBox(options) {
|
|
13
|
+
const { element } = this._helpers;
|
|
14
|
+
const { classPrefix } = options;
|
|
15
|
+
return element('div', { classes: [`${classPrefix}__answer-box`] }, [
|
|
16
|
+
element('div', { classes: [`${classPrefix}__answer-box-inner`] }, this.answerGroup(options)),
|
|
17
|
+
this.answerBoxToggle(options),
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
answerBoxToggle(options) {
|
|
22
|
+
const { element } = this._helpers;
|
|
23
|
+
const { classPrefix } = options;
|
|
24
|
+
return element('div', { classes: [`${classPrefix}__answer-box-toggle-container`] }, [
|
|
25
|
+
element('div', { classes: [`${classPrefix}__answer-box-toggle`], 'data-role': 'answer-box-toggle' }),
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
searchResultsGroup(options) {
|
|
30
|
+
const { container } = this._helpers;
|
|
31
|
+
return container(options, { name: 'search-results', visibleWhen: 'ready' }, [
|
|
32
|
+
this.searchResultsInfoGroup(options),
|
|
33
|
+
this.searchResultsFilters(options),
|
|
34
|
+
'<miso-products></miso-products>',
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
searchResultsInfoGroup(options) {
|
|
39
|
+
const { element } = this._helpers;
|
|
40
|
+
const { classPrefix } = options;
|
|
41
|
+
return element('div', { classes: [`${classPrefix}__search-results-info`] }, [
|
|
42
|
+
this.keywords(options),
|
|
43
|
+
this.hits(options),
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
searchResultsFilters(options) {
|
|
48
|
+
const { element } = this._helpers;
|
|
49
|
+
const { classPrefix } = options;
|
|
50
|
+
return element('div', { classes: [`${classPrefix}__search-results-filters`] }, [
|
|
51
|
+
'<miso-facets></miso-facets>',
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
keywords(options) {
|
|
56
|
+
const { phrase } = this._helpers;
|
|
57
|
+
return phrase(options, { name: 'keywords', tag: 'div', inline: true }).replaceAll('${value}', '<miso-keywords></miso-keywords>');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
hits(options) {
|
|
61
|
+
const { phrase } = this._helpers;
|
|
62
|
+
return phrase(options, { name: 'hits', tag: 'div', inline: true }).replaceAll('${value}', '<miso-hits></miso-hits>');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const helpers = externalize(new Helpers({ ...CONSTANTS, DEFAULT_PHRASES }));
|
|
68
|
+
export const sections = externalize(new Sections({ helpers }));
|
|
69
|
+
|
|
70
|
+
export function root(options = {}) {
|
|
71
|
+
const { normalizeOptions, section, container } = helpers;
|
|
72
|
+
options = normalizeOptions(options);
|
|
73
|
+
const { answerBox } = sections;
|
|
74
|
+
return [
|
|
75
|
+
section(options, { name: 'question' }, [
|
|
76
|
+
container(options, { name: 'query' }, '<miso-query></miso-query>'),
|
|
77
|
+
]),
|
|
78
|
+
section(options, { name: 'answer' }, [
|
|
79
|
+
answerBox(options),
|
|
80
|
+
]),
|
|
81
|
+
section(options, { name: 'search-results' }, sections.searchResultsGroup(options)),
|
|
82
|
+
].join('');
|
|
83
|
+
};
|
package/src/defaults/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { default as MisoAskElement } from './miso-ask.js';
|
|
2
2
|
export { default as MisoExploreElement } from './miso-explore.js';
|
|
3
|
+
export { default as MisoHybridSearchElement } from './miso-hybrid-search.js';
|
|
3
4
|
export { default as MisoSearchElement } from './miso-search.js';
|
|
4
5
|
export { default as MisoRecommendationElement } from './miso-recommendation.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import MisoContainerElement from './miso-container.js';
|
|
2
|
+
|
|
3
|
+
const TAG_NAME = 'miso-hybrid-search';
|
|
4
|
+
|
|
5
|
+
export default class MisoHybridSearchElement extends MisoContainerElement {
|
|
6
|
+
|
|
7
|
+
static get observedAttributes() {
|
|
8
|
+
return MisoContainerElement.observedAttributes;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static get tagName() {
|
|
12
|
+
return TAG_NAME;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
_getWorkflow(client) {
|
|
16
|
+
return client.ui.hybridSearch;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
}
|
|
@@ -10,4 +10,7 @@ export { default as MisoSourcesElement } from './miso-sources.js';
|
|
|
10
10
|
export { default as MisoRelatedResourcesElement } from './miso-related-resources.js';
|
|
11
11
|
export { default as MisoRelatedQuestionsElement } from './miso-related-questions.js';
|
|
12
12
|
export { default as MisoSovrnAffiliationElement } from './miso-affiliation.js';
|
|
13
|
+
export { default as MisoKeywordsElement } from './miso-keywords.js';
|
|
14
|
+
export { default as MisoHitsElement } from './miso-hits.js';
|
|
15
|
+
export { default as MisoFacetsElement } from './miso-facets.js';
|
|
13
16
|
export { default as MisoErrorElement } from './miso-error.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import MisoComponentElement from './miso-component.js';
|
|
2
|
+
import { ROLE } from '../../constants.js';
|
|
3
|
+
|
|
4
|
+
export default class MisoFacetsElement extends MisoComponentElement {
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
super({
|
|
8
|
+
role: ROLE.FACETS,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static get tagName() {
|
|
13
|
+
return 'miso-facets';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import MisoComponentElement from './miso-component.js';
|
|
2
|
+
import { ROLE } from '../../constants.js';
|
|
3
|
+
|
|
4
|
+
export default class MisoHitsElement extends MisoComponentElement {
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
super({
|
|
8
|
+
role: ROLE.HITS,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static get tagName() {
|
|
13
|
+
return 'miso-hits';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import MisoComponentElement from './miso-component.js';
|
|
2
|
+
import { ROLE } from '../../constants.js';
|
|
3
|
+
|
|
4
|
+
export default class MisoKeywordsElement extends MisoComponentElement {
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
super({
|
|
8
|
+
role: ROLE.KEYWORDS,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static get tagName() {
|
|
13
|
+
return 'miso-keywords';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
package/src/layout/index.js
CHANGED
|
@@ -12,3 +12,4 @@ export { default as FeedbackLayout } from './radio/feedback.js';
|
|
|
12
12
|
export { default as TextLayout } from './text/text.js';
|
|
13
13
|
export { default as TypewriterLayout } from './text/typewriter/index.js';
|
|
14
14
|
export { default as AffiliationLayout } from './affiliation/affiliation.js';
|
|
15
|
+
export { default as FacetsLayout } from './input/facets.js';
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { escapeHtml } from '@miso.ai/commons';
|
|
2
|
+
import { LAYOUT_CATEGORY } from '../../constants.js';
|
|
3
|
+
import TemplateBasedLayout from '../template.js';
|
|
4
|
+
import Bindings from '../../util/bindings.js';
|
|
5
|
+
|
|
6
|
+
const TYPE = 'facets';
|
|
7
|
+
const DEFAULT_CLASSNAME = 'miso-facets';
|
|
8
|
+
const DEDAULT_FACET_CLASSNAME = 'miso-facet';
|
|
9
|
+
const CUSTOM_ATTRIBUTES_PREFIX = 'custom_attributes.';
|
|
10
|
+
|
|
11
|
+
function root(layout, state) {
|
|
12
|
+
const { className, role, templates } = layout;
|
|
13
|
+
const roleAttr = role ? `data-role="${role}"` : '';
|
|
14
|
+
return `<div class="${className}" ${roleAttr}>${templates.facets(layout, state)}</div>`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function facets(layout, state) {
|
|
18
|
+
const { templates } = layout;
|
|
19
|
+
const { facet_fields = {} } = state.value || {};
|
|
20
|
+
return Object.keys(facet_fields).map(field => templates.facet(layout, { field, entries: facet_fields[field] }, state));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function facet(layout, facet, state) {
|
|
24
|
+
const { facetClassName = DEDAULT_FACET_CLASSNAME, templates } = layout;
|
|
25
|
+
const { field } = facet;
|
|
26
|
+
return `<div class="${facetClassName}" data-role="facet" data-field="${escapeHtml(field)}">${templates.header(layout, facet, state)}${templates.options(layout, facet, state)}</div>`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function header(layout, facet, state) {
|
|
30
|
+
const { facetClassName = DEDAULT_FACET_CLASSNAME, templates } = layout;
|
|
31
|
+
return `<div class="${facetClassName}__header">${templates.title(layout, facet, state)}</div>`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function title(layout, { field }) {
|
|
35
|
+
if (field.startsWith(CUSTOM_ATTRIBUTES_PREFIX)) {
|
|
36
|
+
field = field.substring(CUSTOM_ATTRIBUTES_PREFIX.length);
|
|
37
|
+
}
|
|
38
|
+
// TODO: to words
|
|
39
|
+
return escapeHtml(field);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function options(layout, { field, entries }, state) {
|
|
43
|
+
const { facetClassName = DEDAULT_FACET_CLASSNAME, templates } = layout;
|
|
44
|
+
return `<ul class="${facetClassName}__options" data-role="options">${entries.map(([value, count]) => templates.option(layout, { field, value, count }, state)).join('')}</ul>`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function option(layout, entry, state) {
|
|
48
|
+
const { facetClassName = DEDAULT_FACET_CLASSNAME, templates } = layout;
|
|
49
|
+
return `
|
|
50
|
+
<li class="${facetClassName}__option" data-role="option" tabindex="0">
|
|
51
|
+
<span class="${facetClassName}__value">${templates.value(layout, entry, state)}</span>
|
|
52
|
+
<span class="${facetClassName}__count">${templates.count(layout, entry, state)}</span>
|
|
53
|
+
</li>`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
function value(layout, { value }, state) {
|
|
58
|
+
return escapeHtml(value);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function count(layout, { count }, state) {
|
|
62
|
+
return layout.templates.helpers.formatNumber(count);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const DEFAULT_TEMPLATES = Object.freeze({
|
|
66
|
+
root,
|
|
67
|
+
facets,
|
|
68
|
+
facet,
|
|
69
|
+
header,
|
|
70
|
+
title,
|
|
71
|
+
options,
|
|
72
|
+
option,
|
|
73
|
+
value,
|
|
74
|
+
count,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
function getItemKey(field, value) {
|
|
78
|
+
return `${field}:::${value}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getSelectedValues(facets) {
|
|
82
|
+
const selections = {};
|
|
83
|
+
for (const field in facets) {
|
|
84
|
+
selections[field] = new Set(facets[field]);
|
|
85
|
+
}
|
|
86
|
+
return selections;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default class FacetsLayout extends TemplateBasedLayout {
|
|
90
|
+
|
|
91
|
+
static get category() {
|
|
92
|
+
return LAYOUT_CATEGORY.FILTERS;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static get type() {
|
|
96
|
+
return TYPE;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static get defaultTemplates() {
|
|
100
|
+
return DEFAULT_TEMPLATES;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static get defaultClassName() {
|
|
104
|
+
return DEFAULT_CLASSNAME;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
constructor({ className = DEFAULT_CLASSNAME, templates, ...options } = {}) {
|
|
108
|
+
super({
|
|
109
|
+
className,
|
|
110
|
+
templates: { ...DEFAULT_TEMPLATES, ...templates },
|
|
111
|
+
...options,
|
|
112
|
+
});
|
|
113
|
+
this._bindings = new Bindings();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
initialize(view) {
|
|
117
|
+
const { proxyElement, hub, filters } = view;
|
|
118
|
+
this._unsubscribes = [
|
|
119
|
+
...this._unsubscribes,
|
|
120
|
+
proxyElement.on('click', (e) => this._handleClick(e)),
|
|
121
|
+
filters.on('update', (e) => this._updateSelections(e)),
|
|
122
|
+
filters.on('reset', () => this._syncSelections()),
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_render(element, data, controls) {
|
|
127
|
+
super._render(element, data, controls);
|
|
128
|
+
this._syncSelections();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
_afterRender(element, state) {
|
|
132
|
+
this._syncBindings(element, state);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_unrender() {
|
|
136
|
+
this._clearBindings();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
_syncSelections() {
|
|
140
|
+
const selectedValues = getSelectedValues(this._view.filters.states.facets);
|
|
141
|
+
for (const { value: { field, value }, element } of this._bindings.entries) {
|
|
142
|
+
this._setSelected(element, selectedValues[field] && selectedValues[field].has(value));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_updateSelections({ updates }) {
|
|
147
|
+
const selectedValues = getSelectedValues(updates.facets);
|
|
148
|
+
for (const { value: { field, value }, element } of this._bindings.entries) {
|
|
149
|
+
if (!selectedValues[field]) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
this._setSelected(element, selectedValues[field].has(value));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
_setSelected(element, selected) {
|
|
157
|
+
if (!element) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (selected) {
|
|
161
|
+
element.classList.add('selected');
|
|
162
|
+
} else {
|
|
163
|
+
element.classList.remove('selected');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
_syncBindings(element, state) {
|
|
168
|
+
if (!element || !state.value) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const items = this._getItems(state);
|
|
172
|
+
const keys = [];
|
|
173
|
+
const values = [];
|
|
174
|
+
const elements = [];
|
|
175
|
+
for (const facetElement of this._getFacetElements(element)) {
|
|
176
|
+
const field = facetElement.getAttribute('data-field');
|
|
177
|
+
const _values = items[field];
|
|
178
|
+
if (!_values) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const itemElements = this._getItemElements(facetElement);
|
|
182
|
+
for (let i = 0; i < itemElements.length; i++) {
|
|
183
|
+
const itemElement = itemElements[i];
|
|
184
|
+
const value = _values[i];
|
|
185
|
+
if (!value) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
keys.push(getItemKey(field, value));
|
|
189
|
+
values.push({ field, value });
|
|
190
|
+
elements.push(itemElement);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
this._bindings.update(keys, values, elements);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
_clearBindings() {
|
|
197
|
+
this._bindings.clear();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
_getItems({ value } = {}) {
|
|
201
|
+
const { facet_fields = {} } = value || {};
|
|
202
|
+
const items = {};
|
|
203
|
+
for (const field in facet_fields) {
|
|
204
|
+
// the key needs to be matched against data-field attribute
|
|
205
|
+
items[escapeHtml(field)] = facet_fields[field].map(([value]) => value);
|
|
206
|
+
}
|
|
207
|
+
return items;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
_getFacetElements(element) {
|
|
211
|
+
return Array.from(element.querySelectorAll(`[data-role="facet"]`));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_getFacetElement(element, field) {
|
|
215
|
+
return element.querySelector(`[data-role="facet"][data-field="${field}"]`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
_getItemElements(element) {
|
|
219
|
+
return Array.from(element.querySelectorAll(`[data-role="option"]`));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
_handleClick(event) {
|
|
223
|
+
const element = event.target.closest(`[data-role="option"]`);
|
|
224
|
+
if (!element) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const binding = this._bindings.get(element);
|
|
228
|
+
const option = binding && binding.value;
|
|
229
|
+
if (!option) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const { field, value } = option;
|
|
233
|
+
this._view.filters.facets.toggle(field, value);
|
|
234
|
+
this._view.filters.apply(); // TODO: support autoApply = false
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
destroy() {
|
|
238
|
+
this._bindings.clear();
|
|
239
|
+
super.destroy();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
}
|
|
@@ -71,13 +71,13 @@ export default class OptionListLayout extends TemplateBasedLayout {
|
|
|
71
71
|
this._unsubscribes = [
|
|
72
72
|
...this._unsubscribes,
|
|
73
73
|
proxyElement.on('click', (e) => this._handleClick(e)),
|
|
74
|
-
hub.on(fields.suggestions(), () => this._handleInput()),
|
|
74
|
+
hub.on(fields.suggestions(), () => this._handleInput()), // TODO: use data mapping
|
|
75
75
|
];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
_preprocess() {
|
|
79
79
|
return super._preprocess({
|
|
80
|
-
state: this._view.hub.states[fields.suggestions()],
|
|
80
|
+
state: this._view.hub.states[fields.suggestions()], // TODO: use data mapping
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -135,12 +135,11 @@ export default class OptionListLayout extends TemplateBasedLayout {
|
|
|
135
135
|
return;
|
|
136
136
|
}
|
|
137
137
|
// TODO: q -> value
|
|
138
|
-
this._view.hub.
|
|
138
|
+
this._view.hub.update(fields.query(), { q: value });
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
destroy() {
|
|
142
142
|
this._destroyTrackable();
|
|
143
|
-
this._view = undefined;
|
|
144
143
|
super.destroy();
|
|
145
144
|
}
|
|
146
145
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { defineValues, escapeHtml, findInAncestors, debounce } from '@miso.ai/commons';
|
|
2
|
-
import { LAYOUT_CATEGORY, STATUS, EVENT_TYPE } from '../../constants.js';
|
|
2
|
+
import { LAYOUT_CATEGORY, STATUS, EVENT_TYPE, DATA_ASPECT } from '../../constants.js';
|
|
3
3
|
import { fields } from '../../actor/index.js';
|
|
4
4
|
import TemplateBasedLayout from '../template.js';
|
|
5
|
-
import {
|
|
5
|
+
import { imageBlock, productInfoBlock } from '../templates.js';
|
|
6
6
|
import { SEND, SEARCH } from '../../asset/svgs.js';
|
|
7
7
|
|
|
8
8
|
const TYPE = 'search-box';
|
|
@@ -57,7 +57,7 @@ function query(layout, { text, text_with_inverted_markups } = {}) {
|
|
|
57
57
|
|
|
58
58
|
function product(layout, { product }) {
|
|
59
59
|
const { templates } = layout;
|
|
60
|
-
const [openTag, closeTag] = helpers.tagPair(layout, product, { role: false });
|
|
60
|
+
const [openTag, closeTag] = templates.helpers.tagPair(layout, product, { role: false });
|
|
61
61
|
return [
|
|
62
62
|
openTag,
|
|
63
63
|
templates.imageBlock(layout, product),
|
|
@@ -93,7 +93,6 @@ const DEFAULT_TEMPLATES = Object.freeze({
|
|
|
93
93
|
product,
|
|
94
94
|
imageBlock,
|
|
95
95
|
productInfoBlock,
|
|
96
|
-
helpers,
|
|
97
96
|
});
|
|
98
97
|
|
|
99
98
|
const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
|
|
@@ -142,8 +141,8 @@ export default class SearchBoxLayout extends TemplateBasedLayout {
|
|
|
142
141
|
proxyElement.on('click', (e) => this._handleClick(e)),
|
|
143
142
|
proxyElement.on('focusin', (e) => this._handleFocusIn(e)),
|
|
144
143
|
proxyElement.on('focusout', (e) => this._handleFocusOut(e)),
|
|
144
|
+
hub.on(fields.data(DATA_ASPECT.AUTOCOMPLETE), () => view.refresh({ force: true })),
|
|
145
145
|
];
|
|
146
|
-
this._unsubscribes.push(hub.on(fields.completions(), () => view.refresh({ force: true })));
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
focus() {
|
|
@@ -176,7 +175,7 @@ export default class SearchBoxLayout extends TemplateBasedLayout {
|
|
|
176
175
|
state = super._preprocess({ state });
|
|
177
176
|
return {
|
|
178
177
|
...state,
|
|
179
|
-
completions: this._view.hub.states[fields.
|
|
178
|
+
completions: this._view.hub.states[fields.data(DATA_ASPECT.AUTOCOMPLETE)],
|
|
180
179
|
};
|
|
181
180
|
}
|
|
182
181
|
|
|
@@ -324,7 +323,7 @@ export default class SearchBoxLayout extends TemplateBasedLayout {
|
|
|
324
323
|
}
|
|
325
324
|
this.close();
|
|
326
325
|
// TODO: q -> value
|
|
327
|
-
this._view.hub.
|
|
326
|
+
this._view.hub.update(fields.query(), { q: value });
|
|
328
327
|
this._trackSubmit(value);
|
|
329
328
|
}
|
|
330
329
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { STATUS, LAYOUT_CATEGORY } from '../../constants.js';
|
|
2
2
|
import TemplateBasedLayout from '../template.js';
|
|
3
3
|
import { makeTrackable } from '../trackable.js';
|
|
4
|
-
import { product, article, question, productInfoBlock, articleInfoBlock, titleBlock, brandBlock, descriptionBlock, dateBlock, priceBlock, discountRateText, ctaBlock, cta, imageBlock, indexBlock
|
|
4
|
+
import { product, article, question, productInfoBlock, articleInfoBlock, titleBlock, brandBlock, descriptionBlock, dateBlock, priceBlock, discountRateText, ctaBlock, cta, imageBlock, indexBlock } from '../templates.js';
|
|
5
5
|
|
|
6
6
|
function root(layout, state) {
|
|
7
7
|
const { className, role, templates } = layout;
|
|
@@ -67,7 +67,6 @@ const DEFAULT_TEMPLATES = Object.freeze({
|
|
|
67
67
|
cta,
|
|
68
68
|
imageBlock,
|
|
69
69
|
indexBlock,
|
|
70
|
-
helpers,
|
|
71
70
|
});
|
|
72
71
|
|
|
73
72
|
export default class CollectionLayout extends TemplateBasedLayout {
|
package/src/layout/raf.js
CHANGED
package/src/layout/template.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineValues } from '@miso.ai/commons';
|
|
2
2
|
import RafLayout from './raf.js';
|
|
3
|
-
import { requiresImplementation } from './templates.js';
|
|
3
|
+
import { requiresImplementation, helpers } from './templates.js';
|
|
4
4
|
|
|
5
5
|
export default class TemplateBasedLayout extends RafLayout {
|
|
6
6
|
|
|
@@ -11,6 +11,7 @@ export default class TemplateBasedLayout extends RafLayout {
|
|
|
11
11
|
templates: {
|
|
12
12
|
...requiresImplementation('root'),
|
|
13
13
|
...templates,
|
|
14
|
+
helpers,
|
|
14
15
|
},
|
|
15
16
|
});
|
|
16
17
|
}
|
package/src/layout/templates.js
CHANGED
|
@@ -176,6 +176,7 @@ function tagPair({ className, options = {} }, { product_id, url }, { classSuffix
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
const DEFAULT_DATE_OPTIONS = Object.freeze({ locale: 'en-US', year: 'numeric', month: 'short', day: 'numeric' });
|
|
179
|
+
const DEFAULT_NUMBER_OPTIONS = 'en-US';
|
|
179
180
|
|
|
180
181
|
function formatDate(date, fn = DEFAULT_DATE_OPTIONS) {
|
|
181
182
|
switch (typeof fn) {
|
|
@@ -190,6 +191,19 @@ function formatDate(date, fn = DEFAULT_DATE_OPTIONS) {
|
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
193
|
|
|
194
|
+
function formatNumber(number, fn = DEFAULT_NUMBER_OPTIONS) {
|
|
195
|
+
switch (typeof fn) {
|
|
196
|
+
case 'function':
|
|
197
|
+
return fn(number);
|
|
198
|
+
case 'string':
|
|
199
|
+
return new Intl.NumberFormat(fn).format(number);
|
|
200
|
+
case 'object':
|
|
201
|
+
return new Intl.NumberFormat(fn.locale || DEFAULT_NUMBER_OPTIONS.locale, fn).format(number);
|
|
202
|
+
default:
|
|
203
|
+
throw new Error(`Invalid number format: ${fn}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
193
207
|
function asFunction(template = '') {
|
|
194
208
|
return typeof template === 'function' ? template : () => template;
|
|
195
209
|
}
|
|
@@ -197,6 +211,7 @@ function asFunction(template = '') {
|
|
|
197
211
|
export const helpers = Object.freeze({
|
|
198
212
|
tagPair,
|
|
199
213
|
formatDate,
|
|
214
|
+
formatNumber,
|
|
200
215
|
escapeHtml,
|
|
201
216
|
asFunction,
|
|
202
217
|
});
|
package/src/layout/text/text.js
CHANGED
|
@@ -5,10 +5,35 @@ import TemplateBasedLayout from '../template.js';
|
|
|
5
5
|
const TYPE = 'text';
|
|
6
6
|
const DEFAULT_CLASSNAME = 'miso-text';
|
|
7
7
|
|
|
8
|
-
function root(layout,
|
|
9
|
-
const { className, role, options: { tag } } = layout;
|
|
8
|
+
function root(layout, { value }) {
|
|
9
|
+
const { className, role, options: { raw = false, tag } } = layout;
|
|
10
10
|
const roleAttr = role ? `data-role="${role}"` : '';
|
|
11
|
-
|
|
11
|
+
const content = value !== undefined ? escapeHtml(getFormatFunction(layout)(value)) : '';
|
|
12
|
+
return raw ? content : `<${tag} class="${className}" ${roleAttr}>${content}</${tag}>`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getFormatFunction(layout) {
|
|
16
|
+
let { format } = layout.options;
|
|
17
|
+
const { formatDate, formatNumber } = layout.templates.helpers;
|
|
18
|
+
if (!format) {
|
|
19
|
+
return v => `${v}`;
|
|
20
|
+
}
|
|
21
|
+
switch (typeof format) {
|
|
22
|
+
case 'function':
|
|
23
|
+
return format;
|
|
24
|
+
case 'string':
|
|
25
|
+
format = [format];
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
let [type, args] = format;
|
|
29
|
+
switch (type) {
|
|
30
|
+
case 'date':
|
|
31
|
+
return v => formatDate(v, args);
|
|
32
|
+
case 'number':
|
|
33
|
+
return v => formatNumber(v, args);
|
|
34
|
+
default:
|
|
35
|
+
return v => `${v}`;
|
|
36
|
+
}
|
|
12
37
|
}
|
|
13
38
|
|
|
14
39
|
const DEFAULT_TEMPLATES = Object.freeze({
|