@miso.ai/client-sdk-ui 1.10.0-beta.0 → 1.10.0-beta.10

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 (90) hide show
  1. package/dist/cjs/actor/autocomplete.js +157 -0
  2. package/dist/cjs/actor/data.js +4 -12
  3. package/dist/cjs/actor/fields.js +8 -0
  4. package/dist/cjs/actor/index.js +7 -0
  5. package/dist/cjs/actor/tracker.js +46 -70
  6. package/dist/cjs/actor/trackers.js +5 -1
  7. package/dist/cjs/actor/utils.js +16 -2
  8. package/dist/cjs/actor/view.js +14 -3
  9. package/dist/cjs/actor/views.js +9 -1
  10. package/dist/cjs/affiliation/index.js +126 -0
  11. package/dist/cjs/combo/ask/options.js +1 -1
  12. package/dist/cjs/combo/base.js +1 -1
  13. package/dist/cjs/constants.js +6 -3
  14. package/dist/cjs/defaults/ask/controls.js +1 -1
  15. package/dist/cjs/defaults/ask/phrases.js +1 -1
  16. package/dist/cjs/defaults/ask/templates.js +35 -4
  17. package/dist/cjs/element/role/index.js +7 -0
  18. package/dist/cjs/element/role/miso-affiliation.js +20 -0
  19. package/dist/cjs/layout/affiliation/affiliation.js +330 -0
  20. package/dist/cjs/layout/banner/banner.js +1 -1
  21. package/dist/cjs/layout/container/container.js +1 -1
  22. package/dist/cjs/layout/error/error.js +1 -1
  23. package/dist/cjs/layout/index.js +7 -0
  24. package/dist/cjs/layout/input/option-list.js +35 -50
  25. package/dist/cjs/layout/input/search-box.js +227 -129
  26. package/dist/cjs/layout/list/cards.js +1 -1
  27. package/dist/cjs/layout/list/carousel.js +5 -3
  28. package/dist/cjs/layout/list/collection.js +44 -49
  29. package/dist/cjs/layout/list/list.js +1 -1
  30. package/dist/cjs/layout/radio/feedback.js +1 -1
  31. package/dist/cjs/layout/radio/radio.js +1 -1
  32. package/dist/cjs/layout/raf.js +1 -1
  33. package/dist/cjs/layout/template.js +1 -1
  34. package/dist/cjs/layout/templates.js +172 -66
  35. package/dist/cjs/layout/text/text.js +1 -1
  36. package/dist/cjs/layout/text/typewriter/index.js +3 -3
  37. package/dist/cjs/layout/trackable.js +124 -0
  38. package/dist/cjs/plugin.js +1 -1
  39. package/dist/cjs/source/index.js +20 -8
  40. package/dist/cjs/util/bindings.js +58 -110
  41. package/dist/cjs/util/states.js +31 -24
  42. package/dist/cjs/util/trackers.js +75 -0
  43. package/dist/cjs/util/viewables.js +55 -0
  44. package/dist/cjs/version.js +1 -1
  45. package/dist/cjs/workflow/ask.js +60 -19
  46. package/dist/cjs/workflow/asks.js +1 -1
  47. package/dist/cjs/workflow/base.js +6 -7
  48. package/dist/cjs/workflow/explore.js +5 -2
  49. package/dist/cjs/workflow/options.js +60 -14
  50. package/dist/cjs/workflow/recommendation.js +2 -0
  51. package/dist/cjs/workflow/search.js +35 -7
  52. package/package.json +2 -2
  53. package/src/actor/autocomplete.js +131 -0
  54. package/src/actor/data.js +3 -10
  55. package/src/actor/fields.js +8 -0
  56. package/src/actor/index.js +1 -0
  57. package/src/actor/tracker.js +39 -62
  58. package/src/actor/trackers.js +3 -1
  59. package/src/actor/utils.js +14 -2
  60. package/src/actor/view.js +12 -1
  61. package/src/actor/views.js +11 -0
  62. package/src/affiliation/index.js +108 -0
  63. package/src/constants.js +10 -1
  64. package/src/defaults/ask/phrases.js +1 -1
  65. package/src/defaults/ask/templates.js +34 -2
  66. package/src/element/role/index.js +1 -0
  67. package/src/element/role/miso-affiliation.js +16 -0
  68. package/src/layout/affiliation/affiliation.js +295 -0
  69. package/src/layout/index.js +1 -0
  70. package/src/layout/input/option-list.js +35 -43
  71. package/src/layout/input/search-box.js +192 -95
  72. package/src/layout/list/carousel.js +4 -2
  73. package/src/layout/list/collection.js +42 -43
  74. package/src/layout/templates.js +127 -46
  75. package/src/layout/text/typewriter/index.js +2 -2
  76. package/src/layout/trackable.js +106 -0
  77. package/src/source/index.js +14 -1
  78. package/src/util/bindings.js +47 -103
  79. package/src/util/states.js +26 -20
  80. package/src/util/trackers.js +69 -0
  81. package/src/util/viewables.js +47 -0
  82. package/src/version.js +1 -1
  83. package/src/workflow/ask.js +37 -3
  84. package/src/workflow/base.js +2 -3
  85. package/src/workflow/explore.js +1 -1
  86. package/src/workflow/options.js +54 -13
  87. package/src/workflow/recommendation.js +1 -0
  88. package/src/workflow/search.js +33 -1
  89. package/dist/cjs/util/tracker.js +0 -348
  90. package/src/util/tracker.js +0 -318
@@ -0,0 +1,295 @@
1
+ import { CarouselItemViewabilityObserver, requestAnimationFrame as raf } from '@miso.ai/commons';
2
+ import { STATUS, LAYOUT_CATEGORY, EVENT_TYPE } from '../../constants.js';
3
+ import CollectionLayout from '../list/collection.js';
4
+ import { affiliation } from '../templates.js';
5
+
6
+ const TYPE = 'affiliation';
7
+ const DEFAULT_CLASSNAME = 'miso-affiliation';
8
+
9
+ function root(layout, state) {
10
+ const { className, role, templates } = layout;
11
+ const { status } = state;
12
+ const roleAttr = role ? ` data-role="${role}"` : '';
13
+ const data = state.value || {};
14
+ const channelAttr = data.channel ? ` data-channel="${data.channel}"` : '';
15
+ return `<div class="${className} ${status}"${roleAttr}${channelAttr}>${status === STATUS.READY ? templates[status](layout, state) : ''}</div>`;
16
+ }
17
+
18
+ function ready(layout, state) {
19
+ const { templates } = layout;
20
+ const items = layout._getItems(state);
21
+
22
+ if (items && items.length > 0) {
23
+ return templates.control(layout, state, 'previous') + templates.body(layout, state) + templates.control(layout, state, 'next');
24
+ } else {
25
+ return templates.empty(layout, state);
26
+ }
27
+ }
28
+
29
+ function control(layout, state, direction) {
30
+ const { className } = layout;
31
+ return `<div class="${className}__control-${direction}" data-role="${direction}"></div>`;
32
+ }
33
+
34
+ function body(layout, state) {
35
+ const { className, templates } = layout;
36
+ const items = layout._getItems(state);
37
+ return `<div class="${className}__body">${templates.list(layout, state, items)}</div>`;
38
+ }
39
+
40
+ function header(layout, state) {
41
+ const { className, templates } = layout;
42
+ let { headerText } = templates;
43
+ if (typeof headerText === 'function') {
44
+ headerText = headerText(layout, state);
45
+ }
46
+ if (!headerText) {
47
+ return '';
48
+ }
49
+ return `<div class="${className}__header"><div class="${className}__header-tab">${headerText}</div></div>`;
50
+ }
51
+
52
+ function item(layout, state, value, index) {
53
+ const { className, templates, options } = layout;
54
+ const { itemType } = options;
55
+ const header = templates.itemHeader(layout, state, value, { index });
56
+ const body = templates[itemType](layout, state, value, { index });
57
+ return `<li class="${className}__item">${header}${body}</li>`;
58
+ }
59
+
60
+ function itemHeader(layout, state, value, meta) {
61
+ const { className, templates } = layout;
62
+ let { headerText } = templates;
63
+ if (typeof headerText === 'function') {
64
+ headerText = headerText(layout, state, value, meta);
65
+ }
66
+ if (!headerText) {
67
+ return '';
68
+ }
69
+ return `<div class="${className}__item-header"><div class="${className}__item-header-tab">${headerText}</div></div>`;
70
+ }
71
+
72
+ const DEFAULT_TEMPLATES = Object.freeze({
73
+ root,
74
+ [STATUS.READY]: ready,
75
+ control,
76
+ body,
77
+ header,
78
+ item,
79
+ itemHeader,
80
+ affiliation,
81
+ ctaText: 'View',
82
+ headerText: 'Featured',
83
+ });
84
+
85
+ const INHERITED_DEFAULT_TEMPLATES = Object.freeze({
86
+ ...CollectionLayout.defaultTemplates,
87
+ ...DEFAULT_TEMPLATES,
88
+ });
89
+
90
+ function normalizeAutoplayOptions(options) {
91
+ if (options === true) {
92
+ options = { interval: 5000 };
93
+ } else if (typeof options === 'number') {
94
+ if (options <= 0) {
95
+ options = false;
96
+ } else {
97
+ options = { interval: Math.max(options, 500) };
98
+ }
99
+ }
100
+ return options;
101
+ }
102
+
103
+ function autoplayOptionsEquals(a, b) {
104
+ if (a === b) {
105
+ return true;
106
+ }
107
+ if (!a || !b) {
108
+ return false;
109
+ }
110
+ return a.interval === b.interval;
111
+ }
112
+
113
+ export default class AffiliationLayout extends CollectionLayout {
114
+
115
+ static get category() {
116
+ return LAYOUT_CATEGORY.AFFILIATION;
117
+ }
118
+
119
+ static get type() {
120
+ return TYPE;
121
+ }
122
+
123
+ static get defaultTemplates() {
124
+ return INHERITED_DEFAULT_TEMPLATES;
125
+ }
126
+
127
+ static get defaultClassName() {
128
+ return DEFAULT_CLASSNAME;
129
+ }
130
+
131
+ constructor({ className = DEFAULT_CLASSNAME, templates, autoplay, ...options } = {}) {
132
+ super({ className, itemType: 'affiliation', templates: { ...DEFAULT_TEMPLATES, ...templates }, ...options });
133
+ this._itemCount = undefined;
134
+ this._itemIndex = 0;
135
+ this._displayedCount = undefined;
136
+ this._displayedIndex = undefined;
137
+ this._viewable = undefined;
138
+ this._autoplay = {};
139
+
140
+ this.autoplay(autoplay);
141
+ }
142
+
143
+ initialize(view) {
144
+ super.initialize(view);
145
+ const { viewable: options } = this._view.trackerOptions || {};
146
+ this._viewable = new CarouselItemViewabilityObserver(this._onViewable.bind(this), options);
147
+ }
148
+
149
+ autoplay(options = true) {
150
+ options = normalizeAutoplayOptions(options);
151
+ if (autoplayOptionsEquals(options, this._autoplay.options)) {
152
+ return;
153
+ }
154
+ if (this._autoplay.options) {
155
+ clearInterval(this._autoplay.intervalId);
156
+ }
157
+ this._autoplay = { options };
158
+ // TODO: use slower transition speed for autoplay
159
+ // TODO: pause on hover
160
+ // TODO: cooldown after manual interaction
161
+ if (options) {
162
+ this._autoplay.intervalId = setInterval(() => this.next(), options.interval);
163
+ }
164
+ }
165
+
166
+ get itemIndex() {
167
+ return this._itemIndex;
168
+ }
169
+
170
+ set itemIndex(index) {
171
+ if (this._itemCount === undefined) {
172
+ return;
173
+ }
174
+ if (index < 0) {
175
+ index = this._itemCount - 1;
176
+ } else if (index >= this._itemCount) {
177
+ index = 0;
178
+ }
179
+ this._itemIndex = index;
180
+ raf(() => this._syncItemIndex());
181
+ }
182
+
183
+ next() {
184
+ if (this._itemCount === undefined) {
185
+ return;
186
+ }
187
+ this.itemIndex++;
188
+ }
189
+
190
+ previous() {
191
+ if (this._itemCount === undefined) {
192
+ return;
193
+ }
194
+ this.itemIndex--;
195
+ }
196
+
197
+ _getItems(state) {
198
+ const { products } = state.value || {};
199
+ return products;
200
+ }
201
+
202
+ _render(element, states, controls) {
203
+ super._render(element, states, controls);
204
+
205
+ // update item count, but raf to display as attribute
206
+ const items = this._getItems(states.state);
207
+ this._itemCount = items ? items.length : undefined;
208
+
209
+ raf(() => {
210
+ this._syncItemCount();
211
+ this._syncItemIndex();
212
+ });
213
+ }
214
+
215
+ _syncElement(element) {
216
+ const oldElement = this._element;
217
+ super._syncElement(element);
218
+ this._syncViewableElement(oldElement, element);
219
+ }
220
+
221
+ _syncViewableElement(oldElement, newElement) {
222
+ if (oldElement === newElement) {
223
+ return;
224
+ }
225
+ oldElement && this._viewable.unobserve(oldElement);
226
+ newElement && this._viewable.observe(newElement);
227
+ }
228
+
229
+ _syncItemCount() {
230
+ const itemCount = this._itemCount;
231
+ if (itemCount === this._displayedCount) {
232
+ return;
233
+ }
234
+ let { element } = this._view;
235
+ element = element.firstElementChild;
236
+ if (!element) {
237
+ return;
238
+ }
239
+ this._displayedCount = itemCount;
240
+ if (itemCount === undefined) {
241
+ element.removeAttribute('data-item-count');
242
+ } else {
243
+ element.setAttribute('data-item-count', itemCount);
244
+ }
245
+ }
246
+
247
+ _syncItemIndex() {
248
+ const index = this._itemIndex;
249
+ if (index === this._displayedIndex) {
250
+ return;
251
+ }
252
+ const { element } = this._view;
253
+ const listElement = element && this._getListElement(element);
254
+ if (!listElement) {
255
+ return;
256
+ }
257
+ this._displayedIndex = index;
258
+ listElement.style.left = `${-index * 100}%`;
259
+ this._viewable.display(index);
260
+ this._view._events.emit('display', { index });
261
+ }
262
+
263
+ _onViewable(index) {
264
+ const item = this._getRenderedItemData(index);
265
+ if (!item) {
266
+ return;
267
+ }
268
+ this._view._track(EVENT_TYPE.VIEWABLE, [item]);
269
+ }
270
+
271
+ _getRenderedItemData(index) {
272
+ const { value } = this._rendered.get(this._element) || {};
273
+ if (!value) {
274
+ return undefined;
275
+ }
276
+ const { products = [] } = value;
277
+ return products[index];
278
+ }
279
+
280
+ _trackViewables() {} // omit default behavior for we want to track by other means
281
+
282
+ _onClick(event) {
283
+ super._onClick(event);
284
+ const element = event.target;
285
+ if (element.closest(`[data-role="previous"]`)) {
286
+ this.previous();
287
+ return;
288
+ }
289
+ if (element.closest(`[data-role="next"]`)) {
290
+ this.next();
291
+ return;
292
+ }
293
+ }
294
+
295
+ }
@@ -11,3 +11,4 @@ export { default as OptionListLayout } from './input/option-list.js';
11
11
  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
+ export { default as AffiliationLayout } from './affiliation/affiliation.js';
@@ -1,9 +1,8 @@
1
- import { defineValues, escapeHtml } from '@miso.ai/commons';
1
+ import { escapeHtml } from '@miso.ai/commons';
2
2
  import { LAYOUT_CATEGORY } from '../../constants.js';
3
3
  import { fields } from '../../actor/index.js';
4
4
  import TemplateBasedLayout from '../template.js';
5
-
6
- const OPTION_VALUE = Symbol.for('miso.option-value');
5
+ import { makeTrackable } from '../trackable.js';
7
6
 
8
7
  const TYPE = 'option-list';
9
8
  const DEFAULT_CLASSNAME = 'miso-option-list';
@@ -64,11 +63,7 @@ export default class OptionListLayout extends TemplateBasedLayout {
64
63
  templates: { ...DEFAULT_TEMPLATES, ...templates },
65
64
  ...options,
66
65
  });
67
- defineValues(this, {
68
- bindings: Object.freeze({
69
- list: this._listBindings.bind(this),
70
- }),
71
- });
66
+ this._initTrackable();
72
67
  }
73
68
 
74
69
  initialize(view) {
@@ -89,59 +84,48 @@ export default class OptionListLayout extends TemplateBasedLayout {
89
84
 
90
85
  _render(element, data, controls) {
91
86
  super._render(element, data, controls);
92
- this._syncOptionValues(element, data);
87
+ const { state } = data;
88
+ this._syncBindings(element, state);
89
+ this._syncOptionValues(element, state);
93
90
  }
94
91
 
95
- _syncOptionValues(element, { state }) {
96
- if (!element) {
97
- return;
98
- }
99
- const optionsElement = this._getOptionsElement(element);
92
+ _unrender() {
93
+ this._clearBindings();
94
+ }
95
+
96
+ _syncOptionValues(element, state) {
97
+ const optionsElement = element && this._getOptionsElement(element);
100
98
  if (!optionsElement) {
101
99
  return;
102
100
  }
103
- const optionValues = state.value || [];
101
+ const optionValues = this._getItems(state) || [];
104
102
  optionsElement.classList[optionValues.length === 0 ? 'add' : 'remove']('empty');
105
- let i = 0;
106
- for (const optionElement of this._listOptionElements(optionsElement)) {
107
- optionElement[OPTION_VALUE] = optionValues[i++];
108
- }
109
103
  }
110
104
 
111
- _listBindings(rootElement) {
112
- if (!rootElement) {
113
- return [];
114
- }
115
- const optionsElement = this._getOptionsElement(rootElement);
116
- const bindings = [];
117
- for (const element of this._listOptionElements(optionsElement)) {
118
- const value = element[OPTION_VALUE];
119
- if (!value) {
120
- continue;
121
- }
122
- const key = value.text || value;
123
- bindings.push({ key, value, element });
124
- }
125
- return bindings;
105
+ _getItems(state) {
106
+ return state.value;
126
107
  }
127
108
 
128
109
  _getOptionsElement(element) {
129
110
  return element.querySelector(`[data-role="options"]`);
130
111
  }
131
112
 
132
- _listOptionElements(optionsElement) {
133
- return optionsElement ? Array.from(optionsElement.querySelectorAll(`[data-role="option"]`)) : [];
113
+ _getItemElements(element) {
114
+ return element ? Array.from(element.querySelectorAll(`[data-role="option"]`)) : [];
134
115
  }
135
116
 
136
- _handleClick(e) {
137
- for (let element = e.target; element && element !== this._view.element; element = element.parentElement) {
138
- const option = element[OPTION_VALUE];
139
- if (!option) {
140
- continue;
141
- }
142
- this._submit(option.text);
117
+ _handleClick(event) {
118
+ const element = event.target.closest(`[data-role="option"]`);
119
+ if (!element) {
120
+ return;
121
+ }
122
+ const binding = this._bindings.get(element);
123
+ const option = binding && binding.value;
124
+ if (!option) {
143
125
  return;
144
126
  }
127
+ this._trackClick(event, binding);
128
+ this._submit(option.text);
145
129
  }
146
130
 
147
131
  async _submit(value) {
@@ -152,4 +136,12 @@ export default class OptionListLayout extends TemplateBasedLayout {
152
136
  this._view.hub.trigger(fields.query(), { q: value });
153
137
  }
154
138
 
139
+ destroy() {
140
+ this._destroyTrackable();
141
+ this._view = undefined;
142
+ super.destroy();
143
+ }
144
+
155
145
  }
146
+
147
+ makeTrackable(OptionListLayout.prototype);