@miso.ai/client-sdk-ui 1.11.4-beta.0 → 1.11.4-beta.2

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 (91) hide show
  1. package/dist/cjs/actor/fields.js +0 -4
  2. package/dist/cjs/actor/filters.js +57 -11
  3. package/dist/cjs/actor/tracker.js +1 -6
  4. package/dist/cjs/actor/view.js +6 -8
  5. package/dist/cjs/actor/views.js +10 -26
  6. package/dist/cjs/constants.js +12 -2
  7. package/dist/cjs/element/role/index.js +7 -0
  8. package/dist/cjs/element/role/miso-sort.js +20 -0
  9. package/dist/cjs/layout/affiliation/affiliation.js +1 -1
  10. package/dist/cjs/layout/container/container.js +2 -2
  11. package/dist/cjs/layout/index.js +7 -0
  12. package/dist/cjs/layout/input/facets.js +2 -2
  13. package/dist/cjs/layout/input/search-box.js +1 -1
  14. package/dist/cjs/layout/input/select.js +150 -0
  15. package/dist/cjs/layout/raf.js +2 -4
  16. package/dist/cjs/layout/text/typewriter/index.js +3 -2
  17. package/dist/cjs/layout/trackable.js +3 -3
  18. package/dist/cjs/layout/triggerable.js +1 -1
  19. package/dist/cjs/util/trackers.js +2 -0
  20. package/dist/cjs/version.js +1 -1
  21. package/dist/cjs/workflow/answer-based.js +19 -3
  22. package/dist/cjs/workflow/ask.js +4 -1
  23. package/dist/cjs/workflow/base.js +27 -4
  24. package/dist/cjs/workflow/context.js +3 -3
  25. package/dist/cjs/workflow/explore.js +2 -2
  26. package/dist/cjs/workflow/hybrid-search-answer.js +9 -7
  27. package/dist/cjs/workflow/hybrid-search-results.js +18 -16
  28. package/dist/cjs/workflow/hybrid-search-views.js +5 -12
  29. package/dist/cjs/workflow/hybrid-search.js +9 -1
  30. package/dist/cjs/workflow/options/api.js +56 -0
  31. package/dist/cjs/workflow/options/configurable.js +49 -0
  32. package/dist/cjs/workflow/options/data-processor.js +23 -0
  33. package/dist/cjs/workflow/options/filters.js +18 -0
  34. package/dist/cjs/workflow/options/index.js +115 -0
  35. package/dist/cjs/workflow/options/interactions.js +44 -0
  36. package/dist/cjs/workflow/options/layouts.js +83 -0
  37. package/dist/cjs/workflow/options/options.js +154 -0
  38. package/dist/cjs/workflow/options/pagination.js +28 -0
  39. package/dist/cjs/workflow/options/roles.js +17 -0
  40. package/dist/cjs/workflow/options/trackers.js +39 -0
  41. package/dist/cjs/workflow/options/utils.js +33 -0
  42. package/dist/cjs/workflow/processors.js +39 -33
  43. package/dist/cjs/workflow/recommendation.js +2 -2
  44. package/dist/cjs/workflow/search-based.js +59 -22
  45. package/package.json +2 -2
  46. package/src/actor/fields.js +0 -4
  47. package/src/actor/filters.js +53 -12
  48. package/src/actor/tracker.js +1 -6
  49. package/src/actor/view.js +7 -8
  50. package/src/actor/views.js +9 -30
  51. package/src/constants.js +11 -0
  52. package/src/element/role/index.js +1 -0
  53. package/src/element/role/miso-sort.js +16 -0
  54. package/src/layout/affiliation/affiliation.js +1 -1
  55. package/src/layout/container/container.js +2 -2
  56. package/src/layout/index.js +1 -0
  57. package/src/layout/input/facets.js +3 -2
  58. package/src/layout/input/search-box.js +1 -1
  59. package/src/layout/input/select.js +142 -0
  60. package/src/layout/raf.js +2 -2
  61. package/src/layout/text/typewriter/index.js +3 -2
  62. package/src/layout/trackable.js +3 -3
  63. package/src/layout/triggerable.js +1 -1
  64. package/src/util/trackers.js +2 -0
  65. package/src/version.js +1 -1
  66. package/src/workflow/answer-based.js +16 -2
  67. package/src/workflow/ask.js +4 -1
  68. package/src/workflow/base.js +22 -1
  69. package/src/workflow/context.js +1 -1
  70. package/src/workflow/explore.js +1 -1
  71. package/src/workflow/hybrid-search-answer.js +8 -3
  72. package/src/workflow/hybrid-search-results.js +21 -7
  73. package/src/workflow/hybrid-search-views.js +4 -12
  74. package/src/workflow/hybrid-search.js +12 -1
  75. package/src/workflow/options/api.js +46 -0
  76. package/src/workflow/options/configurable.js +48 -0
  77. package/src/workflow/options/data-processor.js +15 -0
  78. package/src/workflow/options/filters.js +10 -0
  79. package/src/workflow/options/index.js +10 -0
  80. package/src/workflow/options/interactions.js +36 -0
  81. package/src/workflow/options/layouts.js +73 -0
  82. package/src/workflow/options/options.js +169 -0
  83. package/src/workflow/options/pagination.js +18 -0
  84. package/src/workflow/options/roles.js +12 -0
  85. package/src/workflow/options/trackers.js +32 -0
  86. package/src/workflow/options/utils.js +26 -0
  87. package/src/workflow/processors.js +30 -34
  88. package/src/workflow/recommendation.js +1 -1
  89. package/src/workflow/search-based.js +58 -13
  90. package/dist/cjs/workflow/options.js +0 -448
  91. package/src/workflow/options.js +0 -433
@@ -12,43 +12,84 @@ function mergeState(state = {}, update = {}) {
12
12
  }));
13
13
  }
14
14
 
15
+ function optionsToState({ sort } = {}) {
16
+ // TODO: facets
17
+ return trimObj({
18
+ facets: Object.freeze({}),
19
+ sort: sortOptionsToState(sort),
20
+ });
21
+ }
22
+
23
+ function sortOptionsToState(sort) {
24
+ if (!sort) {
25
+ return undefined;
26
+ }
27
+ const { options = [] } = sort;
28
+ for (const option of options) {
29
+ if (option.default) {
30
+ return Object.freeze(option);
31
+ }
32
+ }
33
+ return Object.freeze(options[0]);
34
+ }
35
+
15
36
  export default class Filters {
16
37
 
17
38
  constructor(views, { facets: facetsOptions } = {}) {
18
39
  this._views = views;
19
40
  this._hub = views._hub;
20
- this._states = this._initialStates = mergeState({}); // TODO: support custom initial states
21
41
  const error = this._error = this._error.bind(this);
22
42
  this._events = new EventEmitter({ target: this, error });
23
43
  defineValues(this, { facets: new Facets(this, facetsOptions) });
44
+
45
+ this._unsubscribes = [
46
+ views._options.on('filters', () => this._syncOptions()),
47
+ ];
48
+
49
+ this._syncOptions();
50
+ this._states = undefined;
51
+ }
52
+
53
+ _syncOptions() {
54
+ const options = this._views._options.resolved.filters;
55
+ this._initialStates = optionsToState(options);
56
+ this.apply({ silent: true });
24
57
  }
25
58
 
26
- get states() {
27
- return this._states;
59
+ _getStates() {
60
+ return this._states || this._initialStates;
28
61
  }
29
62
 
30
63
  update(updates) {
31
- const oldStates = this._states;
32
- const states = this._states = mergeState({ ...this._states, ...updates });
64
+ const oldStates = this._getStates();
65
+ this._states = mergeState({ ...oldStates, ...updates });
66
+ const states = this._getStates();
33
67
  this._events.emit('update', { updates, states, oldStates });
34
68
  }
35
69
 
36
70
  reset() {
37
- const oldStates = this._states;
38
- const states = this._states = this._initialStates;
71
+ const oldStates = this.states;
72
+ this._states = undefined;
73
+ const states = this._getStates();
39
74
  this._events.emit('reset', { states, oldStates });
40
75
  }
41
76
 
42
- apply() {
43
- this._hub.update(fields.filters(), this._states);
44
- this._events.emit('apply');
77
+ apply({ silent = false } = {}) {
78
+ const states = this._getStates();
79
+ this._hub.update(fields.filters(), states, { silent });
80
+ !silent && this._events.emit('apply');
45
81
  }
46
82
 
47
83
  _error(error) {
48
84
  this._views._error(error);
49
85
  }
50
86
 
51
- _destroy() {}
87
+ _destroy() {
88
+ for (const unsubscribe of this._unsubscribes) {
89
+ unsubscribe();
90
+ }
91
+ this._unsubscribes = [];
92
+ }
52
93
 
53
94
  }
54
95
 
@@ -87,7 +128,7 @@ class Facets {
87
128
  }
88
129
 
89
130
  _select(field, value) {
90
- const values = this._options.multiple ? [...(this._filters._states.facets[field] || []), value] : [value];
131
+ const values = this._options.multivalued ? [...(this._filters._states.facets[field] || []), value] : [value];
91
132
  values.sort();
92
133
  this._filters.update({ facets: { [field]: values } });
93
134
  }
@@ -7,20 +7,15 @@ const VALUELESS_ITEMS = [Symbol('valueless')];
7
7
 
8
8
  export default class Tracker {
9
9
 
10
- constructor({ hub, role, valueless = false, options }) {
10
+ constructor({ hub, role, valueless = false }) {
11
11
  this._hub = hub;
12
12
  this._role = role;
13
13
  this._valueless = !!valueless;
14
- this._options = options;
15
14
  // TODO: know item type
16
15
 
17
16
  this._states = new States(this._getSessionId());
18
17
  }
19
18
 
20
- get options() {
21
- return typeof this._options === 'function' ? this._options() : this._options;
22
- }
23
-
24
19
  getState(item) {
25
20
  return this._states.getFullState(item);
26
21
  }
package/src/actor/view.js CHANGED
@@ -97,13 +97,17 @@ export default class ViewActor {
97
97
  }
98
98
  if (!this._tracker) {
99
99
  const { hub } = this;
100
- this._tracker = new Tracker({ hub, role, options: () => this._getTrackerOptions() });
100
+ this._tracker = new Tracker({ hub, role });
101
101
  }
102
102
  return this._tracker;
103
103
  }
104
104
 
105
105
  get workflowOptions() {
106
- return this._views._getWorkflowOptions();
106
+ return this._views._options.resolved;
107
+ }
108
+
109
+ get trackerOptions() {
110
+ return this._views._getTrackerOptions(this.role);
107
111
  }
108
112
 
109
113
  get filters() {
@@ -154,15 +158,10 @@ export default class ViewActor {
154
158
  this.hub.update(fields.view(role), state, { silent });
155
159
  }
156
160
 
157
- _getTrackerOptions() {
158
- // used by tracker to expose tracker options to layout
159
- return this._views._getTrackerOptions(this.role);
160
- }
161
-
162
161
  _sliceData(data) {
163
162
  const { value, error, status, meta, ...rest } = data;
164
163
  const sliced = {
165
- value: this._mapping(data),
164
+ value: this._mapping(data, this),
166
165
  status,
167
166
  data: value,
168
167
  meta,
@@ -44,15 +44,9 @@ export default class ViewsActor {
44
44
  this._unsubscribes = [
45
45
  () => window.removeEventListener('resize', syncSize),
46
46
  hub.on(fields.data(), data => this.refresh({ data })),
47
- options.on('autocomplete', () => this._syncAutocompleteOptions()),
48
- options.on('pagination', () => this._syncPaginationOptions()),
49
- options.on('trackers', () => this._syncTrackers()),
50
47
  options.on('layouts', () => this._syncLayouts()),
51
48
  ];
52
49
 
53
- this._syncAutocompleteOptions();
54
- this._syncPaginationOptions();
55
- this._syncTrackers();
56
50
  this._syncLayouts();
57
51
  }
58
52
 
@@ -150,13 +144,16 @@ export default class ViewsActor {
150
144
  } else {
151
145
  for (let [role, [name, options]] of Object.entries(layouts)) {
152
146
  // TODO: try to omit if unchanged
153
- const fn = () => this._layoutFactory.create(name, { ...options, role, workflow: this._workflowName, workflowOptions: this._getWorkflowOptions() });
147
+ const fn = () => this._layoutFactory.create(name, { ...options, role, workflow: this._workflowName });
154
148
  if (role === ROLE.CONTAINER) {
155
149
  for (const view of this._containers.values()) {
156
150
  view.layout = fn();
157
151
  }
158
152
  } else {
159
- this.get(role).layout = fn();
153
+ const view = this.get(role);
154
+ if (view) {
155
+ view.layout = fn();
156
+ }
160
157
  }
161
158
  }
162
159
  }
@@ -168,11 +165,11 @@ export default class ViewsActor {
168
165
  return undefined;
169
166
  }
170
167
  const [name, options] = args;
171
- return this._layoutFactory.create(name, { ...options, role, workflow: this._workflowName, workflowOptions: this._getWorkflowOptions() });
168
+ return this._layoutFactory.create(name, { ...options, role, workflow: this._workflowName });
172
169
  }
173
170
 
174
171
  get(role) {
175
- return this._views[role] || (this._views[role] = new ViewActor(this, role));
172
+ return this._views[role];
176
173
  }
177
174
 
178
175
  get views() {
@@ -218,12 +215,9 @@ export default class ViewsActor {
218
215
  return data || this._hub.states[fields.data()];
219
216
  }
220
217
 
221
- _syncTrackers() {
222
- this._trackerOptions = this._options.resolved.trackers;
223
- }
224
-
225
218
  _getTrackerOptions(role) {
226
- return normalizeTrackerOptions((this._trackerOptions && this._trackerOptions[role]) || false);
219
+ const allTrackerOptions = this._options.resolved.trackers;
220
+ return normalizeTrackerOptions((allTrackerOptions && allTrackerOptions[role]) || false);
227
221
  }
228
222
 
229
223
  _getContainerTracker() {
@@ -235,21 +229,6 @@ export default class ViewsActor {
235
229
  return this._containerTracker;
236
230
  }
237
231
 
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
-
253
232
  _error(e) {
254
233
  // TODO: hub trigger error event
255
234
  console.error(e);
package/src/constants.js CHANGED
@@ -24,6 +24,7 @@ export const ROLE = Object.freeze({
24
24
  FACETS: 'facets',
25
25
  TOTAL: 'total',
26
26
  MORE: 'more',
27
+ SORT: 'sort',
27
28
  ERROR: 'error',
28
29
  });
29
30
 
@@ -114,3 +115,13 @@ export function validateTrackingStatus(value) {
114
115
  throw new Error(`Unrecognized tracking status: ${value}`);
115
116
  }
116
117
  }
118
+
119
+ export const WORKFLOW_CONFIGURABLE = Object.freeze({
120
+ API: 'api',
121
+ LAYOUTS: 'layouts',
122
+ DATA_PROCESSOR: 'dataProcessor',
123
+ TRACKERS: 'trackers',
124
+ INTERACTIONS: 'interactions',
125
+ FILTERS: 'filters',
126
+ PAGINATION: 'pagination',
127
+ });
@@ -14,4 +14,5 @@ export { default as MisoKeywordsElement } from './miso-keywords.js';
14
14
  export { default as MisoTotalElement } from './miso-total.js';
15
15
  export { default as MisoFacetsElement } from './miso-facets.js';
16
16
  export { default as MisoMoreElement } from './miso-more.js';
17
+ export { default as MisoSortElement } from './miso-sort.js';
17
18
  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 MisoSortElement extends MisoComponentElement {
5
+
6
+ constructor() {
7
+ super({
8
+ role: ROLE.SORT,
9
+ });
10
+ }
11
+
12
+ static get tagName() {
13
+ return 'miso-sort';
14
+ }
15
+
16
+ }
@@ -165,7 +165,7 @@ export default class AffiliationLayout extends CollectionLayout {
165
165
 
166
166
  initialize(view) {
167
167
  super.initialize(view);
168
- const { viewable: options } = this._view.tracker.options || {};
168
+ const { viewable: options } = this._view.trackerOptions || {};
169
169
  this._viewable = new CarouselItemViewabilityObserver(this._onViewable.bind(this), options);
170
170
  }
171
171
 
@@ -82,7 +82,7 @@ export default class ContainerLayout extends RafLayout {
82
82
  }
83
83
 
84
84
  _trackImpression() {
85
- const { impression: options } = this._view.tracker.options || {};
85
+ const { impression: options } = this._view.trackerOptions || {};
86
86
  if (!options) {
87
87
  return;
88
88
  }
@@ -94,7 +94,7 @@ export default class ContainerLayout extends RafLayout {
94
94
  }
95
95
 
96
96
  async _trackViewable(element) {
97
- const { viewable: options } = this._view.tracker.options || {};
97
+ const { viewable: options } = this._view.trackerOptions || {};
98
98
  if (!options) {
99
99
  return;
100
100
  }
@@ -13,4 +13,5 @@ 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
15
  export { default as FacetsLayout } from './input/facets.js';
16
+ export { default as SelectLayout } from './input/select.js';
16
17
  export { default as MoreButtonLayout } from './button/more-button.js';
@@ -1,4 +1,5 @@
1
1
  import { escapeHtml } from '@miso.ai/commons';
2
+ import { fields } from '../../actor/index.js';
2
3
  import TemplateBasedLayout from '../template.js';
3
4
  import Bindings from '../../util/bindings.js';
4
5
 
@@ -109,7 +110,7 @@ export default class FacetsLayout extends TemplateBasedLayout {
109
110
  }
110
111
 
111
112
  initialize(view) {
112
- const { proxyElement, hub, filters } = view;
113
+ const { proxyElement, filters } = view;
113
114
  this._unsubscribes = [
114
115
  ...this._unsubscribes,
115
116
  proxyElement.on('click', (e) => this._handleClick(e)),
@@ -132,7 +133,7 @@ export default class FacetsLayout extends TemplateBasedLayout {
132
133
  }
133
134
 
134
135
  _syncSelections() {
135
- const selectedValues = getSelectedValues(this._view.filters.states.facets);
136
+ const selectedValues = getSelectedValues(this._view.hub.states[fields.filters()].facets);
136
137
  for (const { value: { field, value }, element } of this._bindings.entries) {
137
138
  this._setSelected(element, selectedValues[field] && selectedValues[field].has(value));
138
139
  }
@@ -338,7 +338,7 @@ export default class SearchBoxLayout extends TemplateBasedLayout {
338
338
  }
339
339
 
340
340
  _trackSubmit(value) {
341
- const { [EVENT_TYPE.SUBMIT]: options } = this._view.tracker.options || {};
341
+ const { [EVENT_TYPE.SUBMIT]: options } = this._view.trackerOptions || {};
342
342
  if (!options) {
343
343
  return;
344
344
  }
@@ -0,0 +1,142 @@
1
+ import { escapeHtml, kebabOrSnakeToHuman } from '@miso.ai/commons';
2
+ import TemplateBasedLayout from '../template.js';
3
+ import { makeTrackable } from '../trackable.js';
4
+
5
+ const TYPE = 'select';
6
+ const DEFAULT_CLASSNAME = 'miso-select';
7
+
8
+ function root(layout, state) {
9
+ const { className, role, templates } = layout;
10
+ const roleAttr = role ? `data-role="${role}"` : '';
11
+ const values = layout._getItems(state);
12
+ const selected = values.find(value => value.selected) || values[0];
13
+ return `
14
+ <div class="${className}" ${roleAttr}>
15
+ ${templates.button(layout, selected)}
16
+ ${templates.options(layout, state)}
17
+ </div>`;
18
+ }
19
+
20
+ function button(layout, value) {
21
+ const { className, templates } = layout;
22
+ return `<button class="${className}__button" data-role="button" tabindex="0">${templates.text(layout, value)}</button>`;
23
+ }
24
+
25
+ function options(layout, state) {
26
+ const { className, templates } = layout;
27
+ const values = layout._getItems(state);
28
+ return `<ul class="${className}__options">${values.map((value, index) => templates.option(layout, value, { index })).join('')}</ul>`;
29
+ }
30
+
31
+ function option(layout, value) {
32
+ const { className, templates } = layout;
33
+ const selectedClass = value.selected ? 'selected' : '';
34
+ return `<li class="${className}__option ${selectedClass}" data-role="option" tabindex="0">${templates.text(layout, value)}</li>`;
35
+ }
36
+
37
+ function text(layout, value) {
38
+ return escapeHtml(value && value.text && kebabOrSnakeToHuman(value.text) || value || '');
39
+ }
40
+
41
+ const DEFAULT_TEMPLATES = Object.freeze({
42
+ root,
43
+ button,
44
+ options,
45
+ option,
46
+ text,
47
+ });
48
+
49
+ export default class SelectLayout extends TemplateBasedLayout {
50
+
51
+ static get type() {
52
+ return TYPE;
53
+ }
54
+
55
+ static get defaultTemplates() {
56
+ return DEFAULT_TEMPLATES;
57
+ }
58
+
59
+ static get defaultClassName() {
60
+ return DEFAULT_CLASSNAME;
61
+ }
62
+
63
+ constructor({ className = DEFAULT_CLASSNAME, templates, ...options } = {}) {
64
+ super({
65
+ className,
66
+ templates: { ...DEFAULT_TEMPLATES, ...templates },
67
+ ...options,
68
+ });
69
+ this._initTrackable();
70
+ }
71
+
72
+ initialize(view) {
73
+ const { proxyElement } = view;
74
+ this._unsubscribes = [
75
+ ...this._unsubscribes,
76
+ proxyElement.on('click', (e) => this._handleClick(e)),
77
+ ];
78
+ }
79
+
80
+ _afterRender(element, state) {
81
+ this._syncBindings(element, state);
82
+
83
+ }
84
+
85
+ _unrender() {
86
+ this._clearBindings();
87
+ }
88
+
89
+ _getItems(state) {
90
+ return state.value || [];
91
+ }
92
+
93
+ _getOptionsElement(element) {
94
+ return element.querySelector(`[data-role="options"]`);
95
+ }
96
+
97
+ _getItemElements(element) {
98
+ return element ? Array.from(element.querySelectorAll(`[data-role="option"]`)) : [];
99
+ }
100
+
101
+ _handleClick(event) {
102
+ if (!this._element) {
103
+ return;
104
+ }
105
+ for (let element = event.target; element && element !== this._element; element = element.parentElement) {
106
+ const { role } = element.dataset;
107
+ switch (role) {
108
+ case 'button':
109
+ this._toggle();
110
+ break;
111
+ case 'option':
112
+ const binding = this._bindings.get(element);
113
+ this._select(binding && binding.value);
114
+ break;
115
+ }
116
+ }
117
+ }
118
+
119
+ _toggle() {
120
+ if (!this._element) {
121
+ return;
122
+ }
123
+ const child = this._element.children[0];
124
+ child && child.classList.toggle('open');
125
+ }
126
+
127
+ async _select(value) {
128
+ if (!value) {
129
+ return;
130
+ }
131
+ // TODO: reflect selection right away
132
+ this._view._events.emit('select', { value });
133
+ }
134
+
135
+ destroy() {
136
+ this._destroyTrackable();
137
+ super.destroy();
138
+ }
139
+
140
+ }
141
+
142
+ makeTrackable(SelectLayout.prototype);
package/src/layout/raf.js CHANGED
@@ -2,8 +2,8 @@ import { defineValues, requestAnimationFrame as raf, capture } from '@miso.ai/co
2
2
 
3
3
  export default class RafLayout {
4
4
 
5
- constructor({ role, workflow, workflowOptions, ...options } = {}) {
6
- defineValues(this, { role, workflow, workflowOptions, options });
5
+ constructor({ role, workflow, ...options } = {}) {
6
+ defineValues(this, { role, workflow, options });
7
7
  this._unsubscribes = [];
8
8
  this._rendered = new WeakMap();
9
9
  this._element = undefined;
@@ -130,7 +130,8 @@ export default class TypewriterLayout extends ProgressiveLayout {
130
130
  const container = containerElement(this, element, state);
131
131
  // TODO: distinguish between first render and element replacement
132
132
  let result;
133
- if (!rendered || state.streak !== rendered.streak) {
133
+ const newStreak = !rendered || state.streak !== rendered.streak;
134
+ if (newStreak) {
134
135
  // new typing streak
135
136
  result = this._renderer.clear(container, rendered);
136
137
  } else {
@@ -141,7 +142,7 @@ export default class TypewriterLayout extends ProgressiveLayout {
141
142
 
142
143
  const ongoing = state.status === STATUS.READY && !result.done;
143
144
  // silence on consecutive ongoing renders
144
- const silent = ongoing && (!rendered || rendered.status === STATUS.READY);
145
+ const silent = ongoing && (!rendered || rendered.status === STATUS.READY) && !newStreak;
145
146
  notifyUpdate({ ongoing }, { silent });
146
147
 
147
148
  ongoing && loop();
@@ -51,7 +51,7 @@ export class TrackableMixin {
51
51
  if (entries.length === 0) {
52
52
  return;
53
53
  }
54
- const { impression: options } = this._view.tracker.options || {};
54
+ const { impression: options } = this._view.trackerOptions || {};
55
55
  if (!options) {
56
56
  return;
57
57
  }
@@ -68,7 +68,7 @@ export class TrackableMixin {
68
68
  if (entries.length === 0) {
69
69
  return;
70
70
  }
71
- const { viewable: options } = this._view.tracker.options || {};
71
+ const { viewable: options } = this._view.trackerOptions || {};
72
72
  if (!options) {
73
73
  return;
74
74
  }
@@ -78,7 +78,7 @@ export class TrackableMixin {
78
78
  }
79
79
 
80
80
  _trackClick(event, binding) {
81
- const { click: options } = this._view.tracker.options || {};
81
+ const { click: options } = this._view.trackerOptions || {};
82
82
  if (validateClick(options, event, binding)) {
83
83
  this._view.tracker.click([binding.value]);
84
84
  }
@@ -16,7 +16,7 @@ export class TriggerableMixin {
16
16
  if (!this.options.infiniteScroll) {
17
17
  return;
18
18
  }
19
- const workflowPaginationOptions = this.workflowOptions.pagination;
19
+ const workflowPaginationOptions = this._view.workflowOptions.pagination;
20
20
  if (!workflowPaginationOptions.active || workflowPaginationOptions.mode !== 'infiniteScroll') {
21
21
  return;
22
22
  }
@@ -1,5 +1,7 @@
1
1
  import { findInAncestors, trimObj } from '@miso.ai/commons';
2
2
 
3
+ // TODO: think about how to incorporate this with workflow options
4
+
3
5
  const DEFAULT_TRACKER_OPTIONS = Object.freeze({
4
6
  impression: Object.freeze({
5
7
  }),
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export default '1.11.4-beta.0';
1
+ export default '1.11.4-beta.2';
@@ -4,7 +4,7 @@ import { fields, FeedbackActor } from '../actor/index.js';
4
4
  import { ROLE, STATUS, ORGANIC_QUESTION_SOURCE } from '../constants.js';
5
5
  import { SearchBoxLayout, TextLayout, ListLayout, TypewriterLayout, FeedbackLayout, AffiliationLayout } from '../layout/index.js';
6
6
  import { processData as processAffiliationData } from '../affiliation/index.js';
7
- import { mergeRolesOptions } from './options.js';
7
+ import { mergeRolesOptions } from './options/index.js';
8
8
  import { writeAnswerStageToMeta, mergeInteraction } from './processors.js';
9
9
  import { enableUseLink } from './use-link.js';
10
10
 
@@ -91,6 +91,10 @@ export default class AnswerBasedWorkflow extends Workflow {
91
91
  super.restart();
92
92
  }
93
93
 
94
+ _shallEmitLifecycleEvent(name, event) {
95
+ return name === 'answer-stage' || super._shallEmitLifecycleEvent(name, event);
96
+ }
97
+
94
98
  _emitInterruptEventIfNecessary() {
95
99
  const state = this._hub.states[fields.view(this._roles.main)] || {};
96
100
  if (state.session && state.status !== STATUS.INITIAL && (state.status !== STATUS.READY || state.ongoing)) {
@@ -101,12 +105,22 @@ export default class AnswerBasedWorkflow extends Workflow {
101
105
  }
102
106
 
103
107
  _onMainViewUpdate(state) {
104
- const { status } = state;
108
+ const { status, session } = state;
105
109
  const done = status === STATUS.READY && !state.ongoing;
106
110
  const erroneous = status === STATUS.ERRONEOUS;
107
111
  const eventName = done ? 'done' : erroneous ? 'error' : status;
112
+ // answer-stage
113
+ const context = this._getSessionContext(session);
114
+ const oldAnswerStage = context.answerStage;
115
+ const newAnswerStage = state.meta && state.meta.answer_stage;
116
+ if (newAnswerStage && newAnswerStage !== oldAnswerStage) {
117
+ this._emitLifecycleEvent('answer-stage', Object.freeze({ ...state, answer_stage: newAnswerStage }));
118
+ context.answerStage = newAnswerStage;
119
+ }
120
+ // ready, done, or error
108
121
  this._emitLifecycleEvent(eventName, state);
109
122
  if (done || erroneous) {
123
+ // finally
110
124
  this._emitLifecycleEvent('finally', state);
111
125
  }
112
126
  }
@@ -4,6 +4,7 @@ import { fields } from '../actor/index.js';
4
4
  import { ROLE, ORGANIC_QUESTION_SOURCE } from '../constants.js';
5
5
  import { OptionListLayout, ListLayout, SearchBoxLayout } from '../layout/index.js';
6
6
  import { mergeInteraction } from './processors.js';
7
+ import { mergeRolesOptions } from './options/index.js';
7
8
 
8
9
  const DEFAULT_API_OPTIONS = Object.freeze({
9
10
  ...AnswerBasedWorkflow.DEFAULT_API_OPTIONS,
@@ -38,7 +39,9 @@ const DEFAULT_OPTIONS = Object.freeze({
38
39
  trackers: DEFAULT_TRACKERS,
39
40
  });
40
41
 
41
- const ROLES_OPTIONS = AnswerBasedWorkflow.ROLES_OPTIONS;
42
+ const ROLES_OPTIONS = mergeRolesOptions(AnswerBasedWorkflow.ROLES_OPTIONS, {
43
+ members: Object.keys(DEFAULT_LAYOUTS),
44
+ });
42
45
 
43
46
  export default class Ask extends AnswerBasedWorkflow {
44
47