@miso.ai/client-sdk-ui 1.10.0-beta.9 → 1.10.1-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/index.js +0 -7
- package/dist/cjs/actor/tracker.js +47 -102
- package/dist/cjs/actor/utils.js +1 -1
- package/dist/cjs/actor/view.js +12 -1
- package/dist/cjs/actor/views.js +24 -2
- package/dist/cjs/asset/svgs.js +14 -4
- package/dist/cjs/constants.js +3 -2
- package/dist/cjs/layout/affiliation/affiliation.js +152 -58
- package/dist/cjs/layout/input/option-list.js +34 -49
- package/dist/cjs/layout/list/collection.js +22 -43
- package/dist/cjs/layout/templates.js +54 -32
- package/dist/cjs/layout/text/typewriter/index.js +12 -7
- package/dist/cjs/layout/trackable.js +124 -0
- package/dist/cjs/util/bindings.js +58 -110
- package/dist/cjs/util/states.js +31 -24
- package/dist/cjs/util/trackers.js +75 -0
- package/dist/cjs/util/viewables.js +55 -0
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/workflow/ask.js +1 -5
- package/dist/cjs/workflow/base.js +2 -7
- package/dist/cjs/workflow/recommendation.js +2 -0
- package/package.json +2 -2
- package/src/actor/index.js +0 -1
- package/src/actor/tracker.js +37 -94
- package/src/actor/utils.js +1 -1
- package/src/actor/view.js +14 -1
- package/src/actor/views.js +30 -1
- package/src/asset/svgs.js +13 -2
- package/src/constants.js +7 -1
- package/src/layout/affiliation/affiliation.js +140 -55
- package/src/layout/input/option-list.js +35 -43
- package/src/layout/list/collection.js +20 -38
- package/src/layout/templates.js +25 -19
- package/src/layout/text/typewriter/index.js +8 -6
- package/src/layout/trackable.js +106 -0
- package/src/util/bindings.js +47 -103
- package/src/util/states.js +26 -20
- package/src/util/trackers.js +69 -0
- package/src/util/viewables.js +47 -0
- package/src/version.js +1 -1
- package/src/workflow/ask.js +1 -4
- package/src/workflow/base.js +3 -5
- package/src/workflow/recommendation.js +1 -0
- package/dist/cjs/actor/trackers.js +0 -51
- package/dist/cjs/layout/affiliation/viewable.js +0 -65
- package/dist/cjs/layout/text/typewriter/progress.js +0 -45
- package/dist/cjs/util/tracker.js +0 -348
- package/src/actor/trackers.js +0 -45
- package/src/layout/affiliation/viewable.js +0 -61
- package/src/layout/text/typewriter/progress.js +0 -33
- package/src/util/tracker.js +0 -318
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CarouselItemViewabilityObserver, requestAnimationFrame as raf } from '@miso.ai/commons';
|
|
2
|
+
import { STATUS, LAYOUT_CATEGORY } from '../../constants.js';
|
|
2
3
|
import CollectionLayout from '../list/collection.js';
|
|
3
|
-
import { affiliation } from '../templates.js';
|
|
4
|
-
import
|
|
4
|
+
import { affiliation, helpers } from '../templates.js';
|
|
5
|
+
import { TRIANGLE } from '../../asset/svgs.js';
|
|
5
6
|
|
|
6
7
|
const TYPE = 'affiliation';
|
|
7
8
|
const DEFAULT_CLASSNAME = 'miso-affiliation';
|
|
@@ -12,7 +13,10 @@ function root(layout, state) {
|
|
|
12
13
|
const roleAttr = role ? ` data-role="${role}"` : '';
|
|
13
14
|
const data = state.value || {};
|
|
14
15
|
const channelAttr = data.channel ? ` data-channel="${data.channel}"` : '';
|
|
15
|
-
|
|
16
|
+
const items = layout._getItems(state);
|
|
17
|
+
const itemCount = items ? items.length : undefined;
|
|
18
|
+
const itemCountAttr = itemCount !== undefined ? ` data-item-count="${itemCount}"` : '';
|
|
19
|
+
return `<div class="${className} ${status}"${roleAttr}${channelAttr}${itemCountAttr}>${status === STATUS.READY ? templates[status](layout, state) : ''}</div>`;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
function ready(layout, state) {
|
|
@@ -20,15 +24,24 @@ function ready(layout, state) {
|
|
|
20
24
|
const items = layout._getItems(state);
|
|
21
25
|
|
|
22
26
|
if (items && items.length > 0) {
|
|
23
|
-
return templates.
|
|
27
|
+
return templates.controlContainer(layout, state, 'previous') + templates.body(layout, state) + templates.controlContainer(layout, state, 'next');
|
|
24
28
|
} else {
|
|
25
29
|
return templates.empty(layout, state);
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
function controlContainer(layout, state, direction) {
|
|
34
|
+
const { className, templates } = layout;
|
|
35
|
+
return `<div class="${className}__control-${direction}-container">${templates.control(layout, state, direction)}</div>`;
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
function control(layout, state, direction) {
|
|
30
|
-
const { className } = layout;
|
|
31
|
-
return `<div class="${className}__control-${direction}" data-role="${direction}"
|
|
39
|
+
const { className, templates } = layout;
|
|
40
|
+
return `<div class="${className}__control-${direction}" data-role="${direction}">${templates.controlIcon(layout, state, direction)}</div>`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function controlIcon(layout, state, direction) {
|
|
44
|
+
return TRIANGLE;
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
function body(layout, state) {
|
|
@@ -37,49 +50,58 @@ function body(layout, state) {
|
|
|
37
50
|
return `<div class="${className}__body">${templates.list(layout, state, items)}</div>`;
|
|
38
51
|
}
|
|
39
52
|
|
|
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
53
|
function item(layout, state, value, index) {
|
|
53
54
|
const { className, templates, options } = layout;
|
|
54
55
|
const { itemType } = options;
|
|
55
56
|
const header = templates.itemHeader(layout, state, value, { index });
|
|
56
57
|
const body = templates[itemType](layout, state, value, { index });
|
|
57
|
-
return `<li class="${className}__item">${header}${body}</li>`;
|
|
58
|
+
return `<li class="${className}__item"><div class="${className}__item-inner">${header}${body}</div></li>`;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
function itemHeader(layout, state, value, meta) {
|
|
61
62
|
const { className, templates } = layout;
|
|
62
|
-
let {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
63
|
+
let { itemHeaderLeftText, itemHeaderRightText } = templates;
|
|
64
|
+
itemHeaderLeftText = helpers.asFunction(itemHeaderLeftText)(layout, state, value, meta);
|
|
65
|
+
itemHeaderRightText = helpers.asFunction(itemHeaderRightText)(layout, state, value, meta);
|
|
66
|
+
return `<div class="${className}__item-header">
|
|
67
|
+
<div class="${className}__item-header-left">${itemHeaderLeftText}</div>
|
|
68
|
+
<div class="${className}__item-header-right">${itemHeaderRightText}</div>
|
|
69
|
+
</div>`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function itemHeaderRightText(layout, state, value, meta) {
|
|
73
|
+
const { className, templates } = layout;
|
|
74
|
+
const logoText = helpers.asFunction(templates.logoText)(layout, state, value, meta) || '';
|
|
75
|
+
const logoImg = helpers.asFunction(templates.logoImg)(layout, state, value, meta);
|
|
76
|
+
return logoImg ? `<span class="${className}__item-header-logo-phrase">${logoText}</span>${logoImg}` : '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function logoImg(layout, state, value, meta) {
|
|
80
|
+
const { className, templates } = layout;
|
|
81
|
+
const logoUrl = helpers.asFunction(templates.logoUrl)(layout, state, value, meta);
|
|
82
|
+
if (!logoUrl) {
|
|
67
83
|
return '';
|
|
68
84
|
}
|
|
69
|
-
|
|
85
|
+
const logoAltText = helpers.asFunction(templates.logoAltText)(layout, state, value, meta);
|
|
86
|
+
const logoAltTextAttr = logoAltText ? ` alt="${logoAltText}"` : '';
|
|
87
|
+
return `<img class="${className}__item-header-logo" src="${logoUrl}"${logoAltTextAttr}>`;
|
|
70
88
|
}
|
|
71
89
|
|
|
72
90
|
const DEFAULT_TEMPLATES = Object.freeze({
|
|
73
91
|
root,
|
|
74
92
|
[STATUS.READY]: ready,
|
|
93
|
+
controlContainer,
|
|
75
94
|
control,
|
|
95
|
+
controlIcon,
|
|
76
96
|
body,
|
|
77
|
-
header,
|
|
78
97
|
item,
|
|
79
98
|
itemHeader,
|
|
80
99
|
affiliation,
|
|
81
|
-
ctaText: 'View',
|
|
82
|
-
|
|
100
|
+
ctaText: 'View deal',
|
|
101
|
+
itemHeaderLeftText: `Today's best deals`,
|
|
102
|
+
itemHeaderRightText,
|
|
103
|
+
logoImg,
|
|
104
|
+
logoText: 'Selected by',
|
|
83
105
|
});
|
|
84
106
|
|
|
85
107
|
const INHERITED_DEFAULT_TEMPLATES = Object.freeze({
|
|
@@ -87,6 +109,31 @@ const INHERITED_DEFAULT_TEMPLATES = Object.freeze({
|
|
|
87
109
|
...DEFAULT_TEMPLATES,
|
|
88
110
|
});
|
|
89
111
|
|
|
112
|
+
/*
|
|
113
|
+
function normalizeAutoplayOptions(options) {
|
|
114
|
+
if (options === true) {
|
|
115
|
+
options = { interval: 10000 };
|
|
116
|
+
} else if (typeof options === 'number') {
|
|
117
|
+
if (options <= 0) {
|
|
118
|
+
options = false;
|
|
119
|
+
} else {
|
|
120
|
+
options = { interval: Math.max(options, 500) };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return options;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function autoplayOptionsEquals(a, b) {
|
|
127
|
+
if (a === b) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (!a || !b) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return a.interval === b.interval;
|
|
134
|
+
}
|
|
135
|
+
*/
|
|
136
|
+
|
|
90
137
|
export default class AffiliationLayout extends CollectionLayout {
|
|
91
138
|
|
|
92
139
|
static get category() {
|
|
@@ -105,33 +152,51 @@ export default class AffiliationLayout extends CollectionLayout {
|
|
|
105
152
|
return DEFAULT_CLASSNAME;
|
|
106
153
|
}
|
|
107
154
|
|
|
108
|
-
constructor({ className = DEFAULT_CLASSNAME, templates, ...options } = {}) {
|
|
155
|
+
constructor({ className = DEFAULT_CLASSNAME, templates, autoplay, ...options } = {}) {
|
|
109
156
|
super({ className, itemType: 'affiliation', templates: { ...DEFAULT_TEMPLATES, ...templates }, ...options });
|
|
110
157
|
this._itemCount = undefined;
|
|
111
158
|
this._itemIndex = 0;
|
|
159
|
+
this._displayedCount = undefined;
|
|
112
160
|
this._displayedIndex = undefined;
|
|
113
|
-
this._viewable =
|
|
161
|
+
this._viewable = undefined;
|
|
162
|
+
|
|
163
|
+
// TODO: extract this
|
|
164
|
+
/*
|
|
165
|
+
this._autoplay = {};
|
|
166
|
+
this.autoplay(autoplay);
|
|
167
|
+
*/
|
|
114
168
|
}
|
|
115
169
|
|
|
116
|
-
|
|
117
|
-
|
|
170
|
+
initialize(view) {
|
|
171
|
+
super.initialize(view);
|
|
172
|
+
const { viewable: options } = this._view.tracker.options || {};
|
|
173
|
+
this._viewable = new CarouselItemViewabilityObserver(this._onViewable.bind(this), options);
|
|
118
174
|
}
|
|
119
175
|
|
|
120
|
-
|
|
121
|
-
|
|
176
|
+
/*
|
|
177
|
+
autoplay(options = false) {
|
|
178
|
+
options = normalizeAutoplayOptions(options);
|
|
179
|
+
if (autoplayOptionsEquals(options, this._autoplay.options)) {
|
|
122
180
|
return;
|
|
123
181
|
}
|
|
124
|
-
|
|
182
|
+
if (this._autoplay.options) {
|
|
183
|
+
clearInterval(this._autoplay.intervalId);
|
|
184
|
+
}
|
|
185
|
+
this._autoplay = { options };
|
|
186
|
+
// TODO: use slower transition speed for autoplay
|
|
187
|
+
// TODO: pause on hover
|
|
188
|
+
// TODO: cooldown after manual interaction
|
|
189
|
+
if (options) {
|
|
190
|
+
this._autoplay.intervalId = setInterval(() => this.next(), options.interval);
|
|
191
|
+
}
|
|
125
192
|
}
|
|
193
|
+
*/
|
|
126
194
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
this.go(this._itemIndex - 1);
|
|
195
|
+
get itemIndex() {
|
|
196
|
+
return this._itemIndex;
|
|
132
197
|
}
|
|
133
198
|
|
|
134
|
-
|
|
199
|
+
set itemIndex(index) {
|
|
135
200
|
if (this._itemCount === undefined) {
|
|
136
201
|
return;
|
|
137
202
|
}
|
|
@@ -141,7 +206,21 @@ export default class AffiliationLayout extends CollectionLayout {
|
|
|
141
206
|
index = 0;
|
|
142
207
|
}
|
|
143
208
|
this._itemIndex = index;
|
|
144
|
-
this._syncItemIndex();
|
|
209
|
+
raf(() => this._syncItemIndex());
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
next() {
|
|
213
|
+
if (this._itemCount === undefined) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.itemIndex++;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
previous() {
|
|
220
|
+
if (this._itemCount === undefined) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.itemIndex--;
|
|
145
224
|
}
|
|
146
225
|
|
|
147
226
|
_getItems(state) {
|
|
@@ -151,7 +230,8 @@ export default class AffiliationLayout extends CollectionLayout {
|
|
|
151
230
|
|
|
152
231
|
_render(element, states, controls) {
|
|
153
232
|
super._render(element, states, controls);
|
|
154
|
-
|
|
233
|
+
// sync again for incremental item rendering
|
|
234
|
+
this._syncItemCount(states);
|
|
155
235
|
this._syncItemIndex();
|
|
156
236
|
}
|
|
157
237
|
|
|
@@ -169,14 +249,18 @@ export default class AffiliationLayout extends CollectionLayout {
|
|
|
169
249
|
newElement && this._viewable.observe(newElement);
|
|
170
250
|
}
|
|
171
251
|
|
|
172
|
-
_syncItemCount(
|
|
252
|
+
_syncItemCount({ state } = {}) {
|
|
253
|
+
const items = this._getItems(state);
|
|
254
|
+
const itemCount = this._itemCount = items ? items.length : undefined;
|
|
255
|
+
if (itemCount === this._displayedCount) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
let { element } = this._view;
|
|
173
259
|
element = element.firstElementChild;
|
|
174
260
|
if (!element) {
|
|
175
261
|
return;
|
|
176
262
|
}
|
|
177
|
-
|
|
178
|
-
const items = this._getItems(state);
|
|
179
|
-
const itemCount = this._itemCount = items ? items.length : undefined;
|
|
263
|
+
this._displayedCount = itemCount;
|
|
180
264
|
if (itemCount === undefined) {
|
|
181
265
|
element.removeAttribute('data-item-count');
|
|
182
266
|
} else {
|
|
@@ -185,16 +269,15 @@ export default class AffiliationLayout extends CollectionLayout {
|
|
|
185
269
|
}
|
|
186
270
|
|
|
187
271
|
_syncItemIndex() {
|
|
188
|
-
|
|
272
|
+
const index = this._itemIndex;
|
|
273
|
+
if (index === this._displayedIndex) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
189
276
|
const { element } = this._view;
|
|
190
277
|
const listElement = element && this._getListElement(element);
|
|
191
278
|
if (!listElement) {
|
|
192
279
|
return;
|
|
193
280
|
}
|
|
194
|
-
const index = this._itemIndex;
|
|
195
|
-
if (index === this._displayedIndex) {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
281
|
this._displayedIndex = index;
|
|
199
282
|
listElement.style.left = `${-index * 100}%`;
|
|
200
283
|
this._viewable.display(index);
|
|
@@ -206,7 +289,7 @@ export default class AffiliationLayout extends CollectionLayout {
|
|
|
206
289
|
if (!item) {
|
|
207
290
|
return;
|
|
208
291
|
}
|
|
209
|
-
this._view.
|
|
292
|
+
this._view.tracker.viewable([item]);
|
|
210
293
|
}
|
|
211
294
|
|
|
212
295
|
_getRenderedItemData(index) {
|
|
@@ -218,8 +301,9 @@ export default class AffiliationLayout extends CollectionLayout {
|
|
|
218
301
|
return products[index];
|
|
219
302
|
}
|
|
220
303
|
|
|
304
|
+
_trackViewables() {} // omit default behavior for we want to track by other means
|
|
305
|
+
|
|
221
306
|
_onClick(event) {
|
|
222
|
-
super._onClick(event);
|
|
223
307
|
const element = event.target;
|
|
224
308
|
if (element.closest(`[data-role="previous"]`)) {
|
|
225
309
|
this.previous();
|
|
@@ -229,6 +313,7 @@ export default class AffiliationLayout extends CollectionLayout {
|
|
|
229
313
|
this.next();
|
|
230
314
|
return;
|
|
231
315
|
}
|
|
316
|
+
super._onClick(event);
|
|
232
317
|
}
|
|
233
318
|
|
|
234
319
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
87
|
+
const { state } = data;
|
|
88
|
+
this._syncBindings(element, state);
|
|
89
|
+
this._syncOptionValues(element, state);
|
|
93
90
|
}
|
|
94
91
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
133
|
-
return
|
|
113
|
+
_getItemElements(element) {
|
|
114
|
+
return element ? Array.from(element.querySelectorAll(`[data-role="option"]`)) : [];
|
|
134
115
|
}
|
|
135
116
|
|
|
136
|
-
_handleClick(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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);
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { defineValues } from '@miso.ai/commons';
|
|
2
1
|
import { STATUS, LAYOUT_CATEGORY } from '../../constants.js';
|
|
3
2
|
import TemplateBasedLayout from '../template.js';
|
|
3
|
+
import { makeTrackable } from '../trackable.js';
|
|
4
4
|
import { product, article, question, productInfoBlock, articleInfoBlock, titleBlock, brandBlock, descriptionBlock, dateBlock, priceBlock, discountRateText, ctaBlock, cta, imageBlock, indexBlock, helpers } from '../templates.js';
|
|
5
5
|
|
|
6
|
-
const VALUE = Symbol.for('miso.value');
|
|
7
|
-
|
|
8
6
|
function root(layout, state) {
|
|
9
7
|
const { className, role, templates } = layout;
|
|
10
8
|
const { status } = state;
|
|
@@ -88,18 +86,13 @@ export default class CollectionLayout extends TemplateBasedLayout {
|
|
|
88
86
|
itemType,
|
|
89
87
|
...options,
|
|
90
88
|
});
|
|
91
|
-
defineValues(this, {
|
|
92
|
-
bindings: Object.freeze({
|
|
93
|
-
list: this._listBindings.bind(this),
|
|
94
|
-
}),
|
|
95
|
-
});
|
|
96
89
|
this._view = undefined;
|
|
90
|
+
this._initTrackable();
|
|
97
91
|
}
|
|
98
92
|
|
|
99
93
|
initialize(view) {
|
|
100
94
|
this._view = view;
|
|
101
|
-
|
|
102
|
-
this._unsubscribes.push(proxyElement.on('click', this._onClick.bind(this)));
|
|
95
|
+
this._unsubscribes.push(view.proxyElement.on('click', this._onClick.bind(this)));
|
|
103
96
|
}
|
|
104
97
|
|
|
105
98
|
_preprocess({ state, rendered }) {
|
|
@@ -144,18 +137,11 @@ export default class CollectionLayout extends TemplateBasedLayout {
|
|
|
144
137
|
} else {
|
|
145
138
|
element.innerHTML = html;
|
|
146
139
|
}
|
|
147
|
-
this.
|
|
140
|
+
this._syncBindings(element, state);
|
|
148
141
|
}
|
|
149
142
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
const values = this._getItems(state) || [];
|
|
155
|
-
let i = 0;
|
|
156
|
-
for (const itemElement of this._listItemElements(element)) {
|
|
157
|
-
itemElement[VALUE] = values[i++];
|
|
158
|
-
}
|
|
143
|
+
_unrender() {
|
|
144
|
+
this._clearBindings();
|
|
159
145
|
}
|
|
160
146
|
|
|
161
147
|
_getItems(state) {
|
|
@@ -166,39 +152,35 @@ export default class CollectionLayout extends TemplateBasedLayout {
|
|
|
166
152
|
return element.querySelector('[data-role="list"]');
|
|
167
153
|
}
|
|
168
154
|
|
|
169
|
-
|
|
155
|
+
_getItemElements(element) {
|
|
170
156
|
return element ? Array.from(element.querySelectorAll(`[data-role="item"]`)) : [];
|
|
171
157
|
}
|
|
172
158
|
|
|
173
|
-
_listBindings(rootElement) {
|
|
174
|
-
if (!rootElement) {
|
|
175
|
-
return [];
|
|
176
|
-
}
|
|
177
|
-
const bindings = [];
|
|
178
|
-
for (const element of this._listItemElements(rootElement)) {
|
|
179
|
-
const value = element[VALUE];
|
|
180
|
-
if (!value) {
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
const key = this.options.itemType === 'product' ? value.product_id : value;
|
|
184
|
-
bindings.push({ element, key, value });
|
|
185
|
-
}
|
|
186
|
-
return bindings;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
159
|
_onClick(event) {
|
|
190
160
|
const element = event.target.closest(`[data-role="item"]`);
|
|
191
161
|
if (!element) {
|
|
192
162
|
return;
|
|
193
163
|
}
|
|
194
|
-
const
|
|
164
|
+
const binding = this._bindings.get(element);
|
|
165
|
+
if (!binding) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this._trackClick(event, binding);
|
|
169
|
+
this._emitClickEvent(event, binding);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_emitClickEvent(event, binding) {
|
|
195
173
|
const { session } = this._view._state;
|
|
174
|
+
const { value, element } = binding;
|
|
196
175
|
this._view._events.emit('click', { session, value, element, domEvent: event });
|
|
197
176
|
}
|
|
198
177
|
|
|
199
178
|
destroy() {
|
|
179
|
+
this._destroyTrackable();
|
|
200
180
|
this._view = undefined;
|
|
201
181
|
super.destroy();
|
|
202
182
|
}
|
|
203
183
|
|
|
204
184
|
}
|
|
185
|
+
|
|
186
|
+
makeTrackable(CollectionLayout.prototype);
|
package/src/layout/templates.js
CHANGED
|
@@ -44,12 +44,11 @@ export function article(layout, state, data, meta) {
|
|
|
44
44
|
|
|
45
45
|
export function affiliation(layout, state, data, meta) {
|
|
46
46
|
const { templates } = layout;
|
|
47
|
-
const [openTag, closeTag] = tagPair(layout, data);
|
|
47
|
+
const [openTag, closeTag] = tagPair(layout, data, { link: false});
|
|
48
48
|
return [
|
|
49
49
|
openTag,
|
|
50
50
|
(templates.infoBlock || templates.affiliationInfoBlock || affiliationInfoBlock)(layout, data, meta),
|
|
51
|
-
(templates.imageBlock || imageBlock)(layout, data, meta),
|
|
52
|
-
(templates.brandBlock || brandBlock)(layout, data, meta),
|
|
51
|
+
(templates.imageBlock || imageBlock)(layout, data, { brand: true, ...meta, }),
|
|
53
52
|
closeTag,
|
|
54
53
|
].join('');
|
|
55
54
|
}
|
|
@@ -123,7 +122,7 @@ export function priceBlock(layout, data) {
|
|
|
123
122
|
content += `<span class="${className}__item-price miso-price" data-currency="${currency}">${price}</span>`;
|
|
124
123
|
// discount rate
|
|
125
124
|
if (discount_rate_percent) {
|
|
126
|
-
const text = (templates.discountRateText || discountRateText)(layout, data);
|
|
125
|
+
const text = (templates.discountRateText || discountRateText)(layout, { ...data, discount_rate_percent });
|
|
127
126
|
content += ` <span class="${className}__item-discount-rate">${text}</span>`;
|
|
128
127
|
}
|
|
129
128
|
return `<div class="${className}__item-price-container">${content}</div>`;
|
|
@@ -133,12 +132,15 @@ export function discountRateText(layout, { discount_rate_percent }) {
|
|
|
133
132
|
return `(${discount_rate_percent}% off)`;
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
export function imageBlock(
|
|
135
|
+
export function imageBlock(layout, data, meta) {
|
|
136
|
+
const { className, templates } = layout;
|
|
137
|
+
const { cover_image } = data;
|
|
137
138
|
if (!cover_image) {
|
|
138
139
|
return '';
|
|
139
140
|
}
|
|
140
141
|
const img = `<img class="${className}__item-cover-image" src="${cover_image}">`;
|
|
141
|
-
|
|
142
|
+
const brand = meta.brand ? (templates.brandBlock || brandBlock)(layout, data, meta) : '';
|
|
143
|
+
return `<div class="${className}__item-cover-image-container">${img}${brand}</div>`;
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
export function indexBlock({ className }, data, { index }) {
|
|
@@ -147,28 +149,27 @@ export function indexBlock({ className }, data, { index }) {
|
|
|
147
149
|
|
|
148
150
|
export function ctaBlock(layout, data, meta) {
|
|
149
151
|
const { className, templates } = layout;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
_cta = _cta(layout, data, meta);
|
|
153
|
-
}
|
|
154
|
-
return _cta ? `<div class="${className}__item-cta-container">${_cta}</div>` : '';
|
|
152
|
+
const cta = helpers.asFunction(templates.cta)(layout, data, meta);
|
|
153
|
+
return cta ? `<div class="${className}__item-cta-container">${cta}</div>` : '';
|
|
155
154
|
}
|
|
156
155
|
|
|
157
156
|
export function cta(layout, data, meta) {
|
|
158
|
-
const {
|
|
159
|
-
|
|
160
|
-
if (
|
|
161
|
-
|
|
157
|
+
const { templates } = layout;
|
|
158
|
+
const { url } = data;
|
|
159
|
+
if (!url) {
|
|
160
|
+
return '';
|
|
162
161
|
}
|
|
163
|
-
|
|
162
|
+
const [openTag, closeTag] = tagPair(layout, { url }, { classSuffix: 'item-cta', role: 'cta' });
|
|
163
|
+
const ctaText = helpers.asFunction(templates.ctaText)(layout, data, meta);
|
|
164
|
+
return ctaText ? `${openTag}${ctaText}${closeTag}` : '';
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
// helpers //
|
|
167
|
-
function tagPair({ className, options = {} }, { product_id, url }, { classSuffix = 'item-body', role = 'item' } = {}) {
|
|
168
|
+
function tagPair({ className, options = {} }, { product_id, url }, { classSuffix = 'item-body', role = 'item', ...meta } = {}) {
|
|
168
169
|
const { link = {} } = options;
|
|
169
170
|
const { target = '_blank', rel = 'noopener' } = link; // TODO: other properties
|
|
170
|
-
const tag = url ? 'a' : 'div';
|
|
171
|
-
const urlAttrs = url ? `href="${url}" target="${target}" rel="${rel}"` : '';
|
|
171
|
+
const tag = meta.link !== false && url ? 'a' : 'div';
|
|
172
|
+
const urlAttrs = meta.link !== false && url ? `href="${url}" target="${target}" rel="${rel}"` : '';
|
|
172
173
|
const productAttrs = product_id ? `${ATTR_DATA_MISO_PRODUCT_ID}="${product_id}"` : '';
|
|
173
174
|
const roleAttrs = role ? `data-role="${role}"` : '';
|
|
174
175
|
return [`<${tag} class="${className}__${classSuffix}" ${roleAttrs} ${productAttrs} ${urlAttrs}>`, `</${tag}>`];
|
|
@@ -189,8 +190,13 @@ function formatDate(date, fn = DEFAULT_DATE_OPTIONS) {
|
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
192
|
|
|
193
|
+
function asFunction(template = '') {
|
|
194
|
+
return typeof template === 'function' ? template : () => template;
|
|
195
|
+
}
|
|
196
|
+
|
|
192
197
|
export const helpers = Object.freeze({
|
|
193
198
|
tagPair,
|
|
194
199
|
formatDate,
|
|
195
200
|
escapeHtml,
|
|
201
|
+
asFunction,
|
|
196
202
|
});
|