@miso.ai/client-sdk-ui 1.11.5-beta.1 → 1.11.5-beta.3

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.
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.UseLinkMixin = void 0;
7
7
  exports.enableUseLink = enableUseLink;
8
8
  var _commons = require("@miso.ai/commons");
9
+ const _excluded = ["q"];
10
+ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
9
11
  function enableUseLink(prototype) {
10
12
  (0, _commons.mixin)(prototype, UseLinkMixin.prototype);
11
13
  }
@@ -34,11 +36,16 @@ class UseLinkMixin {
34
36
  window.open(url, target, windowFeatures);
35
37
  }
36
38
  }
37
- _getSubmitUrl(args) {
39
+ _getSubmitUrl() {
40
+ let _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
41
+ let {
42
+ q
43
+ } = _ref,
44
+ args = _objectWithoutPropertiesLoose(_ref, _excluded);
38
45
  if (!this._linkFn) {
39
46
  throw new Error('useLink(fn) must be called before _getSubmitUrl()');
40
47
  }
41
- return this._linkFn[0](args.q);
48
+ return this._linkFn[0](q, args);
42
49
  }
43
50
  }
44
51
  exports.UseLinkMixin = UseLinkMixin;
package/package.json CHANGED
@@ -26,7 +26,7 @@
26
26
  "url": "https://github.com/MisoAI/miso-client-js-sdk/issues"
27
27
  },
28
28
  "dependencies": {
29
- "@miso.ai/commons": "1.11.5-beta.1"
29
+ "@miso.ai/commons": "1.11.5-beta.3"
30
30
  },
31
- "version": "1.11.5-beta.1"
31
+ "version": "1.11.5-beta.3"
32
32
  }
package/src/asset/svgs.js CHANGED
@@ -15,6 +15,7 @@ const ICON_NAME = {
15
15
  SEARCH: 'search',
16
16
  TRIANGLE: 'triangle',
17
17
  TRIANGLE_EQ: 'triangle-eq',
18
+ CHEVRON: 'chevron',
18
19
  CHEVRON_NEGATIVE: 'chevron-negative',
19
20
  };
20
21
 
@@ -27,9 +28,11 @@ const ICON_TRIANGLE = `<symbol id="${PREFIX}-${ICON_NAME.TRIANGLE}" viewBox="-5
27
28
 
28
29
  const ICON_TRIANGLE_EQ = `<symbol id="${PREFIX}-${ICON_NAME.TRIANGLE_EQ}" viewBox="-100 -100 200 200"><path d="M12.99 -77.5A15 15,0,0,0,-12.99 -77.5L-73.61 27.5A15 15,0,0,0,-60.62 50L60.62 50A15 15,0,0,0,73.61 27.5L12.99 -77.5Z"/></symbol>`;
29
30
 
31
+ const ICON_CHEVRON = `<symbol id="${PREFIX}-${ICON_NAME.CHEVRON}" viewBox="-100 -100 200 200"><path d="M10.61 -43.94A15 15,0,0,0,-10.61 -43.94L-60.61 6.06A15 15,0,0,0,-39.39 27.27L0 -12.12L39.39 27.27A15 15,0,0,0,60.61 6.06L10.61 -43.94Z"/></symbol>`;
32
+
30
33
  const ICON_CHEVRON_NEGATIVE = `<symbol id="${PREFIX}-${ICON_NAME.CHEVRON_NEGATIVE}" viewBox="-100 -100 200 200"><path d="M100 100V-200H-200V200H200ZM-10.61 -43.94A15 15,0,0,1,10.61 -43.94L60.61 6.06A15 15,0,0,1,39.39 27.27L0 -12.12L-39.39 27.27A15 15,0,0,1,-60.61 6.06L-10.61 -43.94Z"/></symbol>`;
31
34
 
32
- const ICONS = [ICON_SEND, ICON_SEARCH, ICON_TRIANGLE, ICON_TRIANGLE_EQ, ICON_CHEVRON_NEGATIVE];
35
+ const ICONS = [ICON_SEND, ICON_SEARCH, ICON_TRIANGLE, ICON_TRIANGLE_EQ, ICON_CHEVRON, ICON_CHEVRON_NEGATIVE];
33
36
 
34
37
  const SPRITES_SVG = `<svg id="${ID}" style="display:none;">${ICONS.join('')}</svg>`;
35
38
 
@@ -59,4 +62,5 @@ export const SEND = _icon(ICON_NAME.SEND);
59
62
  export const SEARCH = _icon(ICON_NAME.SEARCH);
60
63
  export const TRIANGLE = _icon(ICON_NAME.TRIANGLE);
61
64
  export const TRIANGLE_EQ = _icon(ICON_NAME.TRIANGLE_EQ);
65
+ export const CHEVRON = _icon(ICON_NAME.CHEVRON);
62
66
  export const CHEVRON_NEGATIVE = _icon(ICON_NAME.CHEVRON_NEGATIVE);
@@ -3,12 +3,13 @@ import { STATUS } from '../../constants.js';
3
3
  import CollectionLayout from '../list/collection.js';
4
4
  import { affiliation, helpers } from '../templates.js';
5
5
  import { TRIANGLE } from '../../asset/svgs.js';
6
+ import { setOrRemoveAttribute } from '../../util/dom.js';
6
7
 
7
8
  const TYPE = 'affiliation';
8
9
  const DEFAULT_CLASSNAME = 'miso-affiliation';
9
10
 
10
11
  function root(layout, state) {
11
- const { className, role, templates } = layout;
12
+ const { className, role, templates, options } = layout;
12
13
  const { status } = state;
13
14
  const roleAttr = role ? ` data-role="${role}"` : '';
14
15
  const data = state.value || {};
@@ -16,7 +17,8 @@ function root(layout, state) {
16
17
  const items = layout._getItems(state);
17
18
  const itemCount = items ? items.length : undefined;
18
19
  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>`;
20
+ const itemTypeAttr = options.itemType ? ` data-item-type="${options.itemType}"` : '';
21
+ return `<div class="${className} ${status}"${roleAttr}${channelAttr}${itemTypeAttr}${itemCountAttr}>${status === STATUS.READY ? templates[status](layout, state) : ''}</div>`;
20
22
  }
21
23
 
22
24
  function ready(layout, state) {
@@ -256,12 +258,8 @@ export default class AffiliationLayout extends CollectionLayout {
256
258
  if (!element) {
257
259
  return;
258
260
  }
261
+ setOrRemoveAttribute(element, 'data-item-count', itemCount);
259
262
  this._displayedCount = itemCount;
260
- if (itemCount === undefined) {
261
- element.removeAttribute('data-item-count');
262
- } else {
263
- element.setAttribute('data-item-count', itemCount);
264
- }
265
263
  }
266
264
 
267
265
  _syncItemIndex() {
@@ -6,6 +6,7 @@ export { default as CollectionLayout } from './list/collection.js';
6
6
  export { default as ListLayout } from './list/list.js';
7
7
  export { default as CardsLayout } from './list/cards.js';
8
8
  export { default as CarouselLayout } from './list/carousel.js';
9
+ export { default as GalleryLayout } from './list/gallery.js';
9
10
  export { default as SearchBoxLayout } from './input/search-box.js';
10
11
  export { default as OptionListLayout } from './input/option-list.js';
11
12
  export { default as FeedbackLayout } from './radio/feedback.js';
@@ -2,6 +2,7 @@ import { escapeHtml } from '@miso.ai/commons';
2
2
  import { fields } from '../../actor/index.js';
3
3
  import TemplateBasedLayout from '../template.js';
4
4
  import { makeTrackable } from '../mixin/trackable.js';
5
+ import { addOrRemoveClass } from '../../util/dom.js';
5
6
 
6
7
  const TYPE = 'option-list';
7
8
  const DEFAULT_CLASSNAME = 'miso-option-list';
@@ -96,7 +97,7 @@ export default class OptionListLayout extends TemplateBasedLayout {
96
97
  return;
97
98
  }
98
99
  const optionValues = this._getItems(state) || [];
99
- optionsElement.classList[optionValues.length === 0 ? 'add' : 'remove']('empty');
100
+ addOrRemoveClass(optionsElement, 'empty', optionValues.length === 0);
100
101
  }
101
102
 
102
103
  _getItems(state) {
@@ -4,6 +4,7 @@ import { fields } from '../../actor/index.js';
4
4
  import TemplateBasedLayout from '../template.js';
5
5
  import { imageBlock, productInfoBlock } from '../templates.js';
6
6
  import { getIcon } from '../../asset/svgs.js';
7
+ import { addOrRemoveClass } from '../../util/dom.js';
7
8
 
8
9
  const TYPE = 'search-box';
9
10
  const DEFAULT_CLASSNAME = 'miso-search-box';
@@ -215,7 +216,7 @@ export default class SearchBoxLayout extends TemplateBasedLayout {
215
216
 
216
217
  const items = context.trackItems(completions);
217
218
 
218
- element.classList[items.length > 0 ? 'add' : 'remove']('nonempty');
219
+ addOrRemoveClass(element, 'nonempty', items.length > 0);
219
220
  autocompleteElement.setAttribute('data-status', status);
220
221
  autocompleteElement.innerHTML = this.templates.completions(this, items);
221
222
 
@@ -1,6 +1,6 @@
1
1
  import CollectionLayout from './collection.js';
2
2
  import { makeSwipeable } from '../mixin/swipeable.js';
3
- import { CHEVRON_NEGATIVE } from '../../asset/svgs.js';
3
+ import { CHEVRON, CHEVRON_NEGATIVE } from '../../asset/svgs.js';
4
4
 
5
5
  const TYPE = 'carousel';
6
6
  const DEFAULT_CLASSNAME = 'miso-carousel';
@@ -41,8 +41,8 @@ function control(layout, state, direction) {
41
41
  return `<div class="${className}__control-${direction}" data-role="${direction}">${templates.controlIcon(layout, state, direction)}</div>`;
42
42
  }
43
43
 
44
- function controlIcon(layout, state, direction) {
45
- return CHEVRON_NEGATIVE;
44
+ function controlIcon(layout) {
45
+ return layout.options.itemType === 'image' ? CHEVRON_NEGATIVE : CHEVRON;
46
46
  }
47
47
 
48
48
  function indexIndicator(layout, state, values) {
@@ -5,10 +5,11 @@ import { makeTriggerable } from '../mixin/triggerable.js';
5
5
  import { product, article, image, question, productInfoBlock, articleInfoBlock, imageInfoBlock, titleBlock, brandBlock, descriptionBlock, dateBlock, priceBlock, discountRateText, ctaBlock, cta, imageBlock, indexBlock } from '../templates.js';
6
6
 
7
7
  function root(layout, state) {
8
- const { className, role, templates } = layout;
8
+ const { className, role, templates, options } = layout;
9
9
  const { status } = state;
10
- const roleAttr = role ? `data-role="${role}"` : '';
11
- return `<div class="${className} ${status}" ${roleAttr}>${status === STATUS.READY ? templates[status](layout, state) : ''}${templates.trigger(layout, state)}${templates.loading(layout, state)}</div>`;
10
+ const roleAttr = role ? ` data-role="${role}"` : '';
11
+ const itemTypeAttr = options.itemType ? ` data-item-type="${options.itemType}"` : '';
12
+ return `<div class="${className} ${status}"${roleAttr}${itemTypeAttr}>${status === STATUS.READY ? templates[status](layout, state) : ''}${templates.trigger(layout, state)}${templates.loading(layout, state)}</div>`;
12
13
  }
13
14
 
14
15
  function ready(layout, state) {
@@ -30,8 +31,9 @@ function body(layout, state, values) {
30
31
  function list(layout, state, values) {
31
32
  const { className, templates, options } = layout;
32
33
  const listTag = templates.ordered ? 'ol' : 'ul';
34
+ const itemTypeAttr = options.itemType ? ` data-item-type="${options.itemType}"` : ''; // backward compatible
33
35
  // TODO: support separator?
34
- return `<${listTag} class="${className}__list" data-role="list" data-item-type="${options.itemType}">${templates.items(layout, state, values)}</${listTag}>`;
36
+ return `<${listTag} class="${className}__list" data-role="list"${itemTypeAttr}>${templates.items(layout, state, values)}</${listTag}>`;
35
37
  }
36
38
 
37
39
  function items(layout, state, values, { offset = 0 } = {}) {
@@ -0,0 +1,230 @@
1
+ import { trimObj } from '@miso.ai/commons';
2
+ import CollectionLayout from './collection.js';
3
+ import { addOrRemoveClass, resolveCssLength, cssAspectRatio } from '../../util/dom.js';
4
+
5
+ function calculateItemSizes(itemCount, columnCount) {
6
+ const totalSpaces = columnCount * 2;
7
+ let remainingSpaces = Math.max(totalSpaces - itemCount, 0);
8
+
9
+ let small = itemCount;
10
+ // turning a small block into a large block takes 3 extra spaces
11
+ const large = Math.min(Math.floor(remainingSpaces / 3), small);
12
+ small -= large;
13
+ remainingSpaces -= large * 3;
14
+ // turning a small block into a medium block takes 1 extra space
15
+ const medium = Math.min(remainingSpaces, small);
16
+ small -= medium;
17
+ remainingSpaces -= medium;
18
+
19
+ return [large, medium, small];
20
+ }
21
+
22
+ function item(layout, state, value, index) {
23
+ const { className, templates, options } = layout;
24
+ const { itemType } = options;
25
+ const body = templates[itemType](layout, state, value, { index });
26
+ const sizeClass = value._size ? ` ${className}__item-${value._size}` : '';
27
+ return `<li class="${className}__item${sizeClass}">${body}</li>`;
28
+ }
29
+
30
+ const DEFAULT_HEIGHT = '16rem';
31
+
32
+ const DEFAULT_TEMPLATES = Object.freeze({
33
+ ...CollectionLayout.defaultTemplates,
34
+ item,
35
+ });
36
+
37
+ const TYPE = 'gallery';
38
+ const DEFAULT_CLASSNAME = 'miso-gallery';
39
+
40
+ export default class GalleryLayout extends CollectionLayout {
41
+
42
+ static get type() {
43
+ return TYPE;
44
+ }
45
+
46
+ static get defaultTemplates() {
47
+ return DEFAULT_TEMPLATES;
48
+ }
49
+
50
+ static get defaultClassName() {
51
+ return DEFAULT_CLASSNAME;
52
+ }
53
+
54
+ constructor({ className = DEFAULT_CLASSNAME, templates, ...options } = {}) {
55
+ super({
56
+ className,
57
+ templates: { ...DEFAULT_TEMPLATES, ...templates },
58
+ ...options,
59
+ });
60
+ this._galleryContext = {
61
+ itemAspectRatio: undefined,
62
+ itemCount: undefined,
63
+ columnCount: undefined,
64
+ };
65
+ }
66
+
67
+ syncSize() {
68
+ const { element } = this._view;
69
+ const listElement = element && this._getListElement(element);
70
+ if (!listElement) {
71
+ return;
72
+ }
73
+ this._syncListAspectRatio(listElement);
74
+ this._syncItemSizes(listElement);
75
+ }
76
+
77
+ _preprocess({ state, rendered }) {
78
+ const incremental = this._shallRenderIncrementally(state, rendered);
79
+ const bodyHtml = incremental ? undefined : this._bodyHtml(state);
80
+ const itemsHtml = this._itemsHtml(state, rendered);
81
+ return {
82
+ ...state,
83
+ ...trimObj({
84
+ incremental,
85
+ bodyHtml,
86
+ itemsHtml,
87
+ }),
88
+ };
89
+ }
90
+
91
+ _bodyHtml(state) {
92
+ return this.templates.root(this, { ...state, value: [] });
93
+ }
94
+
95
+ _itemsHtml(state, rendered) {
96
+ const offset = (rendered && rendered.value && rendered.value.length) || 0;
97
+ const values = (this._getItems(state) || []).slice(offset);
98
+ return values.length > 0 ? this.templates.items(this, state, values, { offset }) : '';
99
+ }
100
+
101
+ _render(element, states, controls) {
102
+ this._syncItemCount(states);
103
+ this._syncCssVariables();
104
+ const listElement = this._renderListElement(element, states, controls);
105
+ if (!listElement) {
106
+ return;
107
+ }
108
+ this._syncListAspectRatio(listElement);
109
+ this._renderItems(listElement, states, controls);
110
+ }
111
+
112
+ _renderListElement(element, { state }) {
113
+ const { incremental, bodyHtml } = state;
114
+ if (!incremental) {
115
+ element.innerHTML = bodyHtml;
116
+ }
117
+ return this._getListElement(element);
118
+ }
119
+
120
+ _renderItems(listElement, states) {
121
+ if (states.state.incremental) {
122
+ this._appendItems(listElement, states);
123
+ } else {
124
+ this._updateItems(listElement, states);
125
+ }
126
+ }
127
+
128
+ _updateItems(listElement, { state }) {
129
+ const { itemSizes } = this._galleryContext;
130
+ const values = this._writeItemSizes(this._getItems(state), itemSizes);
131
+ listElement.innerHTML = this.templates.items(this, state, values);
132
+ }
133
+
134
+ _appendItems(listElement, { state }) {
135
+ const { itemSizes } = this._galleryContext;
136
+ // update existing items
137
+ this._syncItemSizes(listElement);
138
+ // append new items
139
+ const offset = listElement.children.length;
140
+ const values = this._writeItemSizes(this._getItems(state).slice(offset), itemSizes, offset);
141
+ listElement.insertAdjacentHTML('beforeend', this.templates.items(this, state, values, { offset }));
142
+ }
143
+
144
+ _writeItemSizes(values = [], [large = 0, medium = 0] = [], offset = 0) {
145
+ let omitted = 0;
146
+ if (offset > 0) {
147
+ omitted = Math.min(offset, large);
148
+ offset -= omitted;
149
+ large -= omitted;
150
+ }
151
+ if (offset > 0) {
152
+ omitted = Math.min(offset, medium);
153
+ offset -= omitted;
154
+ medium -= omitted;
155
+ }
156
+ const results = [];
157
+ for (const value of values) {
158
+ let _size = 'small';
159
+ if (large > 0) {
160
+ _size = 'large';
161
+ large--;
162
+ } else if (medium > 0) {
163
+ _size = 'medium';
164
+ medium--;
165
+ }
166
+ results.push({ ...value, _size });
167
+ }
168
+ return results;
169
+ }
170
+
171
+ _syncCssVariables() {
172
+ const context = this._galleryContext;
173
+ let { itemAspectRatio = 1, height = DEFAULT_HEIGHT } = this.options;
174
+ itemAspectRatio = context.itemAspectRatio = cssAspectRatio(itemAspectRatio);
175
+ const { element } = this._view;
176
+ if (!element) {
177
+ return;
178
+ }
179
+ // TODO: skip if equal to default values
180
+ element.style.setProperty('--miso-gallery-height', `${height}`);
181
+ element.style.setProperty('--miso-gallery-item-1x1-aspect-ratio', `${itemAspectRatio}`);
182
+ element.style.setProperty('--miso-gallery-item-1x2-aspect-ratio', `${itemAspectRatio.scaleBy([1, 2])}`);
183
+ }
184
+
185
+ _syncListAspectRatio(listElement) {
186
+ const context = this._galleryContext;
187
+ const { itemCount, itemAspectRatio } = context;
188
+ const { height = DEFAULT_HEIGHT } = this.options;
189
+ const resolvedHeight = resolveCssLength(height);
190
+ const width = listElement.clientWidth;
191
+ if (width === 0) {
192
+ context.columnCount = undefined;
193
+ context.itemSizes = undefined;
194
+ return; // probably not visible yet
195
+ }
196
+ const columnCount = context.columnCount = Math.round((width * itemAspectRatio.width * 2) / (resolvedHeight * itemAspectRatio.height));
197
+ context.itemSizes = calculateItemSizes(itemCount, columnCount);
198
+
199
+ // update DOM
200
+ listElement.style.aspectRatio = `${columnCount} / 2`;
201
+ listElement.style.height = 'auto';
202
+ }
203
+
204
+ _syncItemCount({ state } = {}) {
205
+ const items = this._getItems(state);
206
+ this._galleryContext.itemCount = items ? items.length : undefined;
207
+ }
208
+
209
+ _syncItemSizes(listElement) {
210
+ const { itemSizes = [] } = this._galleryContext;
211
+ let [large = 0, medium = 0] = itemSizes;
212
+ const { className } = this;
213
+
214
+ // update DOM
215
+ for (const itemElement of listElement.children) {
216
+ let lg = false, md = false;
217
+ if (large > 0) {
218
+ lg = true;
219
+ large--;
220
+ } else if (medium > 0) {
221
+ md = true;
222
+ medium--;
223
+ }
224
+ addOrRemoveClass(itemElement, `${className}__item-large`, lg);
225
+ addOrRemoveClass(itemElement, `${className}__item-medium`, md);
226
+ addOrRemoveClass(itemElement, `${className}__item-small`, !lg && !md);
227
+ }
228
+ }
229
+
230
+ }
@@ -1,4 +1,5 @@
1
1
  import { mixin, requestAnimationFrame as raf } from '@miso.ai/commons';
2
+ import { setOrRemoveAttribute, addOrRemoveClass } from '../../util/dom.js';
2
3
 
3
4
  export function makeSwipeable(prototype) {
4
5
  mixin(prototype, SwipeableMixin.prototype);
@@ -33,7 +34,7 @@ export class SwipeableMixin {
33
34
  index = 0;
34
35
  }
35
36
  context.itemIndex = index;
36
- raf(() => this._syncSwipeableItemIndex());
37
+ raf(() => this._syncDisplayedSwipeableItemIndex());
37
38
  }
38
39
 
39
40
  next() {
@@ -52,7 +53,8 @@ export class SwipeableMixin {
52
53
 
53
54
  _renderSwipeable(element, states, controls) {
54
55
  this._syncSwipeableItemCount(states);
55
- this._syncSwipeableItemIndex();
56
+ this._syncDisplayedSwipeableItemCount();
57
+ this._syncDisplayedSwipeableItemIndex();
56
58
  }
57
59
 
58
60
  _afterRenderSwipeable() {
@@ -74,8 +76,6 @@ export class SwipeableMixin {
74
76
  } else if (oldItemCount === undefined) {
75
77
  context.itemIndex = 0;
76
78
  }
77
-
78
- this._syncDisplayedSwipeableItemCount();
79
79
  }
80
80
 
81
81
  _syncDisplayedSwipeableItemCount() {
@@ -89,24 +89,20 @@ export class SwipeableMixin {
89
89
  if (!element) {
90
90
  return;
91
91
  }
92
+ setOrRemoveAttribute(element, 'data-item-count', itemCount);
92
93
  context.displayedCount = itemCount;
93
- if (itemCount === undefined) {
94
- element.removeAttribute('data-item-count');
95
- } else {
96
- element.setAttribute('data-item-count', itemCount);
97
- }
98
94
  }
99
95
 
100
- _syncSwipeableItemIndex() {
96
+ _syncDisplayedSwipeableItemIndex() {
101
97
  const context = this._swipeableContext;
102
- const index = context.itemIndex;
103
- if (index === context.displayedIndex) {
98
+ const { itemIndex } = context;
99
+ if (itemIndex === context.displayedIndex) {
104
100
  return;
105
101
  }
106
- context.displayedIndex = index;
102
+ context.displayedIndex = itemIndex;
107
103
 
108
- this._syncSwipeableListIndex(index);
109
- this._syncSwipeableIndicatorIndex(index);
104
+ this._syncSwipeableListIndex(itemIndex);
105
+ this._syncSwipeableIndicatorIndex(itemIndex);
110
106
  }
111
107
 
112
108
  _syncSwipeableListIndex(index) {
@@ -126,7 +122,7 @@ export class SwipeableMixin {
126
122
  }
127
123
  let i = 0;
128
124
  for (const item of indicatorElement.children) {
129
- item.classList[ i === index ? 'add' : 'remove' ]('active');
125
+ addOrRemoveClass(item, 'active', i === index);
130
126
  i++;
131
127
  }
132
128
  }
package/src/layout/raf.js CHANGED
@@ -89,6 +89,7 @@ export default class RafLayout {
89
89
 
90
90
  notifyUpdate && notifyUpdate(...argsForNotifyUpdate.value);
91
91
 
92
+ // TODO: should the signature be aligned with _render?
92
93
  this._afterRender(this._element, state);
93
94
  }
94
95
 
@@ -0,0 +1,103 @@
1
+ export function setOrRemoveAttribute(element, name, value) {
2
+ if (value === undefined) {
3
+ element.removeAttribute(name);
4
+ } else {
5
+ element.setAttribute(name, value);
6
+ }
7
+ }
8
+
9
+ export function addOrRemoveClass(element, name, add) {
10
+ if (add) {
11
+ element.classList.add(name);
12
+ } else {
13
+ element.classList.remove(name);
14
+ }
15
+ }
16
+
17
+ export function resolveCssLength(str) {
18
+ if (typeof str === 'number') {
19
+ return str;
20
+ }
21
+ const [value, unit] = parseLength(str);
22
+ switch (unit) {
23
+ case '':
24
+ case 'px':
25
+ return value;
26
+ case 'rem':
27
+ return remToPx(value);
28
+ default:
29
+ throw new Error(`Unsupported unit: ${unit}`);
30
+ }
31
+ }
32
+
33
+ function parseLength(str) {
34
+ const match = str.match(/^([+-]?(?:\d+|\d*\.\d+))([a-z]*)$/);
35
+ if (!match) {
36
+ throw new Error(`Invalid length: ${str}`);
37
+ }
38
+ const value = parseFloat(match[1]);
39
+ const unit = match[2] || '';
40
+ return [value, unit];
41
+ }
42
+
43
+ function remToPx(value = 1) {
44
+ return value * parseFloat(getComputedStyle(document.documentElement).fontSize);
45
+ }
46
+
47
+ export function cssAspectRatio(value) {
48
+ return (value === undefined || value instanceof CssAspectRatio) ? value : new CssAspectRatio(value);
49
+ }
50
+
51
+ class CssAspectRatio {
52
+
53
+ constructor(value) {
54
+ this._width = NaN;
55
+ this._height = NaN;
56
+ switch (typeof value) {
57
+ case 'number':
58
+ this._width = value;
59
+ this._height = 1;
60
+ break;
61
+ case 'object':
62
+ if (Array.isArray(value)) {
63
+ this._width = parseFloat(value[0]);
64
+ this._height = parseFloat(value[1]);
65
+ }
66
+ break;
67
+ case 'string':
68
+ const i = value.indexOf('/');
69
+ if (i < 0) {
70
+ this._width = parseFloat(value);
71
+ this._height = 1;
72
+ } else {
73
+ this._width = parseFloat(value.slice(0, i).trim());
74
+ this._height = parseFloat(value.slice(i + 1).trim());
75
+ }
76
+ break;
77
+ }
78
+ if (this._width === NaN || this._height === NaN) {
79
+ throw new Error(`Invalid aspect ratio value: ${value}`);
80
+ }
81
+ }
82
+
83
+ get width() {
84
+ return this._width;
85
+ }
86
+
87
+ get height() {
88
+ return this._height;
89
+ }
90
+
91
+ get floatValue() {
92
+ return this._width / this._height;
93
+ }
94
+
95
+ scaleBy(factor) {
96
+ factor = cssAspectRatio(factor);
97
+ return cssAspectRatio([factor._width * this._width, factor._height * this._height]);
98
+ }
99
+
100
+ toString() {
101
+ return this._height === 1 ? `${this._width}` : `${this._width} / ${this._height}`;
102
+ }
103
+ }
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export default '1.11.5-beta.1';
1
+ export default '1.11.5-beta.3';
@@ -2,7 +2,7 @@ import { API } from '@miso.ai/commons';
2
2
  import Workflow from './base.js';
3
3
  import { fields, FeedbackActor } from '../actor/index.js';
4
4
  import { ROLE, STATUS, ORGANIC_QUESTION_SOURCE } from '../constants.js';
5
- import { SearchBoxLayout, TextLayout, ListLayout, CarouselLayout, TypewriterLayout, FeedbackLayout, AffiliationLayout } from '../layout/index.js';
5
+ import { SearchBoxLayout, TextLayout, ListLayout, CarouselLayout, GalleryLayout, TypewriterLayout, FeedbackLayout, AffiliationLayout } from '../layout/index.js';
6
6
  import { processData as processAffiliationData } from '../affiliation/index.js';
7
7
  import { mergeRolesOptions, DEFAULT_TRACKER_OPTIONS } from './options/index.js';
8
8
  import { writeAnswerStageToMeta, mergeInteraction } from './processors.js';
@@ -35,7 +35,7 @@ const DEFAULT_LAYOUTS = Object.freeze({
35
35
  [ROLE.QUESTION]: [TextLayout.type, { tag: 'h2' }],
36
36
  [ROLE.ANSWER]: TypewriterLayout.type,
37
37
  [ROLE.FEEDBACK]: FeedbackLayout.type,
38
- [ROLE.IMAGES]: [CarouselLayout.type, { incremental: true, itemType: 'image' }],
38
+ [ROLE.IMAGES]: [GalleryLayout.type, { incremental: true, itemType: 'image' }],
39
39
  [ROLE.SOURCES]: [ListLayout.type, { incremental: true, itemType: 'article', templates: { ordered: true } }],
40
40
  [ROLE.AFFILIATION]: [AffiliationLayout.type, { incremental: true, itemType: 'affiliation', link: { rel: 'noopener nofollow' } }],
41
41
  });
@@ -121,7 +121,7 @@ export default class Explore extends Workflow {
121
121
  return data;
122
122
  }
123
123
  const linkFn = this._linkFn && this._linkFn[0];
124
- const related_questions = value.related_questions.map(linkFn ? (text => ({ text, url: this._getSubmitUrl({ q: text }, { generated: true }) })) : (text => ({ text })));
124
+ const related_questions = value.related_questions.map(linkFn ? (text => ({ text, url: this._getSubmitUrl({ q: text, generated: true }) })) : (text => ({ text })));
125
125
  return {
126
126
  ...data,
127
127
  value: {
@@ -143,12 +143,12 @@ export default class Explore extends Workflow {
143
143
  }
144
144
 
145
145
  _query(args) {
146
- this._submitToPage(args);
146
+ this._submitToPage({ ...args, generated: false });
147
147
  }
148
148
 
149
- _getSubmitUrl(args, { generated = false } = {}) {
149
+ _getSubmitUrl(args) {
150
150
  let url = UseLinkMixin.prototype._getSubmitUrl.call(this, args);
151
- if (!generated || !this._productId) {
151
+ if (!args.generated || !this._productId) {
152
152
  return url;
153
153
  }
154
154
  return `${url}&qs=${encodeURIComponent(this._productId)}`;
@@ -30,11 +30,11 @@ export class UseLinkMixin {
30
30
  }
31
31
  }
32
32
 
33
- _getSubmitUrl(args) {
33
+ _getSubmitUrl({ q, ...args } = {}) {
34
34
  if (!this._linkFn) {
35
35
  throw new Error('useLink(fn) must be called before _getSubmitUrl()');
36
36
  }
37
- return this._linkFn[0](args.q);
37
+ return this._linkFn[0](q, args);
38
38
  }
39
39
 
40
40
  }