@miso.ai/client-sdk-ui 1.10.0-beta.1 → 1.10.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.
- package/dist/cjs/actor/data.js +1 -1
- package/dist/cjs/actor/tracker.js +46 -70
- package/dist/cjs/actor/trackers.js +5 -1
- package/dist/cjs/actor/utils.js +6 -2
- package/dist/cjs/actor/view.js +14 -3
- package/dist/cjs/actor/views.js +9 -1
- package/dist/cjs/affiliation/index.js +126 -0
- package/dist/cjs/combo/ask/options.js +1 -1
- package/dist/cjs/combo/base.js +1 -1
- package/dist/cjs/constants.js +6 -3
- package/dist/cjs/defaults/ask/controls.js +1 -1
- package/dist/cjs/defaults/ask/phrases.js +1 -1
- package/dist/cjs/defaults/ask/templates.js +14 -4
- package/dist/cjs/element/role/index.js +7 -0
- package/dist/cjs/element/role/miso-affiliation.js +20 -0
- package/dist/cjs/layout/affiliation/affiliation.js +330 -0
- package/dist/cjs/layout/banner/banner.js +1 -1
- package/dist/cjs/layout/container/container.js +1 -1
- package/dist/cjs/layout/error/error.js +1 -1
- package/dist/cjs/layout/index.js +7 -0
- package/dist/cjs/layout/input/option-list.js +35 -50
- package/dist/cjs/layout/input/search-box.js +17 -5
- package/dist/cjs/layout/list/cards.js +1 -1
- package/dist/cjs/layout/list/carousel.js +5 -3
- package/dist/cjs/layout/list/collection.js +38 -48
- package/dist/cjs/layout/list/list.js +1 -1
- package/dist/cjs/layout/radio/feedback.js +1 -1
- package/dist/cjs/layout/radio/radio.js +1 -1
- package/dist/cjs/layout/raf.js +1 -1
- package/dist/cjs/layout/template.js +1 -1
- package/dist/cjs/layout/templates.js +135 -44
- package/dist/cjs/layout/text/text.js +1 -1
- package/dist/cjs/layout/text/typewriter/index.js +3 -3
- package/dist/cjs/layout/trackable.js +124 -0
- package/dist/cjs/plugin.js +1 -1
- 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 +14 -5
- package/dist/cjs/workflow/asks.js +1 -1
- package/dist/cjs/workflow/base.js +5 -5
- package/dist/cjs/workflow/explore.js +1 -1
- package/dist/cjs/workflow/options.js +6 -1
- package/dist/cjs/workflow/recommendation.js +2 -0
- package/package.json +2 -2
- package/src/actor/tracker.js +39 -62
- package/src/actor/trackers.js +3 -1
- package/src/actor/utils.js +6 -2
- package/src/actor/view.js +12 -1
- package/src/actor/views.js +11 -0
- package/src/affiliation/index.js +108 -0
- package/src/constants.js +10 -1
- package/src/defaults/ask/phrases.js +1 -1
- package/src/defaults/ask/templates.js +13 -2
- package/src/element/role/index.js +1 -0
- package/src/element/role/miso-affiliation.js +16 -0
- package/src/layout/affiliation/affiliation.js +295 -0
- package/src/layout/index.js +1 -0
- package/src/layout/input/option-list.js +35 -43
- package/src/layout/input/search-box.js +7 -2
- package/src/layout/list/carousel.js +4 -2
- package/src/layout/list/collection.js +37 -43
- package/src/layout/templates.js +101 -26
- package/src/layout/text/typewriter/index.js +2 -2
- 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 +7 -3
- package/src/workflow/base.js +1 -1
- package/src/workflow/options.js +5 -1
- package/src/workflow/recommendation.js +1 -0
- package/dist/cjs/util/tracker.js +0 -348
- 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
|
+
}
|
package/src/layout/index.js
CHANGED
|
@@ -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 {
|
|
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);
|
|
@@ -285,7 +285,8 @@ export default class SearchBoxLayout extends TemplateBasedLayout {
|
|
|
285
285
|
// TODO: select completion items with arrow keys
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
_handleClick(
|
|
288
|
+
_handleClick(event) {
|
|
289
|
+
const { target } = event;
|
|
289
290
|
const { inputElement } = this._context();
|
|
290
291
|
for (let element = target; element && element !== this._element; element = element.parentElement) {
|
|
291
292
|
if (element.matches('[data-role="submit"]') || element.matches('[data-role="button"]') || element.matches('[type="submit"]')) {
|
|
@@ -303,12 +304,16 @@ export default class SearchBoxLayout extends TemplateBasedLayout {
|
|
|
303
304
|
if (inputElement) {
|
|
304
305
|
inputElement.value = value;
|
|
305
306
|
}
|
|
306
|
-
this.
|
|
307
|
+
this._view._events.emit('select', Object.freeze({ value: item, index, domEvent: event }));
|
|
308
|
+
if (!event.defaultPrevented) {
|
|
309
|
+
this._submit(value);
|
|
310
|
+
}
|
|
307
311
|
}
|
|
308
312
|
this.close();
|
|
309
313
|
return;
|
|
310
314
|
}
|
|
311
315
|
if (element.matches('[data-role="product-item"]')) {
|
|
316
|
+
this._view._events.emit('select', Object.freeze({ value: item, index, domEvent: event }));
|
|
312
317
|
return;
|
|
313
318
|
}
|
|
314
319
|
}
|
|
@@ -17,7 +17,8 @@ const INHERITED_DEFAULT_TEMPLATES = Object.freeze({
|
|
|
17
17
|
|
|
18
18
|
function ready(layout, state) {
|
|
19
19
|
const { templates } = layout;
|
|
20
|
-
const
|
|
20
|
+
const items = layout._getItems(state);
|
|
21
|
+
const nonEmpty = items && items.length > 0;
|
|
21
22
|
const html = Object.getPrototypeOf(layout.constructor).defaultTemplates.ready(layout, state);
|
|
22
23
|
return nonEmpty ? templates.carousel(layout, state, html) : html;
|
|
23
24
|
}
|
|
@@ -60,7 +61,8 @@ export default class CarouselLayout extends CollectionLayout {
|
|
|
60
61
|
_render(element, states, controls) {
|
|
61
62
|
super._render(element, states, controls);
|
|
62
63
|
const { state } = states;
|
|
63
|
-
|
|
64
|
+
const items = this._getItems(state);
|
|
65
|
+
this._itemCount = items ? items.length : undefined;
|
|
64
66
|
this.syncSize();
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { defineValues } from '@miso.ai/commons';
|
|
2
1
|
import { STATUS, LAYOUT_CATEGORY } from '../../constants.js';
|
|
3
2
|
import TemplateBasedLayout from '../template.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const VALUE = Symbol.for('miso.value');
|
|
3
|
+
import { makeTrackable } from '../trackable.js';
|
|
4
|
+
import { product, article, question, productInfoBlock, articleInfoBlock, titleBlock, brandBlock, descriptionBlock, dateBlock, priceBlock, discountRateText, ctaBlock, cta, imageBlock, indexBlock, helpers } from '../templates.js';
|
|
7
5
|
|
|
8
6
|
function root(layout, state) {
|
|
9
7
|
const { className, role, templates } = layout;
|
|
@@ -14,7 +12,7 @@ function root(layout, state) {
|
|
|
14
12
|
|
|
15
13
|
function ready(layout, state) {
|
|
16
14
|
const { templates } = layout;
|
|
17
|
-
const values = state
|
|
15
|
+
const values = layout._getItems(state);
|
|
18
16
|
|
|
19
17
|
// TODO: handle categories, attributes, etc. by introducing sublayout
|
|
20
18
|
if ((values && values.length > 0) || state.ongoing) {
|
|
@@ -28,7 +26,7 @@ function list(layout, state, values) {
|
|
|
28
26
|
const { className, templates, options } = layout;
|
|
29
27
|
const listTag = templates.ordered ? 'ol' : 'ul';
|
|
30
28
|
// TODO: support separator?
|
|
31
|
-
return `<${listTag} class="${className}__list" data-item-type="${options.itemType}">${templates.items(layout, state, values)}</${listTag}>`;
|
|
29
|
+
return `<${listTag} class="${className}__list" data-role="list" data-item-type="${options.itemType}">${templates.items(layout, state, values)}</${listTag}>`;
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
function items(layout, state, values, { offset = 0 } = {}) {
|
|
@@ -59,6 +57,14 @@ const DEFAULT_TEMPLATES = Object.freeze({
|
|
|
59
57
|
item,
|
|
60
58
|
productInfoBlock,
|
|
61
59
|
articleInfoBlock,
|
|
60
|
+
brandBlock,
|
|
61
|
+
titleBlock,
|
|
62
|
+
descriptionBlock,
|
|
63
|
+
dateBlock,
|
|
64
|
+
priceBlock,
|
|
65
|
+
discountRateText,
|
|
66
|
+
ctaBlock,
|
|
67
|
+
cta,
|
|
62
68
|
imageBlock,
|
|
63
69
|
indexBlock,
|
|
64
70
|
helpers,
|
|
@@ -80,18 +86,13 @@ export default class CollectionLayout extends TemplateBasedLayout {
|
|
|
80
86
|
itemType,
|
|
81
87
|
...options,
|
|
82
88
|
});
|
|
83
|
-
defineValues(this, {
|
|
84
|
-
bindings: Object.freeze({
|
|
85
|
-
list: this._listBindings.bind(this),
|
|
86
|
-
}),
|
|
87
|
-
});
|
|
88
89
|
this._view = undefined;
|
|
90
|
+
this._initTrackable();
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
initialize(view) {
|
|
92
94
|
this._view = view;
|
|
93
|
-
|
|
94
|
-
this._unsubscribes.push(proxyElement.on('click', this._onClick.bind(this)));
|
|
95
|
+
this._unsubscribes.push(view.proxyElement.on('click', this._onClick.bind(this)));
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
_preprocess({ state, rendered }) {
|
|
@@ -117,7 +118,7 @@ export default class CollectionLayout extends TemplateBasedLayout {
|
|
|
117
118
|
_html(state, rendered, incremental) {
|
|
118
119
|
if (incremental) {
|
|
119
120
|
const offset = rendered.value.length;
|
|
120
|
-
const values = state.
|
|
121
|
+
const values = (this._getItems(state) || []).slice(offset);
|
|
121
122
|
return values.length > 0 ? this.templates.items(this, state, values, { offset }) : '';
|
|
122
123
|
} else {
|
|
123
124
|
return this.templates.root(this, state);
|
|
@@ -136,42 +137,23 @@ export default class CollectionLayout extends TemplateBasedLayout {
|
|
|
136
137
|
} else {
|
|
137
138
|
element.innerHTML = html;
|
|
138
139
|
}
|
|
139
|
-
this.
|
|
140
|
+
this._syncBindings(element, state);
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const values = state.value || [];
|
|
147
|
-
let i = 0;
|
|
148
|
-
for (const itemElement of this._listItemElements(element)) {
|
|
149
|
-
itemElement[VALUE] = values[i++];
|
|
150
|
-
}
|
|
143
|
+
_unrender() {
|
|
144
|
+
this._clearBindings();
|
|
151
145
|
}
|
|
152
146
|
|
|
153
|
-
|
|
154
|
-
return
|
|
147
|
+
_getItems(state) {
|
|
148
|
+
return state.value;
|
|
155
149
|
}
|
|
156
150
|
|
|
157
|
-
|
|
158
|
-
return element
|
|
151
|
+
_getListElement(element) {
|
|
152
|
+
return element.querySelector('[data-role="list"]');
|
|
159
153
|
}
|
|
160
154
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return [];
|
|
164
|
-
}
|
|
165
|
-
const bindings = [];
|
|
166
|
-
for (const element of this._listItemElements(rootElement)) {
|
|
167
|
-
const value = element[VALUE];
|
|
168
|
-
if (!value) {
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
const key = this.options.itemType === 'product' ? value.product_id : value;
|
|
172
|
-
bindings.push({ element, key, value });
|
|
173
|
-
}
|
|
174
|
-
return bindings;
|
|
155
|
+
_getItemElements(element) {
|
|
156
|
+
return element ? Array.from(element.querySelectorAll(`[data-role="item"]`)) : [];
|
|
175
157
|
}
|
|
176
158
|
|
|
177
159
|
_onClick(event) {
|
|
@@ -179,14 +161,26 @@ export default class CollectionLayout extends TemplateBasedLayout {
|
|
|
179
161
|
if (!element) {
|
|
180
162
|
return;
|
|
181
163
|
}
|
|
182
|
-
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) {
|
|
183
173
|
const { session } = this._view._state;
|
|
174
|
+
const { value, element } = binding;
|
|
184
175
|
this._view._events.emit('click', { session, value, element, domEvent: event });
|
|
185
176
|
}
|
|
186
177
|
|
|
187
178
|
destroy() {
|
|
179
|
+
this._destroyTrackable();
|
|
188
180
|
this._view = undefined;
|
|
189
181
|
super.destroy();
|
|
190
182
|
}
|
|
191
183
|
|
|
192
184
|
}
|
|
185
|
+
|
|
186
|
+
makeTrackable(CollectionLayout.prototype);
|