@miso.ai/client-sdk-ui 1.11.0-beta.1 → 1.11.0-beta.11

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/dist/cjs/actor/data.js +6 -7
  2. package/dist/cjs/actor/feedback.js +6 -39
  3. package/dist/cjs/actor/fields.js +14 -6
  4. package/dist/cjs/actor/hub.js +1 -2
  5. package/dist/cjs/actor/index.js +0 -7
  6. package/dist/cjs/actor/interactions.js +4 -15
  7. package/dist/cjs/actor/sessions.js +2 -33
  8. package/dist/cjs/actor/tracker.js +25 -26
  9. package/dist/cjs/actor/utils.js +0 -49
  10. package/dist/cjs/actor/view.js +40 -40
  11. package/dist/cjs/actor/views.js +32 -8
  12. package/dist/cjs/asset/svgs.js +18 -8
  13. package/dist/cjs/constants.js +18 -19
  14. package/dist/cjs/defaults/hybrid-search/answer-box.js +19 -3
  15. package/dist/cjs/defaults/hybrid-search/phrases.js +5 -5
  16. package/dist/cjs/defaults/hybrid-search/templates.js +37 -9
  17. package/dist/cjs/element/role/index.js +12 -5
  18. package/dist/cjs/element/role/{miso-hits.js → miso-more.js} +4 -4
  19. package/dist/cjs/element/role/miso-total.js +20 -0
  20. package/dist/cjs/layout/affiliation/affiliation.js +0 -3
  21. package/dist/cjs/layout/banner/banner.js +0 -4
  22. package/dist/cjs/layout/button/more-button.js +73 -0
  23. package/dist/cjs/layout/container/container.js +0 -3
  24. package/dist/cjs/layout/error/error.js +0 -4
  25. package/dist/cjs/layout/index.js +7 -0
  26. package/dist/cjs/layout/input/facets.js +5 -5
  27. package/dist/cjs/layout/input/option-list.js +0 -4
  28. package/dist/cjs/layout/input/search-box.js +35 -17
  29. package/dist/cjs/layout/list/cards.js +0 -3
  30. package/dist/cjs/layout/list/carousel.js +0 -3
  31. package/dist/cjs/layout/list/collection.js +22 -7
  32. package/dist/cjs/layout/list/list.js +0 -3
  33. package/dist/cjs/layout/radio/feedback.js +0 -3
  34. package/dist/cjs/layout/radio/radio.js +0 -4
  35. package/dist/cjs/layout/raf.js +4 -2
  36. package/dist/cjs/layout/text/text.js +0 -4
  37. package/dist/cjs/layout/text/typewriter/index.js +0 -3
  38. package/dist/cjs/layout/triggerable.js +79 -0
  39. package/dist/cjs/util/states.js +0 -15
  40. package/dist/cjs/version.js +1 -1
  41. package/dist/cjs/workflow/answer-based.js +109 -111
  42. package/dist/cjs/workflow/ask.js +35 -27
  43. package/dist/cjs/workflow/asks.js +1 -1
  44. package/dist/cjs/workflow/autocompletable.js +33 -0
  45. package/dist/cjs/workflow/autocomplete.js +122 -0
  46. package/dist/cjs/workflow/base.js +169 -102
  47. package/dist/cjs/workflow/explore.js +26 -19
  48. package/dist/cjs/workflow/hybrid-search-answer.js +99 -0
  49. package/dist/cjs/workflow/hybrid-search-results.js +66 -0
  50. package/dist/cjs/workflow/hybrid-search-views.js +122 -0
  51. package/dist/cjs/workflow/hybrid-search.js +98 -103
  52. package/dist/cjs/workflow/options.js +32 -17
  53. package/dist/cjs/workflow/processors.js +191 -17
  54. package/dist/cjs/workflow/recommendation.js +36 -49
  55. package/dist/cjs/workflow/search-based.js +228 -0
  56. package/dist/cjs/workflow/search.js +21 -68
  57. package/package.json +2 -2
  58. package/src/actor/data.js +2 -4
  59. package/src/actor/feedback.js +6 -23
  60. package/src/actor/fields.js +14 -6
  61. package/src/actor/hub.js +1 -2
  62. package/src/actor/index.js +0 -1
  63. package/src/actor/interactions.js +4 -10
  64. package/src/actor/sessions.js +3 -30
  65. package/src/actor/tracker.js +25 -19
  66. package/src/actor/utils.js +0 -47
  67. package/src/actor/view.js +27 -15
  68. package/src/actor/views.js +36 -8
  69. package/src/asset/svgs.js +19 -7
  70. package/src/constants.js +25 -16
  71. package/src/defaults/hybrid-search/answer-box.js +17 -5
  72. package/src/defaults/hybrid-search/phrases.js +4 -4
  73. package/src/defaults/hybrid-search/templates.js +25 -9
  74. package/src/element/role/index.js +2 -1
  75. package/src/element/role/{miso-hits.js → miso-more.js} +3 -3
  76. package/src/element/role/miso-total.js +16 -0
  77. package/src/layout/affiliation/affiliation.js +1 -5
  78. package/src/layout/banner/banner.js +0 -5
  79. package/src/layout/button/more-button.js +69 -0
  80. package/src/layout/container/container.js +1 -5
  81. package/src/layout/error/error.js +0 -5
  82. package/src/layout/index.js +2 -1
  83. package/src/layout/input/facets.js +5 -6
  84. package/src/layout/input/option-list.js +0 -5
  85. package/src/layout/input/search-box.js +29 -19
  86. package/src/layout/list/cards.js +0 -4
  87. package/src/layout/list/carousel.js +0 -4
  88. package/src/layout/list/collection.js +21 -9
  89. package/src/layout/list/list.js +0 -4
  90. package/src/layout/radio/feedback.js +0 -4
  91. package/src/layout/radio/radio.js +0 -5
  92. package/src/layout/raf.js +2 -2
  93. package/src/layout/text/text.js +0 -5
  94. package/src/layout/text/typewriter/index.js +1 -5
  95. package/src/layout/trackable.js +1 -1
  96. package/src/layout/triggerable.js +76 -0
  97. package/src/util/states.js +0 -14
  98. package/src/version.js +1 -1
  99. package/src/workflow/answer-based.js +101 -77
  100. package/src/workflow/ask.js +33 -25
  101. package/src/workflow/asks.js +1 -1
  102. package/src/workflow/autocompletable.js +29 -0
  103. package/src/workflow/autocomplete.js +113 -0
  104. package/src/workflow/base.js +147 -93
  105. package/src/workflow/explore.js +23 -8
  106. package/src/workflow/hybrid-search-answer.js +92 -0
  107. package/src/workflow/hybrid-search-results.js +62 -0
  108. package/src/workflow/hybrid-search-views.js +124 -0
  109. package/src/workflow/hybrid-search.js +102 -73
  110. package/src/workflow/options.js +26 -11
  111. package/src/workflow/processors.js +169 -14
  112. package/src/workflow/recommendation.js +32 -33
  113. package/src/workflow/search-based.js +222 -0
  114. package/src/workflow/search.js +21 -65
  115. package/dist/cjs/actor/autocomplete.js +0 -160
  116. package/dist/cjs/workflow/utils.js +0 -42
  117. package/src/actor/autocomplete.js +0 -133
  118. package/src/workflow/utils.js +0 -29
@@ -1,50 +1,3 @@
1
- import { trimObj } from '@miso.ai/commons';
2
- import { ROLE, EVENT_TYPE } from '../constants.js';
3
-
4
- const PRODUCT_TYPED_PROPERTIES = new Set([
5
- ROLE.PRODUCTS,
6
- ROLE.SOURCES,
7
- ROLE.RELATED_RESOURCES,
8
- ]);
9
-
10
- // TODO: move away
11
- export function isProduct(property) {
12
- return PRODUCT_TYPED_PROPERTIES.has(property);
13
- }
14
-
15
- export function toInteraction({ property, misoId, request }, { event, values, manual }) {
16
- // TODO: ad-hoc
17
- const question_source = request && request._meta && request._meta.question_source;
18
- const isProductType = isProduct(property);
19
- const isSubmit = event === EVENT_TYPE.SUBMIT;
20
- const isAffiliation = property === ROLE.AFFILIATION;
21
-
22
- const product_ids = isProductType && values ? values.map(v => typeof v === 'string' ? v : v.product_id).filter(v => v) : !isSubmit ? [] : undefined;
23
- const items = !isProductType && !isSubmit && values ? values.map(v => v.text || v.id || v) : undefined;
24
- const value = isSubmit ? values : undefined;
25
-
26
- const channel = isAffiliation ? values[0] && values[0].channel : undefined;
27
- const positions = isAffiliation ? values.map(v => v.position) : undefined;
28
-
29
- const payload = trimObj({
30
- type: event === 'viewable' ? 'viewable_impression' : event,
31
- product_ids,
32
- miso_id: misoId,
33
- context: {
34
- custom_context: trimObj({
35
- property,
36
- channel,
37
- items,
38
- value,
39
- positions,
40
- trigger: manual ? 'manual' : 'auto',
41
- question_source,
42
- }),
43
- },
44
- });
45
- return payload;
46
- }
47
-
48
1
  export function isCurrentSession(hub, session) {
49
2
  if (!session) {
50
3
  return false;
package/src/actor/view.js CHANGED
@@ -22,7 +22,7 @@ export default class ViewActor {
22
22
  constructor(views, role) {
23
23
  this._events = new EventEmitter({ target: this });
24
24
  this._views = views;
25
- this._config = views._rolesConfig[role] || {};
25
+ this._mapping = asMappingFunction((views._roles.mappings || {})[role] || role);
26
26
 
27
27
  defineValues(this, {
28
28
  role,
@@ -101,6 +101,10 @@ export default class ViewActor {
101
101
  }
102
102
  return this._tracker;
103
103
  }
104
+
105
+ get workflowOptions() {
106
+ return this._views._getWorkflowOptions();
107
+ }
104
108
 
105
109
  get filters() {
106
110
  return this._views.filters;
@@ -122,21 +126,17 @@ export default class ViewActor {
122
126
  const previousData = this._data;
123
127
  this._data = data = this._sliceData(data || this._views._getData());
124
128
 
125
- // protocol: no reaction until session starts
126
- const { session, status, ongoing, request, meta } = data;
127
- const active = session && session.active;
128
- if (!force && !active) {
129
- return;
130
- }
131
-
132
129
  // if data is unchanged, skip unless forced
133
130
  if (!force && statesEqual(data, previousData)) {
134
131
  return;
135
132
  }
136
133
 
134
+ const { session, status, ongoing, request, meta, value } = data;
135
+ const baseState = { session, status, ongoing, request, meta, data: value, ...getDataEmptyness(data) };
136
+
137
137
  // render
138
138
  const notifyUpdate = (state = {}, options) => {
139
- state !== false && this.updateState({ session, status, ongoing, request, meta, ...state }, options);
139
+ state !== false && this.updateState({ ...baseState, ...state }, options);
140
140
  };
141
141
  try {
142
142
  await this._layout.render(element, data, { notifyUpdate });
@@ -146,11 +146,10 @@ export default class ViewActor {
146
146
  }
147
147
  }
148
148
 
149
- updateState({ session, status, ongoing, request, meta, error }, { silent = false } = {}) {
150
- if (error) {
151
- status = STATUS.ERRONEOUS;
152
- }
153
- const state = this._state = Object.freeze(trimObj({ session, status, ongoing, request, meta, error }));
149
+ updateState(state, { silent = false } = {}) {
150
+ // there might be error from notifyUpdate() call
151
+ const status = state.error ? STATUS.ERRONEOUS : state.status;
152
+ state = this._state = Object.freeze(trimObj({ ...state, status }));
154
153
  const { role } = this;
155
154
  this.hub.update(fields.view(role), state, { silent });
156
155
  }
@@ -163,7 +162,7 @@ export default class ViewActor {
163
162
  _sliceData(data) {
164
163
  const { value, error, status, meta, ...rest } = data;
165
164
  const sliced = {
166
- value: asMappingFunction(this._config.mapping || this.role)(data),
165
+ value: this._mapping(data),
167
166
  status,
168
167
  data: value,
169
168
  meta,
@@ -247,3 +246,16 @@ function asMappingFunction(fn) {
247
246
  throw new Error(`Invalid mapping function: ${fn}`);
248
247
  }
249
248
  }
249
+
250
+ function getDataEmptyness(data) {
251
+ return isDataStatusRelevantToEmptyness(data) ? { empty: !!isDateValueEmpty(data) } : {};
252
+ }
253
+
254
+ function isDataStatusRelevantToEmptyness(data) {
255
+ return data.status === STATUS.READY && !data.ongoing;
256
+ }
257
+
258
+ function isDateValueEmpty(data) {
259
+ const { value } = data;
260
+ return value === undefined || ((Array.isArray(value) || typeof value === 'string') && !value.length);
261
+ }
@@ -1,4 +1,4 @@
1
- import { defineValues, delegateGetters } from '@miso.ai/commons';
1
+ import { defineValues, delegateGetters, trimObj } from '@miso.ai/commons';
2
2
  import { ROLE } from '../constants.js';
3
3
  import * as fields from './fields.js';
4
4
  import ViewActor from './view.js';
@@ -12,8 +12,7 @@ export default class ViewsActor {
12
12
  workflow,
13
13
  extensions,
14
14
  layouts,
15
- roles = [],
16
- rolesConfig = {},
15
+ roles,
17
16
  options,
18
17
  }) {
19
18
  this._hub = hub;
@@ -21,14 +20,17 @@ export default class ViewsActor {
21
20
  this._extensions = extensions;
22
21
  this._layoutFactory = layouts;
23
22
  this._options = options;
24
- this._rolesConfig = rolesConfig;
23
+ this._roles = roles;
25
24
  this._containers = new Map();
26
25
  this._containerTracker = undefined;
27
26
  this._views = {};
28
- this._trackers = new Trackers(this, roles);
27
+ this._trackers = new Trackers(this, roles.members);
29
28
  this._filters = new Filters(this);
30
29
 
31
- for (const role of roles) {
30
+ for (const role of roles.members) {
31
+ if (role === ROLE.CONTAINER) {
32
+ continue;
33
+ }
32
34
  this._views[role] = new ViewActor(this, role);
33
35
  }
34
36
 
@@ -42,10 +44,14 @@ export default class ViewsActor {
42
44
  this._unsubscribes = [
43
45
  () => window.removeEventListener('resize', syncSize),
44
46
  hub.on(fields.data(), data => this.refresh({ data })),
47
+ options.on('autocomplete', () => this._syncAutocompleteOptions()),
48
+ options.on('pagination', () => this._syncPaginationOptions()),
45
49
  options.on('trackers', () => this._syncTrackers()),
46
50
  options.on('layouts', () => this._syncLayouts()),
47
51
  ];
48
52
 
53
+ this._syncAutocompleteOptions();
54
+ this._syncPaginationOptions();
49
55
  this._syncTrackers();
50
56
  this._syncLayouts();
51
57
  }
@@ -99,6 +105,10 @@ export default class ViewsActor {
99
105
  // TODO
100
106
  }
101
107
 
108
+ containsElement(element) {
109
+ return this._containers.has(element) || this._views[element.role];
110
+ }
111
+
102
112
  refreshElement(element) {
103
113
  const view = element.isContainer ? this._containers.get(element) : this._getViewByElement(element);
104
114
  view && view.refresh({ force: true });
@@ -140,7 +150,7 @@ export default class ViewsActor {
140
150
  } else {
141
151
  for (let [role, [name, options]] of Object.entries(layouts)) {
142
152
  // TODO: try to omit if unchanged
143
- const fn = () => this._layoutFactory.create(name, { ...options, role, workflow: this._workflowName });
153
+ const fn = () => this._layoutFactory.create(name, { ...options, role, workflow: this._workflowName, workflowOptions: this._getWorkflowOptions() });
144
154
  if (role === ROLE.CONTAINER) {
145
155
  for (const view of this._containers.values()) {
146
156
  view.layout = fn();
@@ -158,7 +168,7 @@ export default class ViewsActor {
158
168
  return undefined;
159
169
  }
160
170
  const [name, options] = args;
161
- return this._layoutFactory.create(name, { ...options, role, workflow: this._workflowName });
171
+ return this._layoutFactory.create(name, { ...options, role, workflow: this._workflowName, workflowOptions: this._getWorkflowOptions() });
162
172
  }
163
173
 
164
174
  get(role) {
@@ -225,6 +235,21 @@ export default class ViewsActor {
225
235
  return this._containerTracker;
226
236
  }
227
237
 
238
+ _syncAutocompleteOptions() {
239
+ this._autocompleteOptions = this._options.resolved.autocomplete;
240
+ }
241
+
242
+ _syncPaginationOptions() {
243
+ this._paginationOptions = this._options.resolved.pagination;
244
+ }
245
+
246
+ _getWorkflowOptions() {
247
+ return trimObj({
248
+ autocomplete: this._autocompleteOptions,
249
+ pagination: this._paginationOptions,
250
+ });
251
+ }
252
+
228
253
  _error(e) {
229
254
  // TODO: hub trigger error event
230
255
  console.error(e);
@@ -267,6 +292,9 @@ class Trackers {
267
292
  constructor(views, roles) {
268
293
  this._views = views;
269
294
  for (const role of roles) {
295
+ if (role === ROLE.CONTAINER) {
296
+ continue;
297
+ }
270
298
  Object.defineProperty(this, role, {
271
299
  get: () => views.get(role).tracker,
272
300
  });
package/src/asset/svgs.js CHANGED
@@ -10,12 +10,18 @@ function trianglePath(r) {
10
10
  const PREFIX = 'miso-ui-icon';
11
11
  const ID = `${PREFIX}-svg-${version.replaceAll('.', '_')}`;
12
12
 
13
+ const ICON_NAME = {
14
+ SEND: 'send',
15
+ SEARCH: 'search',
16
+ TRIANGLE: 'triangle',
17
+ };
18
+
13
19
  // CC0 SVGs
14
- const ICON_SEND = `<symbol id="${PREFIX}-send" viewBox="0 0 32 32"><path fill="currentColor" d="M11.499,19.173l5.801,-5.849c0.389,-0.392 1.022,-0.394 1.414,-0.006c0.392,0.389 0.395,1.022 0.006,1.414l-5.798,5.847l5.306,8.002c0.207,0.313 0.572,0.483 0.945,0.441c0.373,-0.042 0.691,-0.289 0.824,-0.64l9.024,-23.904c0.138,-0.366 0.05,-0.78 -0.226,-1.058c-0.276,-0.278 -0.689,-0.369 -1.057,-0.233l-24.004,8.892c-0.353,0.13 -0.602,0.448 -0.646,0.821c-0.044,0.373 0.125,0.74 0.438,0.948l7.973,5.325Z"/></symbol>`;
15
- const ICON_SEARCH = `<symbol id="${PREFIX}-search" viewBox="0 0 24 24"><path fill="currentColor" d="M4 11C4 7.13401 7.13401 4 11 4C14.866 4 18 7.13401 18 11C18 14.866 14.866 18 11 18C7.13401 18 4 14.866 4 11ZM11 2C6.02944 2 2 6.02944 2 11C2 15.9706 6.02944 20 11 20C13.125 20 15.078 19.2635 16.6177 18.0319L20.2929 21.7071C20.6834 22.0976 21.3166 22.0976 21.7071 21.7071C22.0976 21.3166 22.0976 20.6834 21.7071 20.2929L18.0319 16.6177C19.2635 15.078 20 13.125 20 11C20 6.02944 15.9706 2 11 2Z"/></symbol>`;
20
+ const ICON_SEND = `<symbol id="${PREFIX}-${ICON_NAME.SEND}" viewBox="0 0 32 32"><path fill="currentColor" d="M11.499,19.173l5.801,-5.849c0.389,-0.392 1.022,-0.394 1.414,-0.006c0.392,0.389 0.395,1.022 0.006,1.414l-5.798,5.847l5.306,8.002c0.207,0.313 0.572,0.483 0.945,0.441c0.373,-0.042 0.691,-0.289 0.824,-0.64l9.024,-23.904c0.138,-0.366 0.05,-0.78 -0.226,-1.058c-0.276,-0.278 -0.689,-0.369 -1.057,-0.233l-24.004,8.892c-0.353,0.13 -0.602,0.448 -0.646,0.821c-0.044,0.373 0.125,0.74 0.438,0.948l7.973,5.325Z"/></symbol>`;
21
+ const ICON_SEARCH = `<symbol id="${PREFIX}-${ICON_NAME.SEARCH}" viewBox="0 0 24 24"><path fill="currentColor" d="M4 11C4 7.13401 7.13401 4 11 4C14.866 4 18 7.13401 18 11C18 14.866 14.866 18 11 18C7.13401 18 4 14.866 4 11ZM11 2C6.02944 2 2 6.02944 2 11C2 15.9706 6.02944 20 11 20C13.125 20 15.078 19.2635 16.6177 18.0319L20.2929 21.7071C20.6834 22.0976 21.3166 22.0976 21.7071 21.7071C22.0976 21.3166 22.0976 20.6834 21.7071 20.2929L18.0319 16.6177C19.2635 15.078 20 13.125 20 11C20 6.02944 15.9706 2 11 2Z"/></symbol>`;
16
22
 
17
23
  // Custom SVGs
18
- const ICON_TRIANGLE = `<symbol id="${PREFIX}-triangle" viewBox="-5 -5 105 105"><path d="${trianglePath(5)}"/></symbol>`;
24
+ const ICON_TRIANGLE = `<symbol id="${PREFIX}-${ICON_NAME.TRIANGLE}" viewBox="-5 -5 105 105"><path d="${trianglePath(5)}"/></symbol>`;
19
25
 
20
26
  const ICONS = [ICON_SEND, ICON_SEARCH, ICON_TRIANGLE];
21
27
 
@@ -33,10 +39,16 @@ function loadSvgSprites() {
33
39
 
34
40
  loadSvgSprites();
35
41
 
36
- function getSvg(name) {
42
+ function _icon(name) {
37
43
  return `<svg class="miso-ui-icon miso-ui-icon-${name}"><use href="#${PREFIX}-${name}"></use></svg>`;
38
44
  }
39
45
 
40
- export const SEND = getSvg('send');
41
- export const SEARCH = getSvg('search');
42
- export const TRIANGLE = getSvg('triangle');
46
+ export const ICON_NAMES = new Set(Object.values(ICON_NAME));
47
+
48
+ export function getIcon(name) {
49
+ return ICON_NAMES.has(name) ? _icon(name) : '';
50
+ }
51
+
52
+ export const SEND = _icon(ICON_NAME.SEND);
53
+ export const SEARCH = _icon(ICON_NAME.SEARCH);
54
+ export const TRIANGLE = _icon(ICON_NAME.TRIANGLE);
package/src/constants.js CHANGED
@@ -22,7 +22,8 @@ export const ROLE = Object.freeze({
22
22
  AFFILIATION: 'affiliation',
23
23
  KEYWORDS: 'keywords',
24
24
  FACETS: 'facets',
25
- HITS: 'hits',
25
+ TOTAL: 'total',
26
+ MORE: 'more',
26
27
  ERROR: 'error',
27
28
  });
28
29
 
@@ -40,34 +41,29 @@ const DATA_ROLE_SET = new Set([
40
41
  ROLE.RELATED_QUESTIONS,
41
42
  ROLE.AFFILIATION,
42
43
  ROLE.FACETS,
43
- ROLE.HITS,
44
+ ROLE.TOTAL,
44
45
  ]);
45
46
 
46
47
  export function isDataRole(role) {
47
48
  return DATA_ROLE_SET.has(role);
48
49
  }
49
50
 
50
- export const DATA_ASPECT = Object.freeze({
51
- AUTOCOMPLETE: 'autocomplete',
52
- });
51
+ const PRODUCT_ROLE_SET = new Set([
52
+ ROLE.PRODUCTS,
53
+ ROLE.SOURCES,
54
+ ROLE.RELATED_RESOURCES,
55
+ ]);
53
56
 
54
- export const LAYOUT_CATEGORY = Object.freeze({
55
- CONTAINER: 'container',
56
- BANNER: 'banner',
57
- LIST: 'list',
58
- QUERY: 'query',
59
- RADIO: 'radio',
60
- FILTERS: 'filters',
61
- TEXT: 'text',
62
- AFFILIATION: 'affiliation',
63
- ERROR: 'error',
64
- });
57
+ export function isProductRole(role) {
58
+ return PRODUCT_ROLE_SET.has(role);
59
+ }
65
60
 
66
61
  export const EVENT_TYPE = Object.freeze({
67
62
  IMPRESSION: 'impression',
68
63
  VIEWABLE: 'viewable',
69
64
  CLICK: 'click',
70
65
  SUBMIT: 'submit',
66
+ FEEDBACK: 'feedback',
71
67
  });
72
68
 
73
69
  export const TRACKING_EVENT_TYPES = [
@@ -75,8 +71,21 @@ export const TRACKING_EVENT_TYPES = [
75
71
  EVENT_TYPE.VIEWABLE,
76
72
  EVENT_TYPE.CLICK,
77
73
  EVENT_TYPE.SUBMIT,
74
+ EVENT_TYPE.FEEDBACK,
75
+ ];
76
+
77
+ export const PERFORMANCE_EVENT_TYPES = [
78
+ EVENT_TYPE.IMPRESSION,
79
+ EVENT_TYPE.VIEWABLE,
80
+ EVENT_TYPE.CLICK,
78
81
  ];
79
82
 
83
+ const PERFORMANCE_EVENT_TYPE_SET = new Set(PERFORMANCE_EVENT_TYPES);
84
+
85
+ export function isPerformanceEventType(type) {
86
+ return PERFORMANCE_EVENT_TYPE_SET.has(type);
87
+ }
88
+
80
89
  export const TRACKING_STATUS = Object.freeze({
81
90
  UNTRACKED: 'untracked',
82
91
  TRACKING: 'tracking',
@@ -1,5 +1,6 @@
1
1
  import { CLASS_PREFIX } from './constants.js';
2
2
  import { STATUS } from '../../constants.js';
3
+ import { fields } from '../../actor/index.js';
3
4
  import { showMore as DEFAULT_PHRASE_SHOW_MORE, showLess as DEFAULT_PHRASE_SHOW_LESS } from './phrases.js';
4
5
 
5
6
  const CLASS_OPEN = 'answer-box-open';
@@ -23,7 +24,12 @@ export default class AnswerBox {
23
24
 
24
25
  // expose widget API
25
26
  workflow.answerBox = this;
26
- workflow._unsubscribes.push(() => this.destroy()); // TODO: review
27
+ workflow._unsubscribes.push(() => {
28
+ if (workflow.answerBox === this) {
29
+ this.destroy();
30
+ workflow.answerBox = undefined;
31
+ }
32
+ });
27
33
 
28
34
  // handle toggle button clicks
29
35
  const callback = event => this._handleClick(event);
@@ -35,9 +41,8 @@ export default class AnswerBox {
35
41
 
36
42
  this._unsubscribes = [
37
43
  () => element.removeEventListener('click', callback),
38
- workflow.on('loading', () => this.hide()),
39
- workflow.on('error', () => this.show()),
40
- workflow.on('ready', () => this.show()),
44
+ // cling to data rather than view, as we may miss view event when tab is inactive due to raf
45
+ workflow._answer._hub.on(fields.data(), () => this._syncWofkflowStatus()),
41
46
  ];
42
47
  }
43
48
 
@@ -52,7 +57,7 @@ export default class AnswerBox {
52
57
  }
53
58
 
54
59
  _syncWofkflowStatus() {
55
- const { status } = this._workflow;
60
+ const { status } = this._workflow._answer;
56
61
  switch (status) {
57
62
  case STATUS.ERRONEOUS:
58
63
  case STATUS.READY:
@@ -74,6 +79,12 @@ export default class AnswerBox {
74
79
  }
75
80
  }
76
81
 
82
+ _syncScroll() {
83
+ if (!this.isOpen) {
84
+ this._element.scrollIntoView({ behavior: 'smooth' });
85
+ }
86
+ }
87
+
77
88
  destroy() {
78
89
  for (const unsubscribe of this._unsubscribes) {
79
90
  unsubscribe();
@@ -113,6 +124,7 @@ for (const [method, [action, className]] of Object.entries(METHODS)) {
113
124
  this._updateBoxStatus(action, className);
114
125
  if (className === CLASS_OPEN) {
115
126
  this._syncButtonText();
127
+ this._syncScroll();
116
128
  }
117
129
  };
118
130
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
- * The phrase displayed right before <miso-question>
2
+ * The phrase to display <miso-question>
3
3
  */
4
- export const question = 'You asked...';
4
+ export const question = 'You asked “${value}”';
5
5
 
6
6
  /**
7
7
  * The phrase displayed right before <miso-sources>
@@ -14,9 +14,9 @@ export const sources = 'Sources';
14
14
  export const keywords = 'You searched for “${value}”';
15
15
 
16
16
  /**
17
- * The phrase to display <miso-hits>
17
+ * The phrase to display <miso-total>
18
18
  */
19
- export const hits = '${value} Results';
19
+ export const total = '${value} Results';
20
20
 
21
21
  export const showMore = 'Show more';
22
22
  export const showLess = 'Show less';
@@ -18,6 +18,11 @@ class Sections extends _Sections {
18
18
  ]);
19
19
  }
20
20
 
21
+ question(options) {
22
+ const { phrase } = this._helpers;
23
+ return phrase(options, { name: 'question', tag: 'div' }).replaceAll('${value}', '<miso-question></miso-question>');
24
+ }
25
+
21
26
  answerBoxToggle(options) {
22
27
  const { element } = this._helpers;
23
28
  const { classPrefix } = options;
@@ -28,10 +33,11 @@ class Sections extends _Sections {
28
33
 
29
34
  searchResultsGroup(options) {
30
35
  const { container } = this._helpers;
31
- return container(options, { name: 'search-results', visibleWhen: 'ready' }, [
36
+ return container(options, { name: 'search-results', visibleWhen: 'loading ready' }, [
32
37
  this.searchResultsInfoGroup(options),
33
38
  this.searchResultsFilters(options),
34
39
  '<miso-products></miso-products>',
40
+ this.more(options),
35
41
  ]);
36
42
  }
37
43
 
@@ -40,7 +46,7 @@ class Sections extends _Sections {
40
46
  const { classPrefix } = options;
41
47
  return element('div', { classes: [`${classPrefix}__search-results-info`] }, [
42
48
  this.keywords(options),
43
- this.hits(options),
49
+ this.total(options),
44
50
  ]);
45
51
  }
46
52
 
@@ -57,9 +63,20 @@ class Sections extends _Sections {
57
63
  return phrase(options, { name: 'keywords', tag: 'div', inline: true }).replaceAll('${value}', '<miso-keywords></miso-keywords>');
58
64
  }
59
65
 
60
- hits(options) {
66
+ total(options) {
61
67
  const { phrase } = this._helpers;
62
- return phrase(options, { name: 'hits', tag: 'div', inline: true }).replaceAll('${value}', '<miso-hits></miso-hits>');
68
+ return phrase(options, { name: 'total', tag: 'div', inline: true }).replaceAll('${value}', '<miso-total></miso-total>');
69
+ }
70
+
71
+ more(options) {
72
+ if (!options.moreButton) {
73
+ return '';
74
+ }
75
+ const { element } = this._helpers;
76
+ const { classPrefix } = options;
77
+ return element('div', { classes: [`${classPrefix}__search-results-more-container`] }, [
78
+ '<miso-more></miso-more>',
79
+ ]);
63
80
  }
64
81
 
65
82
  }
@@ -70,14 +87,13 @@ export const sections = externalize(new Sections({ helpers }));
70
87
  export function root(options = {}) {
71
88
  const { normalizeOptions, section, container } = helpers;
72
89
  options = normalizeOptions(options);
73
- const { answerBox } = sections;
90
+ const { answerBox, answerGroup, searchResultsGroup } = sections;
91
+ const { answerBox: answerBoxOptions = true } = options;
74
92
  return [
75
93
  section(options, { name: 'question' }, [
76
94
  container(options, { name: 'query' }, '<miso-query></miso-query>'),
77
95
  ]),
78
- section(options, { name: 'answer' }, [
79
- answerBox(options),
80
- ]),
81
- section(options, { name: 'search-results' }, sections.searchResultsGroup(options)),
96
+ section(options, { name: 'answer' }, answerBoxOptions ? [ answerBox(options) ] : answerGroup(options)),
97
+ section(options, { name: 'search-results' }, searchResultsGroup(options)),
82
98
  ].join('');
83
99
  };
@@ -11,6 +11,7 @@ export { default as MisoRelatedResourcesElement } from './miso-related-resources
11
11
  export { default as MisoRelatedQuestionsElement } from './miso-related-questions.js';
12
12
  export { default as MisoSovrnAffiliationElement } from './miso-affiliation.js';
13
13
  export { default as MisoKeywordsElement } from './miso-keywords.js';
14
- export { default as MisoHitsElement } from './miso-hits.js';
14
+ export { default as MisoTotalElement } from './miso-total.js';
15
15
  export { default as MisoFacetsElement } from './miso-facets.js';
16
+ export { default as MisoMoreElement } from './miso-more.js';
16
17
  export { default as MisoErrorElement } from './miso-error.js';
@@ -1,16 +1,16 @@
1
1
  import MisoComponentElement from './miso-component.js';
2
2
  import { ROLE } from '../../constants.js';
3
3
 
4
- export default class MisoHitsElement extends MisoComponentElement {
4
+ export default class MisoMoreElement extends MisoComponentElement {
5
5
 
6
6
  constructor() {
7
7
  super({
8
- role: ROLE.HITS,
8
+ role: ROLE.MORE,
9
9
  });
10
10
  }
11
11
 
12
12
  static get tagName() {
13
- return 'miso-hits';
13
+ return 'miso-more';
14
14
  }
15
15
 
16
16
  }
@@ -0,0 +1,16 @@
1
+ import MisoComponentElement from './miso-component.js';
2
+ import { ROLE } from '../../constants.js';
3
+
4
+ export default class MisoTotalElement extends MisoComponentElement {
5
+
6
+ constructor() {
7
+ super({
8
+ role: ROLE.TOTAL,
9
+ });
10
+ }
11
+
12
+ static get tagName() {
13
+ return 'miso-total';
14
+ }
15
+
16
+ }
@@ -1,5 +1,5 @@
1
1
  import { CarouselItemViewabilityObserver, requestAnimationFrame as raf } from '@miso.ai/commons';
2
- import { STATUS, LAYOUT_CATEGORY } from '../../constants.js';
2
+ import { STATUS } from '../../constants.js';
3
3
  import CollectionLayout from '../list/collection.js';
4
4
  import { affiliation, helpers } from '../templates.js';
5
5
  import { TRIANGLE } from '../../asset/svgs.js';
@@ -136,10 +136,6 @@ function autoplayOptionsEquals(a, b) {
136
136
 
137
137
  export default class AffiliationLayout extends CollectionLayout {
138
138
 
139
- static get category() {
140
- return LAYOUT_CATEGORY.AFFILIATION;
141
- }
142
-
143
139
  static get type() {
144
140
  return TYPE;
145
141
  }
@@ -1,4 +1,3 @@
1
- import { LAYOUT_CATEGORY } from '../../constants.js';
2
1
  import TemplateBasedLayout from '../template.js';
3
2
  import { LOGO } from '../svgs.js';
4
3
 
@@ -19,10 +18,6 @@ const DEFAULT_TEMPLATES = Object.freeze({
19
18
 
20
19
  export default class BannerLayout extends TemplateBasedLayout {
21
20
 
22
- static get category() {
23
- return LAYOUT_CATEGORY.BANNER;
24
- }
25
-
26
21
  static get type() {
27
22
  return TYPE;
28
23
  }
@@ -0,0 +1,69 @@
1
+ import { STATUS } from '../../constants.js';
2
+ import { fields } from '../../actor/index.js';
3
+ import TemplateBasedLayout from '../template.js';
4
+
5
+ const TYPE = 'more-button';
6
+ const DEFAULT_CLASSNAME = 'miso-more-button';
7
+
8
+ function root(layout, state) {
9
+ const { className, role, templates } = layout;
10
+ const roleAttr = role ? ` data-role="${role}"` : '';
11
+ const disabledAttr = state.status !== STATUS.READY ? ' disabled' : '';
12
+ return `
13
+ <div class="${className}"${roleAttr}${disabledAttr}>
14
+ ${templates.text(layout)}
15
+ </div>
16
+ `;
17
+ }
18
+
19
+ const DEFAULT_TEMPLATES = Object.freeze({
20
+ root,
21
+ text,
22
+ });
23
+
24
+ function text(layout) {
25
+ return 'More';
26
+ }
27
+
28
+ export default class MoreButtonLayout extends TemplateBasedLayout {
29
+
30
+ static get type() {
31
+ return TYPE;
32
+ }
33
+
34
+ static get defaultTemplates() {
35
+ return DEFAULT_TEMPLATES;
36
+ }
37
+
38
+ static get defaultClassName() {
39
+ return DEFAULT_CLASSNAME;
40
+ }
41
+
42
+ constructor({ className = DEFAULT_CLASSNAME, templates, ...options } = {}) {
43
+ super({
44
+ className,
45
+ templates: { ...DEFAULT_TEMPLATES, ...templates },
46
+ ...options,
47
+ });
48
+ }
49
+
50
+ initialize(view) {
51
+ this._unsubscribes = [
52
+ ...this._unsubscribes,
53
+ view.proxyElement.on('click', (e) => this._handleClick(e)),
54
+ ];
55
+ }
56
+
57
+ _handleClick(event) {
58
+ // only left click
59
+ if (event.button !== 0) {
60
+ return;
61
+ }
62
+ const button = event.target.closest('data-role="more"');
63
+ if (!button || button.disabled) {
64
+ return;
65
+ }
66
+ this._view.hub.trigger(fields.more());
67
+ }
68
+
69
+ }