@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.
Files changed (54) hide show
  1. package/dist/cjs/asset/svgs.js +4 -2
  2. package/dist/cjs/constants.js +2 -0
  3. package/dist/cjs/defaults/ask/index.js +2 -1
  4. package/dist/cjs/defaults/ask/templates.js +2 -1
  5. package/dist/cjs/defaults/hybrid-search/index.js +2 -1
  6. package/dist/cjs/defaults/hybrid-search/templates.js +2 -1
  7. package/dist/cjs/defaults/index.js +2 -1
  8. package/dist/cjs/defaults.js +107 -10
  9. package/dist/cjs/element/container/index.js +14 -0
  10. package/dist/cjs/element/container/miso-message-item.js +91 -0
  11. package/dist/cjs/element/container/miso-thread-item.js +91 -0
  12. package/dist/cjs/element/index.js +2 -1
  13. package/dist/cjs/element/role/index.js +7 -0
  14. package/dist/cjs/element/role/miso-delete.js +20 -0
  15. package/dist/cjs/layout/button/button.js +45 -5
  16. package/dist/cjs/layout/button/checkbox.js +2 -1
  17. package/dist/cjs/layout/container/item-container.js +99 -0
  18. package/dist/cjs/layout/index.js +14 -0
  19. package/dist/cjs/layout/input/search-box.js +9 -4
  20. package/dist/cjs/layout/list/collection.js +21 -4
  21. package/dist/cjs/layout/list/messages.js +261 -224
  22. package/dist/cjs/layout/list/threads.js +106 -150
  23. package/dist/cjs/layout/templates.js +98 -38
  24. package/dist/cjs/layout/text/question.js +71 -0
  25. package/dist/cjs/layout/text/typewriter/index.js +15 -21
  26. package/dist/cjs/plugin.js +6 -3
  27. package/dist/cjs/util/dom.js +21 -0
  28. package/dist/cjs/util/notify.js +74 -0
  29. package/dist/cjs/version.js +1 -1
  30. package/package.json +3 -3
  31. package/src/CLAUDE.md +7 -2
  32. package/src/asset/svgs.js +3 -1
  33. package/src/constants.js +2 -0
  34. package/src/defaults.js +75 -5
  35. package/src/element/container/index.js +2 -0
  36. package/src/element/container/miso-message-item.js +94 -0
  37. package/src/element/container/miso-thread-item.js +94 -0
  38. package/src/element/role/index.js +1 -0
  39. package/src/element/role/miso-delete.js +16 -0
  40. package/src/layout/button/button.js +40 -4
  41. package/src/layout/button/checkbox.js +2 -1
  42. package/src/layout/container/item-container.js +99 -0
  43. package/src/layout/index.js +2 -0
  44. package/src/layout/input/search-box.js +4 -2
  45. package/src/layout/list/collection.js +22 -5
  46. package/src/layout/list/messages.js +231 -201
  47. package/src/layout/list/threads.js +107 -128
  48. package/src/layout/templates.js +100 -38
  49. package/src/layout/text/question.js +58 -0
  50. package/src/layout/text/typewriter/index.js +11 -15
  51. package/src/plugin.js +3 -2
  52. package/src/util/dom.js +16 -0
  53. package/src/util/notify.js +68 -0
  54. 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
- return `<button type="button" class="${className}"${roleAttr}>${templates.icon(layout, state)}${templates.text(layout, state)}</button>`;
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) : this._submit(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
- return state && state.value;
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
- return `<button type="button" class="${className}" role="switch" aria-checked="${checked}"${roleAttr}${checkedAttr}>${templates.icon(layout, state)}${templates.text(layout, state)}</button>`;
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
+ }
@@ -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
- // TODO: q -> value
366
- this._view.hub.update(fields.query(), { q: value });
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, messageQuestionBlock, messageAnswerBlock, productInfoBlock, articleInfoBlock, compactArticleInfoBlock, imageInfoBlock, titleBlock, brandBlock, descriptionBlock, dateBlock, authorsBlock, authorsAndDateBlock, priceBlock, discountRateText, ctaBlock, cta, imageBlock, indexBlock } from '../templates.js';
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 === STATUS.READY ? templates[status](layout, state) : ''}${templates.trigger(layout, state)}${templates.loading(layout, state)}</div>`;
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
- messageQuestionBlock,
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
- return element ? Array.from(element.querySelectorAll(`[data-role="item"]`)) : [];
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) {