@miso.ai/client-sdk-ui 1.11.0-beta.9 → 1.11.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.
Files changed (111) hide show
  1. package/dist/cjs/actor/feedback.js +6 -39
  2. package/dist/cjs/actor/fields.js +14 -6
  3. package/dist/cjs/actor/index.js +0 -7
  4. package/dist/cjs/actor/interactions.js +4 -15
  5. package/dist/cjs/actor/sessions.js +2 -32
  6. package/dist/cjs/actor/tracker.js +25 -26
  7. package/dist/cjs/actor/utils.js +0 -49
  8. package/dist/cjs/actor/view.js +40 -40
  9. package/dist/cjs/actor/views.js +29 -8
  10. package/dist/cjs/constants.js +18 -19
  11. package/dist/cjs/defaults/hybrid-search/phrases.js +3 -3
  12. package/dist/cjs/defaults/hybrid-search/templates.js +22 -7
  13. package/dist/cjs/element/role/index.js +12 -5
  14. package/dist/cjs/element/role/{miso-hits.js → miso-more.js} +4 -4
  15. package/dist/cjs/element/role/miso-total.js +20 -0
  16. package/dist/cjs/layout/affiliation/affiliation.js +0 -3
  17. package/dist/cjs/layout/banner/banner.js +0 -4
  18. package/dist/cjs/layout/button/more-button.js +73 -0
  19. package/dist/cjs/layout/container/container.js +0 -3
  20. package/dist/cjs/layout/error/error.js +0 -4
  21. package/dist/cjs/layout/index.js +7 -0
  22. package/dist/cjs/layout/input/facets.js +0 -4
  23. package/dist/cjs/layout/input/option-list.js +0 -4
  24. package/dist/cjs/layout/input/search-box.js +13 -12
  25. package/dist/cjs/layout/list/cards.js +0 -3
  26. package/dist/cjs/layout/list/carousel.js +0 -3
  27. package/dist/cjs/layout/list/collection.js +22 -7
  28. package/dist/cjs/layout/list/list.js +0 -3
  29. package/dist/cjs/layout/radio/feedback.js +0 -3
  30. package/dist/cjs/layout/radio/radio.js +0 -4
  31. package/dist/cjs/layout/raf.js +4 -2
  32. package/dist/cjs/layout/text/text.js +0 -4
  33. package/dist/cjs/layout/text/typewriter/index.js +0 -3
  34. package/dist/cjs/layout/triggerable.js +79 -0
  35. package/dist/cjs/util/states.js +0 -15
  36. package/dist/cjs/version.js +1 -1
  37. package/dist/cjs/workflow/answer-based.js +70 -105
  38. package/dist/cjs/workflow/ask.js +29 -27
  39. package/dist/cjs/workflow/asks.js +1 -1
  40. package/dist/cjs/workflow/autocompletable.js +33 -0
  41. package/dist/cjs/workflow/autocomplete.js +122 -0
  42. package/dist/cjs/workflow/base.js +156 -98
  43. package/dist/cjs/workflow/explore.js +48 -46
  44. package/dist/cjs/workflow/hybrid-search-answer.js +17 -7
  45. package/dist/cjs/workflow/hybrid-search-results.js +17 -87
  46. package/dist/cjs/workflow/hybrid-search-views.js +122 -0
  47. package/dist/cjs/workflow/hybrid-search.js +47 -168
  48. package/dist/cjs/workflow/options.js +32 -17
  49. package/dist/cjs/workflow/processors.js +166 -2
  50. package/dist/cjs/workflow/recommendation.js +36 -49
  51. package/dist/cjs/workflow/search-based.js +228 -0
  52. package/dist/cjs/workflow/search.js +21 -68
  53. package/dist/cjs/workflow/use-link.js +44 -0
  54. package/package.json +2 -2
  55. package/src/actor/feedback.js +6 -23
  56. package/src/actor/fields.js +14 -6
  57. package/src/actor/index.js +0 -1
  58. package/src/actor/interactions.js +4 -10
  59. package/src/actor/sessions.js +3 -31
  60. package/src/actor/tracker.js +25 -19
  61. package/src/actor/utils.js +0 -47
  62. package/src/actor/view.js +27 -15
  63. package/src/actor/views.js +32 -8
  64. package/src/constants.js +25 -16
  65. package/src/defaults/hybrid-search/phrases.js +2 -2
  66. package/src/defaults/hybrid-search/templates.js +17 -5
  67. package/src/element/role/index.js +2 -1
  68. package/src/element/role/{miso-hits.js → miso-more.js} +3 -3
  69. package/src/element/role/miso-total.js +16 -0
  70. package/src/layout/affiliation/affiliation.js +1 -5
  71. package/src/layout/banner/banner.js +0 -5
  72. package/src/layout/button/more-button.js +69 -0
  73. package/src/layout/container/container.js +1 -5
  74. package/src/layout/error/error.js +0 -5
  75. package/src/layout/index.js +2 -1
  76. package/src/layout/input/facets.js +0 -5
  77. package/src/layout/input/option-list.js +0 -5
  78. package/src/layout/input/search-box.js +11 -14
  79. package/src/layout/list/cards.js +0 -4
  80. package/src/layout/list/carousel.js +0 -4
  81. package/src/layout/list/collection.js +21 -9
  82. package/src/layout/list/list.js +0 -4
  83. package/src/layout/radio/feedback.js +0 -4
  84. package/src/layout/radio/radio.js +0 -5
  85. package/src/layout/raf.js +2 -2
  86. package/src/layout/text/text.js +0 -5
  87. package/src/layout/text/typewriter/index.js +1 -5
  88. package/src/layout/triggerable.js +76 -0
  89. package/src/util/states.js +0 -14
  90. package/src/version.js +1 -1
  91. package/src/workflow/answer-based.js +61 -72
  92. package/src/workflow/ask.js +30 -24
  93. package/src/workflow/asks.js +1 -1
  94. package/src/workflow/autocompletable.js +29 -0
  95. package/src/workflow/autocomplete.js +113 -0
  96. package/src/workflow/base.js +133 -87
  97. package/src/workflow/explore.js +38 -30
  98. package/src/workflow/hybrid-search-answer.js +17 -6
  99. package/src/workflow/hybrid-search-results.js +17 -77
  100. package/src/workflow/hybrid-search-views.js +124 -0
  101. package/src/workflow/hybrid-search.js +47 -157
  102. package/src/workflow/options.js +26 -11
  103. package/src/workflow/processors.js +151 -3
  104. package/src/workflow/recommendation.js +32 -33
  105. package/src/workflow/search-based.js +222 -0
  106. package/src/workflow/search.js +21 -65
  107. package/src/workflow/use-link.js +40 -0
  108. package/dist/cjs/actor/autocomplete.js +0 -160
  109. package/dist/cjs/workflow/utils.js +0 -42
  110. package/src/actor/autocomplete.js +0 -133
  111. package/src/workflow/utils.js +0 -29
@@ -1,39 +1,49 @@
1
- import { defineValues, trimObj, API } from '@miso.ai/commons';
1
+ import { defineValues, API } from '@miso.ai/commons';
2
2
  import Workflow from './base.js';
3
- import { mergeApiOptions } from './options.js';
4
- import { fields } from '../actor/index.js';
5
3
  import { ListLayout } from '../layout/index.js';
6
4
  import { ROLE } from '../constants.js';
5
+ import { mergeRolesOptions } from './options.js';
6
+ import { mergeInteraction } from './processors.js';
7
7
 
8
8
  const DEFAULT_API_OPTIONS = Object.freeze({
9
+ ...Workflow.DEFAULT_API_OPTIONS,
9
10
  group: API.GROUP.RECOMMENDATION,
10
11
  name: API.NAME.USER_TO_PRODUCTS,
11
12
  payload: {
13
+ ...Workflow.DEFAULT_API_OPTIONS.payload,
12
14
  fl: ['*'],
13
15
  },
14
16
  });
15
17
 
16
18
  const DEFAULT_LAYOUTS = Object.freeze({
19
+ ...Workflow.DEFAULT_LAYOUTS,
17
20
  [ROLE.PRODUCTS]: ListLayout.type,
18
21
  });
19
22
 
20
23
  const DEFAULT_TRACKERS = Object.freeze({
24
+ ...Workflow.DEFAULT_TRACKERS,
21
25
  [ROLE.PRODUCTS]: {},
22
26
  });
23
27
 
24
28
  const DEFAULT_OPTIONS = Object.freeze({
29
+ ...Workflow.DEFAULT_OPTIONS,
25
30
  api: DEFAULT_API_OPTIONS,
26
31
  layouts: DEFAULT_LAYOUTS,
27
32
  trackers: DEFAULT_TRACKERS,
28
33
  });
29
34
 
35
+ const ROLES_OPTIONS = mergeRolesOptions(Workflow.ROLES_OPTIONS, {
36
+ main: ROLE.PRODUCTS,
37
+ members: Object.keys(DEFAULT_LAYOUTS),
38
+ });
39
+
30
40
  export default class Recommendation extends Workflow {
31
41
 
32
42
  constructor(context, id) {
33
43
  super({
34
44
  name: 'recommendation',
35
45
  context,
36
- roles: Object.keys(DEFAULT_LAYOUTS),
46
+ roles: ROLES_OPTIONS,
37
47
  defaults: DEFAULT_OPTIONS,
38
48
  id,
39
49
  });
@@ -45,28 +55,15 @@ export default class Recommendation extends Workflow {
45
55
  defineValues(this, { id });
46
56
  }
47
57
 
48
- _initReset(args) {
58
+ _initSession(args) {
49
59
  this._context._members.set(args.id, this);
50
- super._initReset(args);
60
+ super._initSession(args);
51
61
  }
52
62
 
53
63
  // lifecycle //
54
64
  start() {
55
- this._sessions.start();
56
65
  // in recommendation workflow, start() triggers query
57
- // TODO: we should still make the query lifecycle
58
- const { session } = this;
59
- this._hub.update(fields.request(), mergeApiOptions(this._options.resolved.api, { session }));
60
- return this;
61
- }
62
-
63
- // TODO: deprecate this
64
- startTracker() {
65
- this.useApi(false);
66
- this.useLayouts(false);
67
- this._sessions.start();
68
- this.notifyViewUpdate(ROLE.CONTAINER);
69
- this.notifyViewUpdate(ROLE.PRODUCTS);
66
+ this._request();
70
67
  return this;
71
68
  }
72
69
 
@@ -76,21 +73,23 @@ export default class Recommendation extends Workflow {
76
73
  }
77
74
 
78
75
  // interactions //
79
- _preprocessInteraction(payload) {
80
- payload = super._preprocessInteraction(payload) || {};
81
- const { context = {} } = payload;
82
- const { custom_context = {} } = context;
83
- return {
84
- ...payload,
76
+ _defaultProcessInteraction(payload, args) {
77
+ payload = super._defaultProcessInteraction(payload, args);
78
+ payload = this._writeUnitIdToInteraction(payload, args);
79
+ return payload;
80
+ }
81
+
82
+ _writeUnitIdToInteraction(payload) {
83
+ const unit_id = this.id;
84
+ const unit_instance_uuid = this.uuid;
85
+ return mergeInteraction(payload, {
85
86
  context: {
86
- ...context,
87
- custom_context: trimObj({
88
- unit_id: this.id,
89
- unit_instance_uuid: this.uuid,
90
- ...custom_context,
91
- }),
87
+ custom_context: {
88
+ unit_id,
89
+ unit_instance_uuid,
90
+ },
92
91
  },
93
- };
92
+ });
94
93
  }
95
94
 
96
95
  // destroy //
@@ -0,0 +1,222 @@
1
+ import { API } from '@miso.ai/commons';
2
+ import Workflow from './base.js';
3
+ import { fields } from '../actor/index.js';
4
+ import { STATUS, ROLE } from '../constants.js';
5
+ import { SearchBoxLayout, ListLayout, TextLayout, FacetsLayout, MoreButtonLayout } from '../layout/index.js';
6
+ import { writeKeywordsToData, writeFiltersToPayload, retainFacetCountsInData, writeExhaustionToData, concatItemsFromMoreResponse } from './processors.js';
7
+ import { mergeRolesOptions, makeConfigurable } from './options.js';
8
+
9
+ const DEFAULT_ROWS = 10;
10
+ const DEFAULT_PAGE_LIMIT = 10;
11
+
12
+ const DEFAULT_API_OPTIONS = Object.freeze({
13
+ ...Workflow.DEFAULT_API_OPTIONS,
14
+ group: API.GROUP.SEARCH,
15
+ payload: {
16
+ ...Workflow.DEFAULT_API_OPTIONS.payload,
17
+ fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at', 'title'],
18
+ rows: DEFAULT_ROWS,
19
+ },
20
+ });
21
+
22
+ const DEFAULT_LAYOUTS = Object.freeze({
23
+ ...Workflow.DEFAULT_LAYOUTS,
24
+ [ROLE.QUERY]: [SearchBoxLayout.type],
25
+ [ROLE.PRODUCTS]: [ListLayout.type, { incremental: true, infiniteScroll: true }],
26
+ [ROLE.KEYWORDS]: [TextLayout.type, { raw: true }],
27
+ [ROLE.TOTAL]: [TextLayout.type, { raw: true, format: 'number' }],
28
+ [ROLE.FACETS]: [FacetsLayout.type],
29
+ [ROLE.MORE]: [MoreButtonLayout.type],
30
+ });
31
+
32
+ const DEFAULT_TRACKERS = Object.freeze({
33
+ ...Workflow.DEFAULT_TRACKERS,
34
+ [ROLE.PRODUCTS]: {},
35
+ });
36
+
37
+ const DEFAULT_PAGINATION = Object.freeze({
38
+ active: false,
39
+ mode: 'infiniteScroll',
40
+ pageLimit: 10,
41
+ });
42
+
43
+ const DEFAULT_OPTIONS = Object.freeze({
44
+ ...Workflow.DEFAULT_OPTIONS,
45
+ api: DEFAULT_API_OPTIONS,
46
+ layouts: DEFAULT_LAYOUTS,
47
+ trackers: DEFAULT_TRACKERS,
48
+ pagination: DEFAULT_PAGINATION,
49
+ });
50
+
51
+ const ROLES_OPTIONS = mergeRolesOptions(Workflow.ROLES_OPTIONS, {
52
+ main: ROLE.PRODUCTS,
53
+ members: Object.keys(DEFAULT_LAYOUTS),
54
+ mappings: {
55
+ [ROLE.FACETS]: 'facet_counts',
56
+ },
57
+ });
58
+
59
+ export default class SearchBasedWorkflow extends Workflow {
60
+
61
+ _initSubscriptions(args) {
62
+ super._initSubscriptions(args);
63
+ this._unsubscribes = [
64
+ ...this._unsubscribes,
65
+ this._hub.on(fields.query(), args => this._query(args)),
66
+ this._hub.on(fields.filters(), filters => this._refine(filters)),
67
+ this._hub.on(fields.more(), () => this._more()),
68
+ ];
69
+ }
70
+
71
+ restart() {
72
+ super.restart();
73
+ this._page = 0;
74
+ }
75
+
76
+ // properties //
77
+ get exhausted() {
78
+ const data = this._hub.states[fields.data()];
79
+ return !!(data && data.meta && data.meta.exhausted);
80
+ }
81
+
82
+ get page() {
83
+ return this._page;
84
+ }
85
+
86
+ // query //
87
+ query(args) {
88
+ if (!args.q) {
89
+ throw new Error(`q is required in query() call`);
90
+ }
91
+ this._hub.update(fields.query(), args);
92
+ }
93
+
94
+ _query(args) {
95
+ // keep track of the query args for refine and more calls
96
+ this._queryArgs = args;
97
+
98
+ // start a new session
99
+ this.restart();
100
+
101
+ // payload
102
+ const payload = this._buildPayload(args);
103
+
104
+ this._request({ payload });
105
+ }
106
+
107
+ _refine(filters) {
108
+ // remember current facet_counts
109
+ const { facet_counts } = this._hub.states[fields.data()].value || {};
110
+ this._currentFacetCounts = facet_counts;
111
+
112
+ // start a new session
113
+ this.restart();
114
+
115
+ // payload
116
+ const query = this._getQuery();
117
+ const payload = this._buildPayload({ ...query, filters });
118
+
119
+ this._request({ payload });
120
+ }
121
+
122
+ _more() {
123
+ // if still loading, complain
124
+ if (this.status !== STATUS.READY) {
125
+ throw new Error(`Can not call more() while loading.`);
126
+ }
127
+ // no more pages, ignore
128
+ if (this.exhausted || this._page >= DEFAULT_PAGE_LIMIT - 1) {
129
+ return;
130
+ }
131
+ // don't create a new session!
132
+
133
+ this._page++;
134
+
135
+ // payload
136
+ const query = this._getQuery();
137
+ const filters = this._hub.states[fields.filters()]; // get stored filters
138
+ let payload = this._buildPayload({ ...query, filters });
139
+ payload = this._processPayloadForMoreRequest(payload);
140
+
141
+ this._request({ payload });
142
+ }
143
+
144
+ _getQuery() {
145
+ // TODO: just get from the hub
146
+ return this._queryArgs;
147
+ }
148
+
149
+ _getPageSize() {
150
+ return this._options.resolved.api.payload.rows;
151
+ }
152
+
153
+ _buildPayload({ filters, ...payload } = {}) {
154
+ payload = writeFiltersToPayload(payload, filters);
155
+ payload = this._writePageInfoToPayload(payload);
156
+ return payload;
157
+ }
158
+
159
+ _writePageInfoToPayload(payload) {
160
+ return {
161
+ ...payload,
162
+ _meta: {
163
+ ...payload._meta,
164
+ page: this._page,
165
+ },
166
+ };
167
+ }
168
+
169
+ _processPayloadForMoreRequest(payload = {}) {
170
+ const data = this._hub.states[fields.data()];
171
+ const { [this._roles.main]: items = [] } = (data && data.value) || {};
172
+ const productIds = items.map(item => item.product_id).filter(v => v);
173
+ const exclude = [...(payload.exclude || []), ...productIds];
174
+ if (exclude.length === 0) {
175
+ return payload;
176
+ }
177
+ return {
178
+ ...payload,
179
+ exclude,
180
+ };
181
+ }
182
+
183
+ // data //
184
+ _defaultProcessData(data) {
185
+ data = super._defaultProcessData(data);
186
+ data = writeKeywordsToData(data);
187
+ data = retainFacetCountsInData(data, this._currentFacetCounts);
188
+ data = writeExhaustionToData(data, { role: this._roles.main });
189
+ return data;
190
+ }
191
+
192
+ _updateDataInHub(data) {
193
+ data = this._appendResultsFromMoreRequest(data);
194
+ super._updateDataInHub(data);
195
+ }
196
+
197
+ _appendResultsFromMoreRequest(data) {
198
+ const { request } = data;
199
+ if (!request) {
200
+ return data; // the initial state
201
+ }
202
+ const { _meta } = request.payload;
203
+ if (!_meta || !_meta.page) {
204
+ return data; // not from "more" request
205
+ }
206
+ // concat records if it's from "more" request
207
+ const currentData = this._hub.states[fields.data()];
208
+ return concatItemsFromMoreResponse(currentData, data, { role: this._roles.main });
209
+ }
210
+
211
+ }
212
+
213
+ makeConfigurable(SearchBasedWorkflow.prototype, ['pagination']);
214
+
215
+ Object.assign(SearchBasedWorkflow, {
216
+ DEFAULT_API_OPTIONS,
217
+ DEFAULT_LAYOUTS,
218
+ DEFAULT_TRACKERS,
219
+ DEFAULT_PAGINATION,
220
+ DEFAULT_OPTIONS,
221
+ ROLES_OPTIONS,
222
+ });
@@ -1,11 +1,7 @@
1
1
  import { API } from '@miso.ai/commons';
2
- import Workflow from './base.js';
3
- import { mergeApiOptions } from './options.js';
4
- import * as sources from '../source.js';
5
- import { fields, AutocompleteActor } from '../actor/index.js';
6
- import { ROLE, DATA_ASPECT } from '../constants.js';
7
- import { ListLayout, SearchBoxLayout } from '../layout/index.js';
8
- import { makeConfigurable } from './options.js';
2
+ import { ROLE } from '../constants.js';
3
+ import SearchBasedWorkflow from './search-based.js';
4
+ import { makeAutocompletable } from './autocompletable.js';
9
5
 
10
6
  const DEFAULT_API_OPTIONS = Object.freeze({
11
7
  group: API.GROUP.SEARCH,
@@ -16,7 +12,6 @@ const DEFAULT_API_OPTIONS = Object.freeze({
16
12
  });
17
13
 
18
14
  const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
19
- actor: false,
20
15
  api: {
21
16
  group: API.GROUP.SEARCH,
22
17
  name: API.NAME.AUTOCOMPLETE,
@@ -25,54 +20,43 @@ const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
25
20
  fl: ['title', 'url', 'cover_image'],
26
21
  },
27
22
  },
28
- throttle: 300,
29
23
  });
30
24
 
31
25
  const DEFAULT_LAYOUTS = Object.freeze({
32
- [ROLE.QUERY]: SearchBoxLayout.type,
33
- [ROLE.PRODUCTS]: ListLayout.type,
26
+ ...SearchBasedWorkflow.DEFAULT_LAYOUTS,
34
27
  });
35
28
 
36
29
  const DEFAULT_TRACKERS = Object.freeze({
37
- [ROLE.PRODUCTS]: {},
30
+ ...SearchBasedWorkflow.DEFAULT_TRACKERS,
38
31
  });
39
32
 
40
33
  const DEFAULT_OPTIONS = Object.freeze({
34
+ ...SearchBasedWorkflow.DEFAULT_OPTIONS,
41
35
  api: DEFAULT_API_OPTIONS,
42
36
  autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
43
37
  layouts: DEFAULT_LAYOUTS,
44
38
  trackers: DEFAULT_TRACKERS,
45
39
  });
46
40
 
47
- export default class Search extends Workflow {
41
+ const ROLES_OPTIONS = SearchBasedWorkflow.ROLES_OPTIONS;
42
+
43
+ export default class Search extends SearchBasedWorkflow {
48
44
 
49
45
  constructor(plugin, client) {
50
46
  super({
51
47
  name: 'search',
52
48
  plugin,
53
49
  client,
54
- roles: Object.keys(DEFAULT_LAYOUTS),
50
+ roles: ROLES_OPTIONS,
55
51
  defaults: DEFAULT_OPTIONS,
56
52
  });
57
53
  }
58
54
 
59
- _initActors(args) {
60
- super._initActors(args);
61
- this._autocomplete = new AutocompleteActor(this._hub, {
62
- source: sources.api(this._client),
63
- options: this._options,
64
- });
55
+ _initProperties(args) {
56
+ super._initProperties(args);
57
+ this._initAutocomplete(args);
65
58
  }
66
59
 
67
- _initSubscriptions(args) {
68
- super._initSubscriptions(args);
69
- this._unsubscribes = [
70
- ...this._unsubscribes,
71
- this._hub.on(fields.query(), args => this._query(args)),
72
- ];
73
- }
74
-
75
- // lifecycle //
76
60
  autoQuery({ setValue = true, focus = true } = {}) {
77
61
  const q = new URLSearchParams(window.location.search).get('q');
78
62
  const { layout } = this._views.get(ROLE.QUERY);
@@ -89,46 +73,18 @@ export default class Search extends Workflow {
89
73
  }
90
74
  }
91
75
 
92
- query(args) {
93
- if (!args.q) {
94
- throw new Error(`q is required in query() call`);
95
- }
96
- this._hub.update(fields.query(), args);
97
- }
98
-
99
- _query(args) {
100
- // start a new session
101
- this.restart();
102
- const { session } = this;
103
-
104
- // build payload and trigger request
105
- const payload = this._buildPayload(args);
106
- const event = mergeApiOptions(this._options.resolved.api, { payload, session });
107
- this._request(event);
108
- }
109
-
110
- _buildPayload(args) {
111
- return args;
112
- }
113
-
114
- _request(event) {
115
- this._hub.update(fields.request(), event);
116
- }
117
-
118
- updateCompletions(event) {
119
- // TODO: verify
120
- this._hub.update(fields.data(DATA_ASPECT.AUTOCOMPLETE), {
121
- ...event,
122
- source: 'manual',
123
- });
124
- return this;
125
- }
126
-
127
76
  notifyViewUpdate(role = ROLE.PRODUCTS, ...args) {
128
77
  super.notifyViewUpdate(role, ...args);
129
78
  return this;
130
79
  }
131
80
 
81
+ // destroy //
82
+ _destroy(options) {
83
+ this._feedback._destroy();
84
+ this._destroyAutocomplete();
85
+ super._destroy(options);
86
+ }
87
+
132
88
  }
133
89
 
134
- makeConfigurable(Search.prototype, ['autocomplete']);
90
+ makeAutocompletable(Search.prototype);
@@ -0,0 +1,40 @@
1
+ import { mixin } from '@miso.ai/commons';
2
+
3
+ export function enableUseLink(prototype) {
4
+ mixin(prototype, UseLinkMixin.prototype);
5
+ }
6
+
7
+ export class UseLinkMixin {
8
+
9
+ useLink(fn, options = {}) {
10
+ if (typeof fn !== 'function' && fn !== false) {
11
+ throw new Error('useLink(fn) expects fn to be a function or false');
12
+ }
13
+ this._linkFn = fn ? [fn, options] : false;
14
+ return this;
15
+ }
16
+
17
+ _submitToPage(args = {}) {
18
+ if (!this._linkFn) {
19
+ return;
20
+ }
21
+ // TODO: we want a submit event
22
+ const url = this._getSubmitUrl(args);
23
+ const [_, options = {}] = this._linkFn;
24
+ if (options.open === false) {
25
+ window.location.href = url;
26
+ } else {
27
+ const target = options.target || '_blank';
28
+ const windowFeatures = options.windowFeatures || '';
29
+ window.open(url, target, windowFeatures);
30
+ }
31
+ }
32
+
33
+ _getSubmitUrl(args) {
34
+ if (!this._linkFn) {
35
+ throw new Error('useLink(fn) must be called before _getSubmitUrl()');
36
+ }
37
+ return this._linkFn[0](args.q);
38
+ }
39
+
40
+ }
@@ -1,160 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var _constants = require("../constants.js");
8
- var fields = _interopRequireWildcard(require("./fields.js"));
9
- function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
10
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
11
- class AutocompleteActor {
12
- constructor(hub, _ref) {
13
- let {
14
- source,
15
- options
16
- } = _ref;
17
- this._hub = hub;
18
- this._source = source;
19
- this._options = options;
20
- this._index = -1;
21
-
22
- // TODO: listen to request:input instead
23
- this._unsubscribes = [hub.on(fields.input(), () => this._sync()), options.on('autocomplete', () => this._syncOptions())];
24
-
25
- // sync right away
26
- this._sync(true);
27
- }
28
- get active() {
29
- return this._options.resolved.autocomplete.actor !== false;
30
- }
31
- get source() {
32
- return this._source;
33
- }
34
- _syncOptions() {
35
- // TODO: explore scenarios where options.actor true -> false
36
- this._sync(true);
37
- }
38
- async _sync() {
39
- let force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
40
- // protocol: inactive -> no reaction
41
- if (!this.active) {
42
- return;
43
- }
44
- const {
45
- input
46
- } = this._hub.states;
47
- const value = input && input.value && input.value.trim() || undefined;
48
- if (sameInput(this._input, input) && !force) {
49
- return;
50
- }
51
- this._input = input;
52
- const index = ++this._index;
53
-
54
- // new input, abort previous data fetch if any
55
- this._clearTimeout();
56
- this._abortPendingRequest({
57
- type: 'new-input',
58
- message: 'A new input renders previous autocomplete request obsolete.'
59
- });
60
-
61
- // emit loading/initial state
62
- this._emitCompletions({
63
- index,
64
- input,
65
- status: value ? _constants.STATUS.LOADING : _constants.STATUS.INITIAL
66
- });
67
- if (!value) {
68
- return;
69
- }
70
- try {
71
- const response = await this._fetchCompletions(input);
72
- if (this._index !== index) {
73
- return; // new results loading, discard this one
74
- }
75
- this._emitCompletions({
76
- index,
77
- input,
78
- status: _constants.STATUS.READY,
79
- value: response.completions
80
- });
81
- } catch (error) {
82
- this._error(error);
83
- if (this._index !== index) {
84
- return; // ignore
85
- }
86
- this._emitCompletions({
87
- index,
88
- input,
89
- status: _constants.STATUS.ERRONEOUS,
90
- error
91
- });
92
- }
93
- }
94
- async _getCompletions(input) {
95
- // TODO: cache
96
- return await this._fetchCompletions(input);
97
- }
98
- async _fetchCompletions(_ref2) {
99
- let {
100
- value
101
- } = _ref2;
102
- // TODO: other options?
103
- const {
104
- api
105
- } = this._options.resolved.autocomplete;
106
- const {
107
- group,
108
- name
109
- } = api;
110
- const {
111
- signal
112
- } = this._ac || {};
113
- const options = Object.assign({}, api.options, {
114
- signal
115
- });
116
- const payload = Object.assign({}, api.payload, {
117
- q: value
118
- });
119
- return await this._source({
120
- group,
121
- name,
122
- payload,
123
- options
124
- });
125
- }
126
- _clearTimeout() {
127
- if (this._timeoutId) {
128
- clearTimeout(this._timeoutId);
129
- this._timeoutId = undefined;
130
- }
131
- }
132
- _abortPendingRequest(info) {
133
- this._ac && this._ac.abort(info);
134
- this._ac = new AbortController();
135
- }
136
- _emitCompletions(event) {
137
- event.source = 'actor';
138
- // TDOO: update response:autocomplete instead
139
- this._hub.update(fields.data(_constants.DATA_ASPECT.AUTOCOMPLETE), event);
140
- }
141
- _error(error) {
142
- // TODO
143
- console.error(error);
144
- }
145
- _destroy() {
146
- // abort ongoing data fetch if any
147
- this._ac && this._ac.abort({
148
- type: 'autocomplete-actor-destroy',
149
- message: 'Autocomplete actor is destroyed.'
150
- });
151
- for (const unsubscribe of this._unsubscribes) {
152
- unsubscribe();
153
- }
154
- this._unsubscribes = [];
155
- }
156
- }
157
- exports.default = AutocompleteActor;
158
- function sameInput(a, b) {
159
- return !a && !b || a && b && a.value === b.value;
160
- }