@miso.ai/client-sdk-ui 1.14.0-beta.2 → 1.14.0-beta.4
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/asset/svgs.js +4 -2
- package/dist/cjs/constants.js +2 -0
- package/dist/cjs/defaults/ask/index.js +2 -1
- package/dist/cjs/defaults/ask/templates.js +2 -1
- package/dist/cjs/defaults/hybrid-search/index.js +2 -1
- package/dist/cjs/defaults/hybrid-search/templates.js +2 -1
- package/dist/cjs/defaults/index.js +2 -1
- package/dist/cjs/defaults.js +107 -10
- package/dist/cjs/element/container/index.js +14 -0
- package/dist/cjs/element/container/miso-message-item.js +91 -0
- package/dist/cjs/element/container/miso-thread-item.js +91 -0
- package/dist/cjs/element/index.js +2 -1
- package/dist/cjs/element/role/index.js +7 -0
- package/dist/cjs/element/role/miso-delete.js +20 -0
- package/dist/cjs/layout/button/button.js +45 -5
- package/dist/cjs/layout/button/checkbox.js +2 -1
- package/dist/cjs/layout/container/item-container.js +99 -0
- package/dist/cjs/layout/index.js +14 -0
- package/dist/cjs/layout/input/search-box.js +9 -4
- package/dist/cjs/layout/list/collection.js +21 -4
- package/dist/cjs/layout/list/messages.js +261 -224
- package/dist/cjs/layout/list/threads.js +106 -150
- package/dist/cjs/layout/templates.js +98 -38
- package/dist/cjs/layout/text/question.js +71 -0
- package/dist/cjs/layout/text/typewriter/index.js +15 -21
- package/dist/cjs/plugin.js +6 -3
- package/dist/cjs/util/dom.js +21 -0
- package/dist/cjs/util/notify.js +74 -0
- package/dist/cjs/version.js +1 -1
- package/package.json +3 -3
- package/src/CLAUDE.md +7 -2
- package/src/asset/svgs.js +3 -1
- package/src/constants.js +2 -0
- package/src/defaults.js +75 -5
- package/src/element/container/index.js +2 -0
- package/src/element/container/miso-message-item.js +94 -0
- package/src/element/container/miso-thread-item.js +94 -0
- package/src/element/role/index.js +1 -0
- package/src/element/role/miso-delete.js +16 -0
- package/src/layout/button/button.js +40 -4
- package/src/layout/button/checkbox.js +2 -1
- package/src/layout/container/item-container.js +99 -0
- package/src/layout/index.js +2 -0
- package/src/layout/input/search-box.js +4 -2
- package/src/layout/list/collection.js +22 -5
- package/src/layout/list/messages.js +231 -201
- package/src/layout/list/threads.js +107 -128
- package/src/layout/templates.js +100 -38
- package/src/layout/text/question.js +58 -0
- package/src/layout/text/typewriter/index.js +11 -15
- package/src/plugin.js +3 -2
- package/src/util/dom.js +16 -0
- package/src/util/notify.js +68 -0
- package/src/version.js +1 -1
|
@@ -3,6 +3,7 @@ import { LAYOUT_TYPE } from '../../constants.js';
|
|
|
3
3
|
import TemplateBasedLayout from '../template.js';
|
|
4
4
|
import { getIcon } from '../../asset/svgs.js';
|
|
5
5
|
import prompt from '../../util/prompt.js';
|
|
6
|
+
import confirm from '../../util/confirm.js';
|
|
6
7
|
|
|
7
8
|
const TYPE = LAYOUT_TYPE.BUTTON;
|
|
8
9
|
const DEFAULT_CLASSNAME = 'miso-button';
|
|
@@ -10,7 +11,8 @@ const DEFAULT_CLASSNAME = 'miso-button';
|
|
|
10
11
|
function root(layout, state) {
|
|
11
12
|
const { className, role, templates } = layout;
|
|
12
13
|
const roleAttr = role ? ` data-role="${role}"` : '';
|
|
13
|
-
|
|
14
|
+
const disabledAttr = layout._isDisabled(state) ? ' disabled' : '';
|
|
15
|
+
return `<button type="button" class="${className}"${roleAttr}${disabledAttr}>${templates.icon(layout, state)}${templates.text(layout, state)}</button>`;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
function icon(layout) {
|
|
@@ -45,6 +47,19 @@ const DEFAULT_TEMPLATES = Object.freeze({
|
|
|
45
47
|
* dialog submits nothing. Use it for actions that take a text input, such
|
|
46
48
|
* as the rename button (role `rename`) of the conversation header, whose
|
|
47
49
|
* role maps to `'thread.title'`.
|
|
50
|
+
*
|
|
51
|
+
* With a `confirm` option, the click asks through the shared confirm dialog
|
|
52
|
+
* first, and submits only when confirmed; the option's `message` may be a
|
|
53
|
+
* function of the control's current value. Use it for destructive actions,
|
|
54
|
+
* such as the delete button (role `delete`) of a thread item's context menu.
|
|
55
|
+
*
|
|
56
|
+
* The control's current value is the role's mapped data value, put through
|
|
57
|
+
* the `value` option when given — map the role to a record and let `value`
|
|
58
|
+
* pick the text the dialogs work with. A `disabled` option (a boolean, or a
|
|
59
|
+
* function of the mapped value) renders the button's disabled state from
|
|
60
|
+
* the data, re-evaluated on every commit — e.g. rename/delete disable while
|
|
61
|
+
* their thread record has no thread id yet (a thread being created is not
|
|
62
|
+
* addressable), enabling in place when the resolution patches the record.
|
|
48
63
|
*/
|
|
49
64
|
export default class ButtonLayout extends TemplateBasedLayout {
|
|
50
65
|
|
|
@@ -84,7 +99,9 @@ export default class ButtonLayout extends TemplateBasedLayout {
|
|
|
84
99
|
if (!button || button.disabled) {
|
|
85
100
|
return;
|
|
86
101
|
}
|
|
87
|
-
this.options.prompt ? this._submitViaPrompt(event) :
|
|
102
|
+
this.options.prompt ? this._submitViaPrompt(event) :
|
|
103
|
+
this.options.confirm ? this._submitViaConfirm(event) :
|
|
104
|
+
this._submit(event);
|
|
88
105
|
}
|
|
89
106
|
|
|
90
107
|
_submit(event, payload) {
|
|
@@ -104,13 +121,32 @@ export default class ButtonLayout extends TemplateBasedLayout {
|
|
|
104
121
|
this._submit(event, { value });
|
|
105
122
|
}
|
|
106
123
|
|
|
124
|
+
async _submitViaConfirm(event) {
|
|
125
|
+
const { message, ...options } = this.options.confirm;
|
|
126
|
+
const confirmed = await confirm({
|
|
127
|
+
...options,
|
|
128
|
+
message: typeof message === 'function' ? message(this._currentValue()) : message,
|
|
129
|
+
});
|
|
130
|
+
if (!confirmed) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this._submit(event);
|
|
134
|
+
}
|
|
135
|
+
|
|
107
136
|
/**
|
|
108
137
|
* The current value of the control: the role's mapped data value, as of
|
|
109
|
-
* the latest render.
|
|
138
|
+
* the latest render, put through the `value` option when given.
|
|
110
139
|
*/
|
|
111
140
|
_currentValue() {
|
|
112
141
|
const state = this._element && this._rendered.get(this._element);
|
|
113
|
-
|
|
142
|
+
const raw = state && state.value;
|
|
143
|
+
const { value } = this.options;
|
|
144
|
+
return typeof value === 'function' ? value(raw) : raw;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
_isDisabled(state) {
|
|
148
|
+
const { disabled } = this.options;
|
|
149
|
+
return typeof disabled === 'function' ? !!disabled(state && state.value) : !!disabled;
|
|
114
150
|
}
|
|
115
151
|
|
|
116
152
|
}
|
|
@@ -11,7 +11,8 @@ function root(layout, state) {
|
|
|
11
11
|
const checked = isChecked(state);
|
|
12
12
|
const roleAttr = role ? ` data-role="${role}"` : '';
|
|
13
13
|
const checkedAttr = checked ? ' data-checked' : '';
|
|
14
|
-
|
|
14
|
+
const disabledAttr = layout._isDisabled(state) ? ' disabled' : '';
|
|
15
|
+
return `<button type="button" class="${className}" role="switch" aria-checked="${checked}"${roleAttr}${checkedAttr}${disabledAttr}>${templates.icon(layout, state)}${templates.text(layout, state)}</button>`;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
function icon(layout, state) {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { LAYOUT_TYPE } from '../../constants.js';
|
|
2
|
+
import ContainerLayout from './container.js';
|
|
3
|
+
|
|
4
|
+
const TYPE = LAYOUT_TYPE.ITEM_CONTAINER;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The container layout of a collection's item subworkflow (a thread of the
|
|
8
|
+
* thread list, a message of the conversation panel): the standard container
|
|
9
|
+
* duties (status attributes, banner, trackings), plus the item-level
|
|
10
|
+
* behaviors — currently the context menu (vertical dots + popup).
|
|
11
|
+
*
|
|
12
|
+
* The menu is handled by contract, not rendered here: the item template
|
|
13
|
+
* supplies the markup (e.g. the threads layout's threadMenuBlock), marking
|
|
14
|
+
* the toggle button `data-role="item-menu-button"` and the popup
|
|
15
|
+
* `data-role="item-menu"`, and this layout drives their open/close behavior
|
|
16
|
+
* — toggle on the button; any other click on the item (a menu action
|
|
17
|
+
* included) closes it, and so does a click outside the item, which is also
|
|
18
|
+
* how opening one item's menu closes another's. The menu contents are
|
|
19
|
+
* typically role elements binding to the item workflow (e.g. the thread
|
|
20
|
+
* item's `<miso-rename>`/`<miso-delete>` buttons), so the actions stay the
|
|
21
|
+
* workflow's own controls. Collection shells treat clicks carrying the
|
|
22
|
+
* menu data-roles as item-internal (e.g. the threads layout does not
|
|
23
|
+
* select); an item template without menu markup simply has nothing to
|
|
24
|
+
* drive. Ownership is guarded: the layout only ever drives its own item's
|
|
25
|
+
* menu — a menu or click inside a nested container element is that
|
|
26
|
+
* container's own layout's — so the layout is inert on the list panel
|
|
27
|
+
* itself, whose options bag it shares with the items.
|
|
28
|
+
*/
|
|
29
|
+
export default class ItemContainerLayout extends ContainerLayout {
|
|
30
|
+
|
|
31
|
+
static get type() {
|
|
32
|
+
return TYPE;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
initialize(view) {
|
|
36
|
+
this._unsubscribes.push(view.proxyElement.on('click', event => this._handleClick(event)));
|
|
37
|
+
if (typeof document !== 'undefined') {
|
|
38
|
+
const onDocumentClick = (event) => {
|
|
39
|
+
const element = this._element;
|
|
40
|
+
if (element && !element.contains(event.target)) {
|
|
41
|
+
this._closeMenu();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
document.addEventListener('click', onDocumentClick);
|
|
45
|
+
this._unsubscribes.push(() => document.removeEventListener('click', onDocumentClick));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// menu //
|
|
50
|
+
_handleClick(event) {
|
|
51
|
+
// only left click
|
|
52
|
+
if (event.button !== 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// a click inside a nested container element belongs to that
|
|
56
|
+
// container's own layout (this layout may also host the list panel,
|
|
57
|
+
// whose subtree holds the items)
|
|
58
|
+
if (!this._owns(event.target)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (event.target.closest(`[data-role="item-menu-button"]`)) {
|
|
62
|
+
this._toggleMenu();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// any other click on the item — a menu action included — closes the menu
|
|
66
|
+
this._closeMenu();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_getMenu() {
|
|
70
|
+
const menu = this._element && this._element.querySelector(`[data-role="item-menu"]`);
|
|
71
|
+
// only its own item's menu: one found inside a nested container
|
|
72
|
+
// element is that container's own layout's to drive
|
|
73
|
+
return menu && this._owns(menu) ? menu : undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_owns(node) {
|
|
77
|
+
for (let el = node; el && el !== this._element; el = el.parentElement) {
|
|
78
|
+
if (el.isContainer) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_toggleMenu() {
|
|
86
|
+
const menu = this._getMenu();
|
|
87
|
+
if (menu) {
|
|
88
|
+
menu.hidden = !menu.hidden;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_closeMenu() {
|
|
93
|
+
const menu = this._getMenu();
|
|
94
|
+
if (menu && !menu.hidden) {
|
|
95
|
+
menu.hidden = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
}
|
package/src/layout/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as TemplateBasedLayout } from './template.js';
|
|
2
2
|
export { default as ContainerLayout } from './container/container.js';
|
|
3
|
+
export { default as ItemContainerLayout } from './container/item-container.js';
|
|
3
4
|
export { default as ErrorLayout } from './error/error.js';
|
|
4
5
|
export { default as BannerLayout } from './banner/banner.js';
|
|
5
6
|
export { default as CollectionLayout } from './list/collection.js';
|
|
@@ -14,6 +15,7 @@ export { default as SearchBoxLayout } from './input/search-box.js';
|
|
|
14
15
|
export { default as OptionListLayout } from './input/option-list.js';
|
|
15
16
|
export { default as FeedbackLayout } from './radio/feedback.js';
|
|
16
17
|
export { default as TextLayout } from './text/text.js';
|
|
18
|
+
export { default as QuestionLayout } from './text/question.js';
|
|
17
19
|
export { default as TypewriterLayout } from './text/typewriter/index.js';
|
|
18
20
|
export { default as AffiliationLayout } from './affiliation/affiliation.js';
|
|
19
21
|
export { default as FacetsLayout } from './input/facets.js';
|
|
@@ -362,8 +362,10 @@ export default class SearchBoxLayout extends TemplateBasedLayout {
|
|
|
362
362
|
return;
|
|
363
363
|
}
|
|
364
364
|
this.close();
|
|
365
|
-
//
|
|
366
|
-
|
|
365
|
+
// a view event, like the buttons: the workflow routes it into its query
|
|
366
|
+
// flow (or its own submission semantics), the layout stays hands-off
|
|
367
|
+
const { session } = this._view._state || {};
|
|
368
|
+
this._view._emit('submit', Object.freeze({ session, value }));
|
|
367
369
|
if (this.options.clearOnSubmit) {
|
|
368
370
|
const inputElement = this._element && this._element.querySelector(`[data-role="input"]`);
|
|
369
371
|
if (inputElement) {
|
|
@@ -2,14 +2,20 @@ import { STATUS } from '../../constants.js';
|
|
|
2
2
|
import TemplateBasedLayout from '../template.js';
|
|
3
3
|
import { makeTrackable } from '../mixin/trackable.js';
|
|
4
4
|
import { makeTriggerable } from '../mixin/triggerable.js';
|
|
5
|
-
import { product, article, compactArticle, image, question, thread, threadMenuBlock, message,
|
|
5
|
+
import { product, article, compactArticle, image, question, thread, threadBody, threadMenuBlock, message, messageBody, productInfoBlock, articleInfoBlock, compactArticleInfoBlock, imageInfoBlock, titleBlock, brandBlock, descriptionBlock, dateBlock, authorsBlock, authorsAndDateBlock, priceBlock, discountRateText, ctaBlock, cta, imageBlock, indexBlock } from '../templates.js';
|
|
6
6
|
|
|
7
7
|
function root(layout, state) {
|
|
8
8
|
const { className, role, templates, options } = layout;
|
|
9
9
|
const { status } = state;
|
|
10
10
|
const roleAttr = role ? ` data-role="${role}"` : '';
|
|
11
11
|
const itemTypeAttr = options.itemType ? ` data-item-type="${options.itemType}"` : '';
|
|
12
|
-
return `<div class="${className} ${status}"${roleAttr}${itemTypeAttr}>${status
|
|
12
|
+
return `<div class="${className} ${status}"${roleAttr}${itemTypeAttr}>${hasStatusTemplate(status) ? templates[status](layout, state) : ''}${templates.trigger(layout, state)}${templates.loading(layout, state)}</div>`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// the statuses with a content template of their own: the list at ready, the
|
|
16
|
+
// error notice at erroneous
|
|
17
|
+
export function hasStatusTemplate(status) {
|
|
18
|
+
return status === STATUS.READY || status === STATUS.ERRONEOUS;
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
function ready(layout, state) {
|
|
@@ -67,13 +73,16 @@ const DEFAULT_TEMPLATES = Object.freeze({
|
|
|
67
73
|
image,
|
|
68
74
|
question,
|
|
69
75
|
thread,
|
|
76
|
+
threadBody,
|
|
70
77
|
threadMenuBlock,
|
|
71
78
|
message,
|
|
72
|
-
|
|
73
|
-
messageAnswerBlock,
|
|
79
|
+
messageBody,
|
|
74
80
|
root,
|
|
75
81
|
[STATUS.READY]: ready,
|
|
76
82
|
empty: () => ``,
|
|
83
|
+
// no notice by default: the ask/search UIs present errors in a section of
|
|
84
|
+
// their own (<miso-error>); list layouts that stand alone supply one
|
|
85
|
+
[STATUS.ERRONEOUS]: () => ``,
|
|
77
86
|
body,
|
|
78
87
|
list,
|
|
79
88
|
ordered: false,
|
|
@@ -181,7 +190,15 @@ export default class CollectionLayout extends TemplateBasedLayout {
|
|
|
181
190
|
}
|
|
182
191
|
|
|
183
192
|
_getItemElements(element) {
|
|
184
|
-
|
|
193
|
+
if (!element) {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
// an item may host a nested collection with items of its own (a
|
|
197
|
+
// conversation message and its source cards): ours are only those whose
|
|
198
|
+
// closest list is this layout's own list
|
|
199
|
+
const listElement = this._getListElement(element);
|
|
200
|
+
return Array.from((listElement || element).querySelectorAll(`[data-role="item"]`))
|
|
201
|
+
.filter(item => item.closest(`[data-role="list"]`) === listElement);
|
|
185
202
|
}
|
|
186
203
|
|
|
187
204
|
_onClick(event) {
|