@miso.ai/client-sdk-ui 1.14.0-beta.2 → 1.14.0-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.
Files changed (44) hide show
  1. package/dist/cjs/asset/svgs.js +4 -2
  2. package/dist/cjs/constants.js +2 -0
  3. package/dist/cjs/defaults.js +100 -10
  4. package/dist/cjs/element/container/index.js +14 -0
  5. package/dist/cjs/element/container/miso-message-item.js +91 -0
  6. package/dist/cjs/element/container/miso-thread-item.js +91 -0
  7. package/dist/cjs/element/role/index.js +7 -0
  8. package/dist/cjs/element/role/miso-delete.js +20 -0
  9. package/dist/cjs/layout/button/button.js +45 -5
  10. package/dist/cjs/layout/button/checkbox.js +2 -1
  11. package/dist/cjs/layout/container/item-container.js +99 -0
  12. package/dist/cjs/layout/index.js +14 -0
  13. package/dist/cjs/layout/input/search-box.js +9 -4
  14. package/dist/cjs/layout/list/collection.js +10 -3
  15. package/dist/cjs/layout/list/messages.js +239 -226
  16. package/dist/cjs/layout/list/threads.js +97 -155
  17. package/dist/cjs/layout/templates.js +65 -38
  18. package/dist/cjs/layout/text/question.js +71 -0
  19. package/dist/cjs/layout/text/typewriter/index.js +14 -21
  20. package/dist/cjs/util/dom.js +21 -0
  21. package/dist/cjs/version.js +1 -1
  22. package/package.json +3 -3
  23. package/src/CLAUDE.md +6 -2
  24. package/src/asset/svgs.js +3 -1
  25. package/src/constants.js +2 -0
  26. package/src/defaults.js +71 -4
  27. package/src/element/container/index.js +2 -0
  28. package/src/element/container/miso-message-item.js +94 -0
  29. package/src/element/container/miso-thread-item.js +94 -0
  30. package/src/element/role/index.js +1 -0
  31. package/src/element/role/miso-delete.js +16 -0
  32. package/src/layout/button/button.js +40 -4
  33. package/src/layout/button/checkbox.js +2 -1
  34. package/src/layout/container/item-container.js +99 -0
  35. package/src/layout/index.js +2 -0
  36. package/src/layout/input/search-box.js +4 -2
  37. package/src/layout/list/collection.js +12 -4
  38. package/src/layout/list/messages.js +202 -204
  39. package/src/layout/list/threads.js +90 -130
  40. package/src/layout/templates.js +74 -39
  41. package/src/layout/text/question.js +58 -0
  42. package/src/layout/text/typewriter/index.js +10 -15
  43. package/src/util/dom.js +16 -0
  44. package/src/version.js +1 -1
@@ -28,7 +28,8 @@ const ICON_NAME = {
28
28
  PLUS: 'plus',
29
29
  PENCIL: 'pencil',
30
30
  BELL: 'bell',
31
- BELL_FILL: 'bell-fill'
31
+ BELL_FILL: 'bell-fill',
32
+ TRASH: 'trash'
32
33
  };
33
34
 
34
35
  // CC0 SVGs
@@ -48,7 +49,8 @@ const ICON_PLUS = `<symbol id="${PREFIX}-${ICON_NAME.PLUS}" viewBox="0 0 16 16">
48
49
  const ICON_PENCIL = `<symbol id="${PREFIX}-${ICON_NAME.PENCIL}" viewBox="0 0 16 16"><path fill="currentColor" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.325-.175"/></symbol>`;
49
50
  const ICON_BELL = `<symbol id="${PREFIX}-${ICON_NAME.BELL}" viewBox="0 0 16 16"><path fill="currentColor" d="M8 16a2 2 0 0 0 2-2H6a2 2 0 0 0 2 2M8 1.918l-.797.161A4 4 0 0 0 4 6c0 .628-.134 2.197-.459 3.742-.16.767-.376 1.566-.663 2.258h10.244c-.287-.692-.502-1.49-.663-2.258C12.134 8.197 12 6.628 12 6a4 4 0 0 0-3.203-3.92zM14.22 12c.223.447.481.801.78 1H1c.299-.199.557-.553.78-1C2.68 10.2 3 6.88 3 6c0-2.42 1.72-4.44 4.005-4.901a1 1 0 1 1 1.99 0A5 5 0 0 1 13 6c0 .88.32 4.2 1.22 6"/></symbol>`;
50
51
  const ICON_BELL_FILL = `<symbol id="${PREFIX}-${ICON_NAME.BELL_FILL}" viewBox="0 0 16 16"><path fill="currentColor" d="M8 16a2 2 0 0 0 2-2H6a2 2 0 0 0 2 2m.995-14.901a1 1 0 1 0-1.99 0A5 5 0 0 0 3 6c0 1.098-.5 6-2 7h14c-1.5-1-2-5.902-2-7 0-2.42-1.72-4.44-4.005-4.901"/></symbol>`;
51
- const ICONS = [ICON_SEND, ICON_SEARCH, ICON_TRIANGLE, ICON_TRIANGLE_EQ, ICON_CHEVRON, ICON_CHEVRON_NEGATIVE, ICON_BULB, ICON_DOTS_VERTICAL, ICON_PLUS, ICON_PENCIL, ICON_BELL, ICON_BELL_FILL];
52
+ const ICON_TRASH = `<symbol id="${PREFIX}-${ICON_NAME.TRASH}" viewBox="0 0 16 16"><path fill="currentColor" d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/><path fill="currentColor" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/></symbol>`;
53
+ const ICONS = [ICON_SEND, ICON_SEARCH, ICON_TRIANGLE, ICON_TRIANGLE_EQ, ICON_CHEVRON, ICON_CHEVRON_NEGATIVE, ICON_BULB, ICON_DOTS_VERTICAL, ICON_PLUS, ICON_PENCIL, ICON_BELL, ICON_BELL_FILL, ICON_TRASH];
52
54
  const SPRITES_SVG = `<svg id="${ID}" style="display:none;">${ICONS.join('')}</svg>`;
53
55
  let loaded = false;
54
56
  function loadSvgSprites() {
@@ -108,6 +108,7 @@ const ATTR_DATA_MISO_UNIT_ID = exports.ATTR_DATA_MISO_UNIT_ID = `data-miso-unit-
108
108
  const ATTR_DATA_MISO_PRODUCT_ID = exports.ATTR_DATA_MISO_PRODUCT_ID = `data-miso-product-id`;
109
109
  const LAYOUT_TYPE = exports.LAYOUT_TYPE = Object.freeze({
110
110
  CONTAINER: 'container',
111
+ ITEM_CONTAINER: 'item-container',
111
112
  ERROR: 'error',
112
113
  BANNER: 'banner',
113
114
  AFFILIATION: 'affiliation',
@@ -127,5 +128,6 @@ const LAYOUT_TYPE = exports.LAYOUT_TYPE = Object.freeze({
127
128
  CHECKBOX: 'checkbox',
128
129
  FEEDBACK: 'feedback',
129
130
  TEXT: 'text',
131
+ QUESTION: 'question',
130
132
  TYPEWRITER: 'typewriter'
131
133
  });
@@ -4,8 +4,12 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.defaultLayouts = void 0;
7
+ var _commons = require("@miso.ai/commons");
7
8
  var _constants = require("./constants.js");
8
9
  var _templates = require("./layout/templates.js");
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; }
11
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
12
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
9
13
  /**
10
14
  * Default layout options of each workflow, keyed by workflow name. The UI
11
15
  * plugin seeds the workflow plugin's defaults store with these values at
@@ -57,6 +61,29 @@ function totalContent(layout, {
57
61
  const label = value === 1 ? 'result' : 'results';
58
62
  return `${formatted} ${label}`;
59
63
  }
64
+ const _ROLE$QUERY = _constants.ROLE.QUERY,
65
+ MESSAGE_LAYOUTS = _objectWithoutPropertiesLoose(ANSWER_BASED_LAYOUTS, [_ROLE$QUERY].map(_toPropertyKey));
66
+
67
+ // the rename/delete button controls of a thread (the conversation header's,
68
+ // and a thread list item's context menu's): mapped to the thread record —
69
+ // the dialogs work with its title, and the buttons disable while the record
70
+ // has no thread id yet (a thread being created is not addressable until
71
+ // resolved; the workflows guard all the same)
72
+ const THREAD_CONTROL_OPTIONS = Object.freeze({
73
+ value: thread => thread && thread.title,
74
+ disabled: thread => !(thread && thread.thread_id)
75
+ });
76
+ const THREAD_RENAME_PROMPT = Object.freeze({
77
+ title: 'Rename thread',
78
+ placeholder: 'Thread name',
79
+ confirmText: 'Rename'
80
+ });
81
+ const THREAD_DELETE_CONFIRM = Object.freeze({
82
+ title: 'Delete thread',
83
+ message: title => `Are you sure you want to delete "${title || 'this thread'}"? This cannot be undone.`,
84
+ confirmText: 'Delete',
85
+ danger: true
86
+ });
60
87
  const SEARCH_BASED_LAYOUTS = Object.assign({}, BASE_LAYOUTS, {
61
88
  [_constants.ROLE.QUERY]: [_constants.LAYOUT_TYPE.SEARCH_BOX],
62
89
  [_constants.ROLE.PRODUCTS]: [_constants.LAYOUT_TYPE.LIST, {
@@ -124,20 +151,83 @@ const defaultLayouts = exports.defaultLayouts = Object.freeze({
124
151
  placeholder: 'Ask a question'
125
152
  }]
126
153
  })),
154
+ // the thread list's own roles plus its thread item subworkflows' — the
155
+ // items share the history workflow's options, so one bag serves both.
156
+ // The item roles: the item-container layout (the standard container
157
+ // duties plus the items' context menus, driven by the item-menu
158
+ // data-role contract — it only ever drives its own item's menu, so
159
+ // hosting it on the panel is inert, and the panel never triggers the
160
+ // auto logo either, having no main-role component), the title text
161
+ // (with a presentation fallback for untitled threads), the context
162
+ // menu's rename (ask-then-submit through the prompt dialog) and delete
163
+ // (confirm-then-submit) buttons, and its subscription toggle — a menu
164
+ // item like the other two, so text only, labeled by the action a click
165
+ // takes rather than by the state
127
166
  'history': Object.freeze(Object.assign({}, BASE_LAYOUTS, {
167
+ [_constants.ROLE.CONTAINER]: [_constants.LAYOUT_TYPE.ITEM_CONTAINER, {
168
+ logo: false
169
+ }],
128
170
  [_constants.ROLE.THREADS]: [_constants.LAYOUT_TYPE.THREADS, {
129
171
  itemType: 'thread',
130
- incremental: true
172
+ incremental: true,
173
+ infiniteScroll: true
131
174
  }],
132
175
  [_constants.ROLE.NEW_THREAD]: [_constants.LAYOUT_TYPE.BUTTON, {
133
176
  icon: 'plus',
134
177
  text: 'New chat'
178
+ }],
179
+ [_constants.ROLE.TITLE]: [_constants.LAYOUT_TYPE.TEXT, {
180
+ tag: 'div',
181
+ templates: {
182
+ content: (layout, {
183
+ value
184
+ }) => (0, _commons.escapeHtml)(value || 'Untitled')
185
+ }
186
+ }],
187
+ [_constants.ROLE.RENAME]: [_constants.LAYOUT_TYPE.BUTTON, Object.assign({}, THREAD_CONTROL_OPTIONS, {
188
+ text: 'Rename',
189
+ prompt: THREAD_RENAME_PROMPT
190
+ })],
191
+ [_constants.ROLE.DELETE]: [_constants.LAYOUT_TYPE.BUTTON, Object.assign({}, THREAD_CONTROL_OPTIONS, {
192
+ text: 'Delete',
193
+ confirm: THREAD_DELETE_CONFIRM
194
+ })],
195
+ [_constants.ROLE.SUBSCRIPTION]: [_constants.LAYOUT_TYPE.CHECKBOX, {
196
+ text: 'Subscribe',
197
+ checkedText: 'Unsubscribe'
135
198
  }]
136
199
  })),
137
- 'conversation': Object.freeze(Object.assign({}, BASE_LAYOUTS, {
200
+ // the conversation panel's own roles plus its message item subworkflows'
201
+ // — the items share the conversation workflow's options, so one bag
202
+ // serves both. The message roles: the answer-based layouts minus the
203
+ // query role (a message has no query flow of its own; the query entry
204
+ // below is the panel's composer), with the question layout (text plus
205
+ // authorship attributes, off the whole record), the answer typewriter
206
+ // with `instant: true` (an already-finished answer renders in one shot),
207
+ // and the sources as the compact horizontal cards of the hybrid-search
208
+ // UI. logo: false turns the per-message banner off; the panel never
209
+ // triggers the auto logo anyway, having no main-role component
210
+ 'conversation': Object.freeze(Object.assign({}, MESSAGE_LAYOUTS, {
211
+ [_constants.ROLE.CONTAINER]: [_constants.LAYOUT_TYPE.CONTAINER, {
212
+ logo: false
213
+ }],
214
+ [_constants.ROLE.QUESTION]: _constants.LAYOUT_TYPE.QUESTION,
215
+ [_constants.ROLE.ANSWER]: [_constants.LAYOUT_TYPE.TYPEWRITER, {
216
+ instant: true
217
+ }],
218
+ [_constants.ROLE.SOURCES]: [_constants.LAYOUT_TYPE.HORIZONTAL, {
219
+ incremental: true,
220
+ itemType: 'article',
221
+ templates: {
222
+ ordered: true,
223
+ article: _templates.compactArticle,
224
+ articleInfoBlock: _templates.compactArticleInfoBlock
225
+ }
226
+ }],
138
227
  [_constants.ROLE.MESSAGES]: [_constants.LAYOUT_TYPE.MESSAGES, {
139
228
  itemType: 'message',
140
- incremental: true
229
+ incremental: true,
230
+ infiniteScroll: true
141
231
  }],
142
232
  [_constants.ROLE.QUERY]: [_constants.LAYOUT_TYPE.SEARCH_BOX, {
143
233
  placeholder: 'Ask a follow-up question',
@@ -151,14 +241,14 @@ const defaultLayouts = exports.defaultLayouts = Object.freeze({
151
241
  [_constants.ROLE.TITLE]: [_constants.LAYOUT_TYPE.TEXT, {
152
242
  tag: 'div'
153
243
  }],
154
- [_constants.ROLE.RENAME]: [_constants.LAYOUT_TYPE.BUTTON, {
244
+ [_constants.ROLE.RENAME]: [_constants.LAYOUT_TYPE.BUTTON, Object.assign({}, THREAD_CONTROL_OPTIONS, {
155
245
  icon: 'pencil',
156
- prompt: {
157
- title: 'Rename thread',
158
- placeholder: 'Thread name',
159
- confirmText: 'Rename'
160
- }
161
- }],
246
+ prompt: THREAD_RENAME_PROMPT
247
+ })],
248
+ [_constants.ROLE.DELETE]: [_constants.LAYOUT_TYPE.BUTTON, Object.assign({}, THREAD_CONTROL_OPTIONS, {
249
+ icon: 'trash',
250
+ confirm: THREAD_DELETE_CONFIRM
251
+ })],
162
252
  [_constants.ROLE.SUBSCRIPTION]: [_constants.LAYOUT_TYPE.CHECKBOX, {
163
253
  icon: 'bell',
164
254
  text: 'Subscribe',
@@ -33,6 +33,12 @@ Object.defineProperty(exports, "MisoHybridSearchElement", {
33
33
  return _misoHybridSearch.default;
34
34
  }
35
35
  });
36
+ Object.defineProperty(exports, "MisoMessageItemElement", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _misoMessageItem.default;
40
+ }
41
+ });
36
42
  Object.defineProperty(exports, "MisoRecommendationElement", {
37
43
  enumerable: true,
38
44
  get: function () {
@@ -45,10 +51,18 @@ Object.defineProperty(exports, "MisoSearchElement", {
45
51
  return _misoSearch.default;
46
52
  }
47
53
  });
54
+ Object.defineProperty(exports, "MisoThreadItemElement", {
55
+ enumerable: true,
56
+ get: function () {
57
+ return _misoThreadItem.default;
58
+ }
59
+ });
48
60
  var _misoAsk = _interopRequireDefault(require("./miso-ask.js"));
49
61
  var _misoExplore = _interopRequireDefault(require("./miso-explore.js"));
50
62
  var _misoHistory = _interopRequireDefault(require("./miso-history.js"));
51
63
  var _misoConversation = _interopRequireDefault(require("./miso-conversation.js"));
64
+ var _misoMessageItem = _interopRequireDefault(require("./miso-message-item.js"));
65
+ var _misoThreadItem = _interopRequireDefault(require("./miso-thread-item.js"));
52
66
  var _misoHybridSearch = _interopRequireDefault(require("./miso-hybrid-search.js"));
53
67
  var _misoSearch = _interopRequireDefault(require("./miso-search.js"));
54
68
  var _misoRecommendation = _interopRequireDefault(require("./miso-recommendation.js"));
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _misoContainer = _interopRequireDefault(require("./miso-container.js"));
8
+ var _utils = require("../utils.js");
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ const TAG_NAME = 'miso-message-item';
11
+ const ATTR_QUESTION_ID = 'question-id';
12
+ const OBSERVED_ATTRIBUTES = Object.freeze([..._misoContainer.default.observedAttributes, ATTR_QUESTION_ID]);
13
+
14
+ /**
15
+ * A container element for one message (question-answer pair) of the
16
+ * conversation panel, placed inside <miso-conversation> — a container
17
+ * element inside another container element hosting an item subworkflow (the
18
+ * `message` workflow). Normally bound implicitly: the messages layout
19
+ * renders one <miso-message-item> per item and assigns the workflow off the item
20
+ * binding (element.workflow = parent workflow's _getMessageWorkflow(record)),
21
+ * which also covers a just-posted message that has no question id yet. A
22
+ * `question-id` attribute binds explicitly instead, through the parent
23
+ * container's workflow. Role elements inside (question, answer, sources,
24
+ * ...) bind to the message workflow as usual — getContainer() resolves to
25
+ * the closest container ancestor.
26
+ */
27
+ class MisoMessageItemElement extends _misoContainer.default {
28
+ static get tagName() {
29
+ return TAG_NAME;
30
+ }
31
+ static get observedAttributes() {
32
+ return OBSERVED_ATTRIBUTES;
33
+ }
34
+
35
+ // properties //
36
+ get questionId() {
37
+ return this.getAttribute(ATTR_QUESTION_ID) || undefined;
38
+ }
39
+ set questionId(value) {
40
+ value = value !== undefined ? `${value}` : undefined;
41
+ if (value === this.questionId) {
42
+ return;
43
+ }
44
+ if (value) {
45
+ this.setAttribute(ATTR_QUESTION_ID, value);
46
+ } else {
47
+ this.removeAttribute(ATTR_QUESTION_ID);
48
+ }
49
+ }
50
+ _getWorkflow(client) {
51
+ const {
52
+ questionId
53
+ } = this;
54
+ if (!questionId) {
55
+ // implicitly bound: the messages layout assigns element.workflow off
56
+ // the item binding — keep whatever is (or will be) assigned
57
+ return this._workflow;
58
+ }
59
+ const container = this._getParentContainer();
60
+ const workflow = container && container.workflow;
61
+ // the parent workflow hands out the item subworkflow; the parent binds
62
+ // its own workflow first (its connectedCallback resolves earlier), so
63
+ // it is normally present by now
64
+ return workflow && typeof workflow._getMessageWorkflow === 'function' ? workflow._getMessageWorkflow(questionId) : undefined;
65
+ }
66
+ _getParentContainer() {
67
+ try {
68
+ return this.parentNode ? (0, _utils.getContainer)(this.parentNode) : undefined;
69
+ } catch (_) {
70
+ return undefined; // not inside a container element
71
+ }
72
+ }
73
+ attributeChangedCallback(attr, oldValue, newValue) {
74
+ switch (attr) {
75
+ case ATTR_QUESTION_ID:
76
+ this._handleQuestionIdUpdate(oldValue, newValue);
77
+ break;
78
+ default:
79
+ super.attributeChangedCallback(attr, oldValue, newValue);
80
+ }
81
+ }
82
+ _handleQuestionIdUpdate(oldValue, newValue) {
83
+ oldValue = oldValue || undefined; // null -> undefined
84
+ newValue = newValue || undefined;
85
+ if (oldValue === newValue || !this._client) {
86
+ return;
87
+ }
88
+ this._setWorkflow(this._getWorkflow(this._client));
89
+ }
90
+ }
91
+ exports.default = MisoMessageItemElement;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _misoContainer = _interopRequireDefault(require("./miso-container.js"));
8
+ var _utils = require("../utils.js");
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ const TAG_NAME = 'miso-thread-item';
11
+ const ATTR_THREAD_ID = 'thread-id';
12
+ const OBSERVED_ATTRIBUTES = Object.freeze([..._misoContainer.default.observedAttributes, ATTR_THREAD_ID]);
13
+
14
+ /**
15
+ * A container element for one thread item of the thread list, placed inside
16
+ * <miso-history> — a container element inside another container element
17
+ * hosting an item subworkflow (the `thread` workflow). Normally bound
18
+ * implicitly: the threads layout renders one <miso-thread-item> per item and
19
+ * assigns the workflow off the item binding (element.workflow = parent
20
+ * workflow's _getThreadWorkflow(record)), which also covers a thread being
21
+ * created that has no thread id yet. A `thread-id` attribute binds
22
+ * explicitly instead, through the parent container's workflow. Role
23
+ * elements inside (title, rename, delete, subscription) bind to the thread
24
+ * workflow as usual — getContainer() resolves to the closest container
25
+ * ancestor.
26
+ */
27
+ class MisoThreadItemElement extends _misoContainer.default {
28
+ static get tagName() {
29
+ return TAG_NAME;
30
+ }
31
+ static get observedAttributes() {
32
+ return OBSERVED_ATTRIBUTES;
33
+ }
34
+
35
+ // properties //
36
+ get threadId() {
37
+ return this.getAttribute(ATTR_THREAD_ID) || undefined;
38
+ }
39
+ set threadId(value) {
40
+ value = value !== undefined ? `${value}` : undefined;
41
+ if (value === this.threadId) {
42
+ return;
43
+ }
44
+ if (value) {
45
+ this.setAttribute(ATTR_THREAD_ID, value);
46
+ } else {
47
+ this.removeAttribute(ATTR_THREAD_ID);
48
+ }
49
+ }
50
+ _getWorkflow(client) {
51
+ const {
52
+ threadId
53
+ } = this;
54
+ if (!threadId) {
55
+ // implicitly bound: the threads layout assigns element.workflow off
56
+ // the item binding — keep whatever is (or will be) assigned
57
+ return this._workflow;
58
+ }
59
+ const container = this._getParentContainer();
60
+ const workflow = container && container.workflow;
61
+ // the parent workflow hands out the item subworkflow; the parent binds
62
+ // its own workflow first (its connectedCallback resolves earlier), so
63
+ // it is normally present by now
64
+ return workflow && typeof workflow._getThreadWorkflow === 'function' ? workflow._getThreadWorkflow(threadId) : undefined;
65
+ }
66
+ _getParentContainer() {
67
+ try {
68
+ return this.parentNode ? (0, _utils.getContainer)(this.parentNode) : undefined;
69
+ } catch (_) {
70
+ return undefined; // not inside a container element
71
+ }
72
+ }
73
+ attributeChangedCallback(attr, oldValue, newValue) {
74
+ switch (attr) {
75
+ case ATTR_THREAD_ID:
76
+ this._handleThreadIdUpdate(oldValue, newValue);
77
+ break;
78
+ default:
79
+ super.attributeChangedCallback(attr, oldValue, newValue);
80
+ }
81
+ }
82
+ _handleThreadIdUpdate(oldValue, newValue) {
83
+ oldValue = oldValue || undefined; // null -> undefined
84
+ newValue = newValue || undefined;
85
+ if (oldValue === newValue || !this._client) {
86
+ return;
87
+ }
88
+ this._setWorkflow(this._getWorkflow(this._client));
89
+ }
90
+ }
91
+ exports.default = MisoThreadItemElement;
@@ -21,6 +21,12 @@ Object.defineProperty(exports, "MisoComponentElement", {
21
21
  return _misoComponent.default;
22
22
  }
23
23
  });
24
+ Object.defineProperty(exports, "MisoDeleteElement", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _misoDelete.default;
28
+ }
29
+ });
24
30
  Object.defineProperty(exports, "MisoErrorElement", {
25
31
  enumerable: true,
26
32
  get: function () {
@@ -189,6 +195,7 @@ var _misoThreads = _interopRequireDefault(require("./miso-threads.js"));
189
195
  var _misoNewThread = _interopRequireDefault(require("./miso-new-thread.js"));
190
196
  var _misoTitle = _interopRequireDefault(require("./miso-title.js"));
191
197
  var _misoRename = _interopRequireDefault(require("./miso-rename.js"));
198
+ var _misoDelete = _interopRequireDefault(require("./miso-delete.js"));
192
199
  var _misoSubscription = _interopRequireDefault(require("./miso-subscription.js"));
193
200
  var _misoMessages = _interopRequireDefault(require("./miso-messages.js"));
194
201
  var _misoError = _interopRequireDefault(require("./miso-error.js"));
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _misoComponent = _interopRequireDefault(require("./miso-component.js"));
8
+ var _constants = require("../../constants.js");
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ class MisoDeleteElement extends _misoComponent.default {
11
+ constructor() {
12
+ super({
13
+ role: _constants.ROLE.DELETE
14
+ });
15
+ }
16
+ static get tagName() {
17
+ return 'miso-delete';
18
+ }
19
+ }
20
+ exports.default = MisoDeleteElement;
@@ -9,7 +9,9 @@ var _constants = require("../../constants.js");
9
9
  var _template = _interopRequireDefault(require("../template.js"));
10
10
  var _svgs = require("../../asset/svgs.js");
11
11
  var _prompt = _interopRequireDefault(require("../../util/prompt.js"));
12
- const _excluded = ["className", "templates"];
12
+ var _confirm = _interopRequireDefault(require("../../util/confirm.js"));
13
+ const _excluded = ["className", "templates"],
14
+ _excluded2 = ["message"];
13
15
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
16
  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; }
15
17
  const TYPE = _constants.LAYOUT_TYPE.BUTTON;
@@ -21,7 +23,8 @@ function root(layout, state) {
21
23
  templates
22
24
  } = layout;
23
25
  const roleAttr = role ? ` data-role="${role}"` : '';
24
- return `<button type="button" class="${className}"${roleAttr}>${templates.icon(layout, state)}${templates.text(layout, state)}</button>`;
26
+ const disabledAttr = layout._isDisabled(state) ? ' disabled' : '';
27
+ return `<button type="button" class="${className}"${roleAttr}${disabledAttr}>${templates.icon(layout, state)}${templates.text(layout, state)}</button>`;
25
28
  }
26
29
  function icon(layout) {
27
30
  const {
@@ -60,6 +63,19 @@ const DEFAULT_TEMPLATES = Object.freeze({
60
63
  * dialog submits nothing. Use it for actions that take a text input, such
61
64
  * as the rename button (role `rename`) of the conversation header, whose
62
65
  * role maps to `'thread.title'`.
66
+ *
67
+ * With a `confirm` option, the click asks through the shared confirm dialog
68
+ * first, and submits only when confirmed; the option's `message` may be a
69
+ * function of the control's current value. Use it for destructive actions,
70
+ * such as the delete button (role `delete`) of a thread item's context menu.
71
+ *
72
+ * The control's current value is the role's mapped data value, put through
73
+ * the `value` option when given — map the role to a record and let `value`
74
+ * pick the text the dialogs work with. A `disabled` option (a boolean, or a
75
+ * function of the mapped value) renders the button's disabled state from
76
+ * the data, re-evaluated on every commit — e.g. rename/delete disable while
77
+ * their thread record has no thread id yet (a thread being created is not
78
+ * addressable), enabling in place when the resolution patches the record.
63
79
  */
64
80
  class ButtonLayout extends _template.default {
65
81
  static get type() {
@@ -94,7 +110,7 @@ class ButtonLayout extends _template.default {
94
110
  if (!button || button.disabled) {
95
111
  return;
96
112
  }
97
- this.options.prompt ? this._submitViaPrompt(event) : this._submit(event);
113
+ this.options.prompt ? this._submitViaPrompt(event) : this.options.confirm ? this._submitViaConfirm(event) : this._submit(event);
98
114
  }
99
115
  _submit(event, payload) {
100
116
  const {
@@ -117,14 +133,38 @@ class ButtonLayout extends _template.default {
117
133
  value
118
134
  });
119
135
  }
136
+ async _submitViaConfirm(event) {
137
+ const _this$options$confirm = this.options.confirm,
138
+ {
139
+ message
140
+ } = _this$options$confirm,
141
+ options = _objectWithoutPropertiesLoose(_this$options$confirm, _excluded2);
142
+ const confirmed = await (0, _confirm.default)(Object.assign({}, options, {
143
+ message: typeof message === 'function' ? message(this._currentValue()) : message
144
+ }));
145
+ if (!confirmed) {
146
+ return;
147
+ }
148
+ this._submit(event);
149
+ }
120
150
 
121
151
  /**
122
152
  * The current value of the control: the role's mapped data value, as of
123
- * the latest render.
153
+ * the latest render, put through the `value` option when given.
124
154
  */
125
155
  _currentValue() {
126
156
  const state = this._element && this._rendered.get(this._element);
127
- return state && state.value;
157
+ const raw = state && state.value;
158
+ const {
159
+ value
160
+ } = this.options;
161
+ return typeof value === 'function' ? value(raw) : raw;
162
+ }
163
+ _isDisabled(state) {
164
+ const {
165
+ disabled
166
+ } = this.options;
167
+ return typeof disabled === 'function' ? !!disabled(state && state.value) : !!disabled;
128
168
  }
129
169
  }
130
170
  exports.default = ButtonLayout;
@@ -22,7 +22,8 @@ function root(layout, state) {
22
22
  const checked = isChecked(state);
23
23
  const roleAttr = role ? ` data-role="${role}"` : '';
24
24
  const checkedAttr = checked ? ' data-checked' : '';
25
- return `<button type="button" class="${className}" role="switch" aria-checked="${checked}"${roleAttr}${checkedAttr}>${templates.icon(layout, state)}${templates.text(layout, state)}</button>`;
25
+ const disabledAttr = layout._isDisabled(state) ? ' disabled' : '';
26
+ return `<button type="button" class="${className}" role="switch" aria-checked="${checked}"${roleAttr}${checkedAttr}${disabledAttr}>${templates.icon(layout, state)}${templates.text(layout, state)}</button>`;
26
27
  }
27
28
  function icon(layout, state) {
28
29
  const {
@@ -0,0 +1,99 @@
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 _container = _interopRequireDefault(require("./container.js"));
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ const TYPE = _constants.LAYOUT_TYPE.ITEM_CONTAINER;
11
+
12
+ /**
13
+ * The container layout of a collection's item subworkflow (a thread of the
14
+ * thread list, a message of the conversation panel): the standard container
15
+ * duties (status attributes, banner, trackings), plus the item-level
16
+ * behaviors — currently the context menu (vertical dots + popup).
17
+ *
18
+ * The menu is handled by contract, not rendered here: the item template
19
+ * supplies the markup (e.g. the threads layout's threadMenuBlock), marking
20
+ * the toggle button `data-role="item-menu-button"` and the popup
21
+ * `data-role="item-menu"`, and this layout drives their open/close behavior
22
+ * — toggle on the button; any other click on the item (a menu action
23
+ * included) closes it, and so does a click outside the item, which is also
24
+ * how opening one item's menu closes another's. The menu contents are
25
+ * typically role elements binding to the item workflow (e.g. the thread
26
+ * item's `<miso-rename>`/`<miso-delete>` buttons), so the actions stay the
27
+ * workflow's own controls. Collection shells treat clicks carrying the
28
+ * menu data-roles as item-internal (e.g. the threads layout does not
29
+ * select); an item template without menu markup simply has nothing to
30
+ * drive. Ownership is guarded: the layout only ever drives its own item's
31
+ * menu — a menu or click inside a nested container element is that
32
+ * container's own layout's — so the layout is inert on the list panel
33
+ * itself, whose options bag it shares with the items.
34
+ */
35
+ class ItemContainerLayout extends _container.default {
36
+ static get type() {
37
+ return TYPE;
38
+ }
39
+ initialize(view) {
40
+ this._unsubscribes.push(view.proxyElement.on('click', event => this._handleClick(event)));
41
+ if (typeof document !== 'undefined') {
42
+ const onDocumentClick = event => {
43
+ const element = this._element;
44
+ if (element && !element.contains(event.target)) {
45
+ this._closeMenu();
46
+ }
47
+ };
48
+ document.addEventListener('click', onDocumentClick);
49
+ this._unsubscribes.push(() => document.removeEventListener('click', onDocumentClick));
50
+ }
51
+ }
52
+
53
+ // menu //
54
+ _handleClick(event) {
55
+ // only left click
56
+ if (event.button !== 0) {
57
+ return;
58
+ }
59
+ // a click inside a nested container element belongs to that
60
+ // container's own layout (this layout may also host the list panel,
61
+ // whose subtree holds the items)
62
+ if (!this._owns(event.target)) {
63
+ return;
64
+ }
65
+ if (event.target.closest(`[data-role="item-menu-button"]`)) {
66
+ this._toggleMenu();
67
+ return;
68
+ }
69
+ // any other click on the item — a menu action included — closes the menu
70
+ this._closeMenu();
71
+ }
72
+ _getMenu() {
73
+ const menu = this._element && this._element.querySelector(`[data-role="item-menu"]`);
74
+ // only its own item's menu: one found inside a nested container
75
+ // element is that container's own layout's to drive
76
+ return menu && this._owns(menu) ? menu : undefined;
77
+ }
78
+ _owns(node) {
79
+ for (let el = node; el && el !== this._element; el = el.parentElement) {
80
+ if (el.isContainer) {
81
+ return false;
82
+ }
83
+ }
84
+ return true;
85
+ }
86
+ _toggleMenu() {
87
+ const menu = this._getMenu();
88
+ if (menu) {
89
+ menu.hidden = !menu.hidden;
90
+ }
91
+ }
92
+ _closeMenu() {
93
+ const menu = this._getMenu();
94
+ if (menu && !menu.hidden) {
95
+ menu.hidden = true;
96
+ }
97
+ }
98
+ }
99
+ exports.default = ItemContainerLayout;