@kubex/zinc 1.0.4 → 1.0.5

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 (153) hide show
  1. package/dist/custom-elements.json +1034 -111
  2. package/dist/vscode.html-custom-data.json +85 -15
  3. package/dist/web-types.json +215 -24
  4. package/dist/zn.d.ts +1234 -888
  5. package/dist/zn.min.css +1 -1
  6. package/dist/zn.min.js +1979 -940
  7. package/docs/_includes/default.njk +1 -0
  8. package/docs/assets/scripts/docs.js +4 -5
  9. package/docs/assets/scripts/search.js +0 -9
  10. package/docs/assets/styles/docs.css +3 -3
  11. package/docs/data/data-table.json +79 -0
  12. package/docs/data/products-table.json +36 -0
  13. package/docs/eleventy.config.cjs +3 -0
  14. package/docs/pages/components/data-select.md +10 -0
  15. package/docs/pages/components/data-table.md +84 -45
  16. package/docs/pages/components/dialog.md +1 -1
  17. package/docs/pages/components/editor.md +1 -3
  18. package/docs/pages/components/filter-wrapper.md +20 -0
  19. package/docs/pages/components/icon.md +2 -0
  20. package/docs/pages/components/item.md +2 -2
  21. package/docs/pages/components/menu-item.md +12 -3
  22. package/docs/pages/components/panel.md +17 -0
  23. package/docs/pages/components/progress-tile.md +17 -1
  24. package/docs/pages/components/settings-container.md +137 -0
  25. package/docs/pages/components/textarea.md +1 -1
  26. package/package.json +3 -1
  27. package/scss/_root.scss +26 -0
  28. package/scss/boot.scss +27 -1
  29. package/scss/mixins/index.scss +30 -1
  30. package/scss/shared/_base.scss +1 -1
  31. package/scss/shared/_button.scss +1 -1
  32. package/scss/shared/layout.scss +7 -0
  33. package/scss/themes/_dark.scss +5 -5
  34. package/scss/themes/_light.scss +6 -5
  35. package/scss/variables/index.scss +9 -0
  36. package/src/components/alert/alert.component.ts +1 -1
  37. package/src/components/alert/alert.scss +18 -4
  38. package/src/components/button/button.component.ts +6 -2
  39. package/src/components/button/button.scss +16 -1
  40. package/src/components/button-menu/button-menu.component.ts +23 -15
  41. package/src/components/button-menu/button-menu.scss +4 -4
  42. package/src/components/checkbox/checkbox.component.ts +5 -1
  43. package/src/components/checkbox/checkbox.scss +1 -1
  44. package/src/components/chip/chip.component.ts +7 -3
  45. package/src/components/chip/chip.scss +9 -1
  46. package/src/components/collapsible/collapsible.component.ts +48 -2
  47. package/src/components/confirm/confirm.component.ts +1 -2
  48. package/src/components/content-block/content-block.component.ts +25 -1
  49. package/src/components/content-block/content-block.scss +8 -0
  50. package/src/components/data-select/data-select.component.ts +1 -1
  51. package/src/components/data-table/data-table.component.ts +361 -422
  52. package/src/components/data-table/data-table.scss +6 -1
  53. package/src/components/datepicker/datepicker.scss +1 -1
  54. package/src/components/dialog/dialog.component.ts +52 -33
  55. package/src/components/dialog/dialog.scss +15 -1
  56. package/src/components/dropdown/dropdown.component.ts +1 -1
  57. package/src/components/editor/editor.component.ts +196 -158
  58. package/src/components/editor/editor.scss +25 -5
  59. package/src/components/editor/modules/ai/index.ts +281 -0
  60. package/src/components/editor/modules/ai/panel/ai-panel.component.ts +102 -0
  61. package/src/components/editor/modules/ai/panel/ai-panel.scss +83 -0
  62. package/src/components/editor/modules/ai/tooltip/ai-tooltip.component.ts +30 -0
  63. package/src/components/editor/modules/ai/tooltip/ai-tooltip.scss +28 -0
  64. package/src/components/editor/modules/{attachment-module.ts → attachment/attachment.ts} +7 -7
  65. package/src/components/editor/modules/context-menu/context-menu-component.ts +115 -0
  66. package/src/components/editor/modules/context-menu/context-menu.scss +49 -0
  67. package/src/components/editor/modules/context-menu/context-menu.ts +296 -0
  68. package/src/components/editor/modules/context-menu/quick-action/index.ts +12 -0
  69. package/src/components/editor/modules/context-menu/quick-action/quick-action.component.ts +18 -0
  70. package/src/components/editor/modules/date-picker/date-picker.ts +77 -0
  71. package/src/components/editor/modules/dialog/dialog.component.ts +115 -0
  72. package/src/components/editor/modules/dialog/dialog.scss +43 -0
  73. package/src/components/editor/modules/dialog/dialog.ts +46 -0
  74. package/src/components/editor/modules/{drag-drop-module.ts → drag-drop/drag-drop.ts} +6 -6
  75. package/src/components/editor/modules/emoji/emoji.ts +106 -0
  76. package/src/components/editor/modules/emoji/headless/headless-emoji.component.ts +122 -0
  77. package/src/components/editor/modules/emoji/headless/headless-emoji.scss +52 -0
  78. package/src/components/editor/modules/emoji/headless/headless-emoji.ts +223 -0
  79. package/src/components/editor/modules/{image-resize-module/image-resize-module.ts → image-resize/image-resize.ts} +5 -5
  80. package/src/components/editor/modules/{time-tracking-module.ts → time-tracking/time-tracking.ts} +5 -5
  81. package/src/components/editor/modules/toolbar/tool/index.ts +12 -0
  82. package/src/components/editor/modules/toolbar/tool/tool.component.ts +30 -0
  83. package/src/components/editor/modules/toolbar/tool/tool.scss +5 -0
  84. package/src/components/editor/modules/toolbar/toolbar.component.ts +738 -0
  85. package/src/components/editor/modules/toolbar/toolbar.scss +98 -0
  86. package/src/components/editor/modules/toolbar/toolbar.ts +328 -0
  87. package/src/components/expanding-action/expanding-action.component.ts +2 -2
  88. package/src/components/expanding-action/expanding-action.scss +2 -2
  89. package/src/components/filter-wrapper/filter-wrapper.component.ts +91 -0
  90. package/src/components/filter-wrapper/filter-wrapper.scss +11 -0
  91. package/src/components/filter-wrapper/filter-wrapper.test.ts +10 -0
  92. package/src/components/filter-wrapper/index.ts +12 -0
  93. package/src/components/header/header.component.ts +1 -1
  94. package/src/components/header/header.scss +8 -1
  95. package/src/components/hover-container/hover-container.scss +39 -36
  96. package/src/components/icon/icon.component.ts +11 -5
  97. package/src/components/icon/icon.scss +15 -2
  98. package/src/components/inline-edit/inline-edit.component.ts +28 -3
  99. package/src/components/inline-edit/inline-edit.scss +4 -1
  100. package/src/components/input/input.scss +1 -1
  101. package/src/components/item/item.component.ts +30 -19
  102. package/src/components/item/item.scss +56 -14
  103. package/src/components/linked-select/linked-select.component.ts +5 -1
  104. package/src/components/menu/menu.component.ts +1 -0
  105. package/src/components/menu/menu.scss +1 -1
  106. package/src/components/menu-item/menu-item.component.ts +22 -4
  107. package/src/components/menu-item/menu-item.scss +54 -7
  108. package/src/components/navbar/navbar.component.ts +7 -1
  109. package/src/components/navbar/navbar.scss +26 -16
  110. package/src/components/option/option.component.ts +4 -1
  111. package/src/components/option/option.scss +9 -0
  112. package/src/components/page-nav/page-nav.component.ts +2 -2
  113. package/src/components/page-nav/page-nav.scss +4 -4
  114. package/src/components/panel/panel.component.ts +20 -9
  115. package/src/components/panel/panel.scss +5 -1
  116. package/src/components/popup/popup.component.ts +16 -2
  117. package/src/components/progress-tile/progress-tile.component.ts +10 -4
  118. package/src/components/progress-tile/progress-tile.scss +7 -4
  119. package/src/components/query-builder/query-builder.component.ts +1 -1
  120. package/src/components/radio/radio.scss +1 -1
  121. package/src/components/scroll-container/scroll-container.component.ts +56 -9
  122. package/src/components/scroll-container/scroll-container.scss +37 -2
  123. package/src/components/select/select.component.ts +3 -0
  124. package/src/components/select/select.scss +1 -1
  125. package/src/components/settings-container/index.ts +12 -0
  126. package/src/components/settings-container/settings-container.component.ts +226 -0
  127. package/src/components/settings-container/settings-container.scss +92 -0
  128. package/src/components/settings-container/settings-container.test.ts +10 -0
  129. package/src/components/skeleton/skeleton.scss +5 -4
  130. package/src/components/sp/sp.component.ts +2 -0
  131. package/src/components/sp/sp.scss +2 -0
  132. package/src/components/split-button/split-button.component.ts +24 -18
  133. package/src/components/split-pane/split-pane.component.ts +5 -5
  134. package/src/components/stat/stat.component.ts +5 -1
  135. package/src/components/stat/stat.scss +24 -1
  136. package/src/components/style/style.component.ts +18 -0
  137. package/src/components/tabs/tabs.component.ts +12 -1
  138. package/src/components/textarea/textarea.component.ts +3 -1
  139. package/src/components/textarea/textarea.scss +21 -2
  140. package/src/components/vertical-stepper/vertical-stepper.scss +1 -1
  141. package/src/internal/form.ts +3 -1
  142. package/src/internal/slot.ts +29 -3
  143. package/src/utilities/on.ts +7 -8
  144. package/src/utilities/query.ts +5 -1
  145. package/src/wc.scss +7 -0
  146. package/src/zinc.ts +5 -0
  147. package/src/components/editor/modules/dialog-module/dialog-module.component.ts +0 -289
  148. package/src/components/editor/modules/dialog-module/dialog-module.scss +0 -115
  149. package/src/components/editor/modules/dialog-module/dialog-module.ts +0 -99
  150. package/src/components/editor/modules/events/zn-show-canned-response-dialog.ts +0 -15
  151. package/src/components/editor/modules/menu-module/menu-module.component.ts +0 -149
  152. package/src/components/editor/modules/menu-module/menu-module.scss +0 -17
  153. package/src/components/editor/modules/menu-module/menu-module.ts +0 -203
@@ -1,29 +1,30 @@
1
1
  import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
2
+ import {deepQuerySelectorAll} from "../../utilities/query";
2
3
  import {FormControlController} from '../../internal/form';
4
+ import {on} from "../../utilities/on";
3
5
  import {property, query} from 'lit/decorators.js';
4
- import AttachmentModule from "./modules/attachment-module";
5
- import DialogModule from "./modules/dialog-module/dialog-module";
6
- import DragAndDropModule from "./modules/drag-drop-module";
7
- import ImageResizeModule from "./modules/image-resize-module/image-resize-module";
8
- import MenuModule from "./modules/menu-module/menu-module";
9
- import Quill, {Range} from "quill";
10
- import TimeTrackingModule from "./modules/time-tracking-module";
6
+ import {trim} from "lodash";
7
+ import Attachment from "./modules/attachment/attachment";
8
+ import ContextMenu from "./modules/context-menu/context-menu";
9
+ import DatePicker from "./modules/date-picker/date-picker";
10
+ import Dialog from "./modules/dialog/dialog";
11
+ import DragAndDrop from "./modules/drag-drop/drag-drop";
12
+ import Emoji from "./modules/emoji/emoji";
13
+ import HeadlessEmoji from "./modules/emoji/headless/headless-emoji";
14
+ import ImageResize from "./modules/image-resize/image-resize";
15
+ import Quill from "quill";
16
+ import QuillAI from "./modules/ai";
17
+ import TimeTracking from "./modules/time-tracking/time-tracking";
18
+ import Toolbar from "./modules/toolbar/toolbar";
11
19
  import ZincElement from '../../internal/zinc-element';
20
+ import ZnTextarea from "../textarea";
21
+ import type {OnEvent} from "../../utilities/on";
22
+ import type {Range} from "quill";
12
23
  import type {ZincFormControl} from '../../internal/zinc-element';
13
- import type {ZnShowCannedResponseDialogEvent} from "./modules/events/zn-show-canned-response-dialog";
14
- import type DialogModuleComponent from "./modules/dialog-module/dialog-module.component";
15
- import type MenuModuleComponent from "./modules/menu-module/menu-module.component";
24
+ import type ToolbarComponent from "./modules/toolbar/toolbar.component";
16
25
 
17
26
  import styles from './editor.scss';
18
27
 
19
- export interface CannedResponse {
20
- title: string;
21
- content: string;
22
- command: string;
23
- labels?: string[];
24
- count: string;
25
- }
26
-
27
28
  /**
28
29
  * @summary Short summary of the component's intended use.
29
30
  * @documentation https://zinc.style/components/editor
@@ -52,21 +53,24 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
52
53
  @query('#editorHtml')
53
54
  private editorHtml: HTMLTextAreaElement;
54
55
 
56
+ @query('#toolbar')
57
+ private toolbar: ToolbarComponent;
58
+
59
+ @property({reflect: true}) id: string;
55
60
  @property() name: string;
56
61
  @property() value: string;
57
62
 
58
63
  @property({attribute: 'interaction-type', type: String})
59
64
  interactionType: 'ticket' | 'chat' = 'chat';
60
65
 
61
- @property({attribute: 'canned-responses', type: Array})
62
- cannedResponses: any[];
63
-
64
- @property({attribute: 'canned-responses-url'}) cannedResponsesUri: string;
65
-
66
66
  @property({attribute: 'attachment-url', type: String})
67
67
  uploadAttachmentUrl: string;
68
68
 
69
+ @property({attribute: 'ai-path'}) aiPath: string = '';
70
+
69
71
  private quillElement: Quill;
72
+ private _content: string = '';
73
+ private _selectionRange: Range = {index: 0, length: 0};
70
74
 
71
75
  get validity(): ValidityState {
72
76
  return this.editorHtml.validity;
@@ -98,78 +102,79 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
98
102
 
99
103
  const bindings = this._getQuillKeyboardBindings();
100
104
 
101
- Quill.register('modules/dialogModule', DialogModule as any);
102
- Quill.register('modules/menuModule', MenuModule as any);
103
- Quill.register('modules/attachmentModule', AttachmentModule as any);
104
- Quill.register('modules/timeTrackingModule', TimeTrackingModule as any);
105
- Quill.register('modules/dragAndDropModule', DragAndDropModule as any);
106
- Quill.register('modules/imageResizeModule', ImageResizeModule as any);
105
+ let checkId = this.id
106
+ if (!checkId) {
107
+ checkId = "abc"
108
+ }
109
+ on(document, 'click', `[editor-id="` + checkId + `"]`, (e: OnEvent) => {
110
+ this._handleEditorChange(e);
111
+ });
112
+
113
+ Quill.register({'modules/toolbar': Toolbar}, true);
114
+ Quill.register({'modules/datePicker': DatePicker}, true);
115
+ Quill.register({'modules/emoji': Emoji}, true);
116
+ Quill.register({'modules/headlessEmoji': HeadlessEmoji}, true);
117
+ Quill.register({'modules/attachment': Attachment}, true);
118
+ Quill.register({'modules/timeTracking': TimeTracking}, true);
119
+ Quill.register({'modules/dragAndDrop': DragAndDrop}, true);
120
+ Quill.register({'modules/imageResize': ImageResize}, true);
121
+ Quill.register({'modules/contextMenu': ContextMenu}, true);
122
+ Quill.register({'modules/dialog': Dialog}, true);
123
+
124
+ /* AI Modules */
125
+ Quill.register('modules/ai', QuillAI as any, true);
126
+
127
+ // Register a custom HR blot so we can insert an inline horizontal rule block
128
+ const BlockEmbed = Quill.import('blots/block/embed') as { new(...args: any[]): any };
129
+
130
+ class HrBlot extends BlockEmbed {
131
+ static blotName = 'hr';
132
+ static tagName = 'HR';
133
+ static className = 'ql-hr';
134
+
135
+ static create(): HTMLElement {
136
+ const node = document.createElement('hr');
137
+ node.classList.add('ql-hr');
138
+ return node;
139
+ }
140
+ }
141
+
142
+ Quill.register({'formats/hr': HrBlot}, true);
107
143
 
108
- this._updateIcons();
109
144
  const attachmentInput = this.getForm()?.querySelector('input[name="attachments"]');
110
145
  const startTimeInput = this.getForm()?.querySelector('input[name="startTime"]');
111
146
  const openTimeInput = this.getForm()?.querySelector('input[name="openTime"]');
112
147
 
113
- const container = [
114
- ['bold', 'italic', 'underline', 'strike'],
115
- ['undo', 'redo'],
116
- [{'list': 'ordered'}, {'list': 'bullet'}],
117
- ];
118
- container.push(this.interactionType === 'ticket' ? ['link', 'image', 'attachment'] : ['link', 'image', 'video']);
119
- container.push(['remove-formatting']);
120
-
121
148
  const quill = new Quill(this.editor, {
122
149
  modules: {
123
150
  toolbar: {
124
- container,
125
- handlers: {
126
- 'attachment': () => null,
127
- 'placeholder': function (value: string) {
128
- if (value) {
129
- const editor: Quill | null = this.quill;
130
- if (editor) {
131
- const cursorPosition = editor.getSelection()?.index;
132
- if (cursorPosition) {
133
- editor.insertText(cursorPosition, value);
134
- editor.setSelection(new Range(cursorPosition, value.length));
135
- }
136
- }
137
- }
138
- },
139
- 'redo': () => this.quillElement.history.redo(),
140
- 'undo': () => this.quillElement.history.undo(),
141
- 'remove-formatting': () => {
142
- const range = this.quillElement.getSelection();
143
- if (range) {
144
- this.quillElement.formatText(range.index, range.length, 'bold', false);
145
- this.quillElement.formatText(range.index, range.length, 'italic', false);
146
- this.quillElement.formatText(range.index, range.length, 'underline', false);
147
- this.quillElement.formatText(range.index, range.length, 'strike', false);
148
- }
149
-
150
- }
151
- }
151
+ container: this.toolbar,
152
152
  },
153
+ contextMenu: {},
153
154
  keyboard: {
154
155
  bindings: bindings
155
156
  },
156
- dialogModule: {},
157
- menuModule: {
158
- cannedResponses: this.cannedResponses,
159
- cannedResponsesUri: this.cannedResponsesUri
157
+ dialog: {
158
+ editorId: this.id
160
159
  },
161
- timeTrackingModule: {
160
+ emoji: {},
161
+ headlessEmoji: {},
162
+ datePicker: {},
163
+ ai: {
164
+ path: this.aiPath
165
+ },
166
+ timeTracking: {
162
167
  startTimeInput: startTimeInput as HTMLInputElement,
163
168
  openTimeInput: openTimeInput as HTMLInputElement
164
169
  },
165
- attachmentModule: {
170
+ attachment: {
166
171
  attachmentInput: attachmentInput,
167
172
  onFileUploaded: () => {
168
173
  window.onbeforeunload = () => null;
169
174
  },
170
175
  upload: (file: File) => {
171
176
  window.onbeforeunload = () => 'You have unsaved changes. Are you sure you want to leave?';
172
- return new Promise((resolve, _) => {
177
+ return new Promise((resolve) => {
173
178
  const fd = new FormData();
174
179
  fd.append('filename', file.name);
175
180
  fd.append('size', file.size.toString());
@@ -179,7 +184,11 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
179
184
  xhr.open('POST', this.uploadAttachmentUrl, true);
180
185
  xhr.onload = () => {
181
186
  if (xhr.status === 200) {
182
- const response = JSON.parse(xhr.responseText);
187
+ const response = JSON.parse(xhr.responseText) as {
188
+ uploadPath: string;
189
+ uploadUrl: string;
190
+ originalFilename: string;
191
+ };
183
192
  resolve({path: response.uploadPath, url: response.uploadUrl, filename: response.originalFilename});
184
193
  }
185
194
  };
@@ -187,7 +196,8 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
187
196
  });
188
197
  },
189
198
  },
190
- imageResizeModule: {}
199
+ imageResize: {},
200
+ history: {}
191
201
  },
192
202
  placeholder: 'Compose your reply...',
193
203
  theme: 'snow',
@@ -196,8 +206,6 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
196
206
 
197
207
  this.quillElement = quill;
198
208
 
199
- this._supplyPlaceholderDialog();
200
-
201
209
  // @ts-expect-error getSelection is available it lies.
202
210
  const hasShadowRootSelection = !!(document.createElement('div').attachShadow({mode: 'open'}).getSelection);
203
211
  // Each browser engine has a different implementation for retrieving the Range
@@ -209,16 +217,12 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
209
217
  return rootNode?.getSelection()?.getRangeAt(0);
210
218
  } else {
211
219
  const selection = window.getSelection();
212
- // @ts-expect-error getComposedRanges is available it lies.
213
- if (selection.getComposedRanges) {
220
+ if (selection && "getComposedRanges" in selection && typeof selection.getComposedRanges === 'function') {
214
221
  // Webkit range retrieval is done with getComposedRanges (see: https://bugs.webkit.org/show_bug.cgi?id=163921)
215
- // @ts-ignore
216
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
217
- return selection?.getComposedRanges(rootNode)[0];
222
+ return Array.isArray(selection.getComposedRanges(rootNode)) ? (selection.getComposedRanges(rootNode) as unknown[])[0] : undefined;
218
223
  } else {
219
224
  // Gecko implements the range API properly in Native Shadow: https://developer.mozilla.org/en-US/docs/Web/API/Selection/getRangeAt
220
- // @ts-ignore
221
- return selection.getRangeAt(0);
225
+ return selection?.getRangeAt(0);
222
226
  }
223
227
  }
224
228
  } catch {
@@ -266,7 +270,7 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
266
270
  if (selection === null) return;
267
271
  if (startNode !== null) {
268
272
  if (!quill.selection.hasFocus()) quill.selection.root.focus();
269
- const native = (quill.selection.getNativeRange() || {}).native;
273
+ const native = quill.selection.getNativeRange()?.native;
270
274
  // @ts-ignore
271
275
  if (native === null || force || startNode !== native?.startContainer || startOffset !== native.startOffset || endNode !== native.endContainer || endOffset !== native.endOffset) {
272
276
  if (startNode.tagName === "BR") {
@@ -292,25 +296,20 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
292
296
  }
293
297
  }
294
298
 
295
- // Listen for 'View All' click from menu module
296
- document.addEventListener('zn-show-canned-response-dialog', (e: ZnShowCannedResponseDialogEvent) => {
297
- const dialog: DialogModuleComponent | null = document.querySelector('zn-dialog-module');
298
- const dialogModule = this.quillElement.getModule('dialogModule') as DialogModule;
299
- if (dialog && dialogModule && e.detail.commands) {
300
- dialogModule.setCommands(e.detail.commands);
301
- dialog.allCommands = e.detail.commands; // Need a ref of original list when searching
302
- }
303
- });
304
-
305
299
  /**
306
300
  * Subscribe to selection change separately, because emitter in Quill doesn't catch this event in Shadow DOM
307
301
  **/
308
- document.addEventListener('selectionchange', () => this.quillElement.selection.update());
302
+ document.addEventListener('selectionchange', () => {
303
+ this.quillElement.selection.update();
304
+ });
309
305
 
310
306
  document.addEventListener('zn-editor-update', this._handleTextChange.bind(this));
311
307
  quill.on('text-change', this._handleTextChange.bind(this));
312
308
 
313
- this._setupTitleAttributes(quill);
309
+ // Track selection range inside the editor so other modules can access it
310
+ quill.on(Quill.events.SELECTION_CHANGE, (range: Range, oldRange: Range) => {
311
+ this._selectionRange = range ?? oldRange;
312
+ });
314
313
 
315
314
  const delta = quill.clipboard.convert({html: this.value});
316
315
  quill.setContents(delta, Quill.sources.SILENT);
@@ -325,95 +324,134 @@ export default class ZnEditor extends ZincElement implements ZincFormControl {
325
324
  this.emit('zn-change');
326
325
  }
327
326
 
328
- private _updateIcons() {
329
- // @ts-expect-error icons has no type
330
- const icons: { [key: string]: string } = Quill.import("ui/icons");
331
- if (icons) {
332
- icons["undo"] = `<zn-icon src="undo" size="20"></zn-icon>`;
333
- icons["redo"] = `<zn-icon src="redo" size="20"></zn-icon>`;
334
- icons["remove-formatting"] = `<zn-icon src="format_clear" size="20"></zn-icon>`;
335
-
336
- if (this.interactionType === 'ticket') {
337
- icons["attachment"] = `<zn-icon src="attachment" size="20"></zn-icon>`;
338
- }
339
- }
340
- }
341
-
342
327
  private _getQuillKeyboardBindings() {
343
- const bindings = {
344
- 'remove-formatting': {
345
- key: 'V',
346
- shiftKey: true,
347
- handler: (_range: any, context: any) => {
348
- const clipboard = context.event.clipboardData;
349
- const text = clipboard.getData('text/plain');
350
- const html = clipboard.getData('text/html');
351
- const delta = this.quillElement.clipboard.convert({html: html, text: text});
352
- this.quillElement.setContents(delta, 'silent');
353
- this.quillElement.setSelection(delta.length(), Quill.sources.SILENT);
354
- }
355
- },
356
- };
357
-
358
328
  const empty = (value: string) => {
359
329
  const match = value.match(/[^<pbr\s>/]/);
360
330
  return match === null;
361
331
  };
362
332
 
363
- if (this.interactionType === 'chat') {
364
- // @ts-expect-error bindings has no type
365
- bindings['enter'] = {
333
+ // Always add an Enter binding to support emoji selection in all interaction types
334
+ return {
335
+ 'enter': {
366
336
  key: 'Enter',
367
337
  shiftKey: false,
368
338
  handler: () => {
369
- const dialog = document.querySelector('zn-dialog-module') as DialogModuleComponent | null;
370
- const isDialogOpen = dialog?.open ?? false;
371
- const menu = document.querySelector('zn-menu-module') as MenuModuleComponent | null;
372
- const isMenuOpen = menu?.open ?? false;
373
- if (!isMenuOpen && !isDialogOpen) {
339
+ const emoji = this.quillElement.getModule('headlessEmoji') as HeadlessEmoji;
340
+ const menu = this.quillElement.getModule('contextMenu') as ContextMenu;
341
+ const dialog = this.quillElement.getModule('dialog') as Dialog;
342
+ if (emoji.isOpen() || menu.isOpen() || dialog.isOpen()) {
343
+ return false;
344
+ }
345
+
346
+ if (this.interactionType === 'chat') {
374
347
  const form = this.closest('form');
375
348
  if (form && this.value && this.value.trim().length > 0 && !empty(this.value)) {
376
349
  this.emit('zn-submit', {detail: {value: this.value, element: this}});
377
350
  form.requestSubmit();
378
351
  this.quillElement.setText('');
352
+ // prevent default Enter (submit completed)
353
+ return false;
379
354
  }
355
+ // If we didn't submit, allow default so Enter inserts newline
356
+ return true;
380
357
  }
358
+
359
+ // For non-chat interactions, allow default behavior (newline)
360
+ return true;
381
361
  },
382
- };
362
+ }
363
+ };
364
+ }
365
+
366
+ private _handleEditorChange(e: OnEvent) {
367
+ const target = e.selectedTarget as HTMLElement;
368
+ if (!target.hasAttribute('editor-mode')) return;
369
+
370
+ const editorMode = target.getAttribute('editor-mode');
371
+ if (!editorMode) return;
372
+
373
+ const contentContainer = target.getAttribute('editor-content-id');
374
+ if (!contentContainer) return;
375
+
376
+ // Find which element contains the content to insert
377
+ // Priority: currentTarget > selectedTarget > target > composedPath > querySelector
378
+ const contentElement: Element | null =
379
+ (e.currentTarget instanceof Element && e.currentTarget.closest('#' + contentContainer)) ||
380
+ (e.selectedTarget instanceof Element && e.selectedTarget.closest('#' + contentContainer)) ||
381
+ (e.target instanceof Element && e.target.closest('#' + contentContainer)) ||
382
+ ((e.composedPath()[0] as Element)?.closest('#' + contentContainer)) ||
383
+ deepQuerySelectorAll(`#${contentContainer}`, document.documentElement, '')[0];
384
+ if (!contentElement) return;
385
+
386
+ let content = contentElement.textContent;
387
+ if (contentElement instanceof HTMLInputElement || contentElement instanceof ZnTextarea) {
388
+ content = contentElement.value;
389
+ }
390
+ if (!content || content === '') return;
391
+
392
+ this._content = trim(content);
393
+
394
+ if (editorMode === 'replace') {
395
+ this._replaceTextAtSelection();
396
+ } else if (editorMode === 'insert') {
397
+ this._insertTextAtSelection();
398
+ }
399
+ }
400
+
401
+ private _replaceTextAtSelection() {
402
+ const range = this.quillElement.getSelection();
403
+ if (range) {
404
+ if (range.length === 0) {
405
+ this.quillElement.setText(this._content || '');
406
+ this.quillElement.setSelection((this._content?.length || 0), 0);
407
+ } else {
408
+ this.quillElement.deleteText(range.index, range.length);
409
+ this.quillElement.insertText(range.index, this._content || '');
410
+ this.quillElement.setSelection(range.index + (this._content.length || 0), 0);
411
+ }
412
+ } else {
413
+ this.quillElement.setText(this._content || '');
414
+ this.quillElement.setSelection((this._content?.length || 0), 0);
383
415
  }
384
416
 
385
- return bindings;
417
+ this._closePopups();
386
418
  }
387
419
 
388
- private _supplyPlaceholderDialog() {
389
- const placeholderItems: HTMLElement[] = Array.prototype.slice.call(this.shadowRoot?.querySelectorAll('.ql-placeholder .ql-picker-item'));
390
- placeholderItems.forEach((item) => {
391
- item.textContent = item.dataset.value ?? ''
392
- });
393
- placeholderItems.forEach((item) => item.classList.remove('ql-selected'));
420
+ private _insertTextAtSelection() {
421
+ const range = this.quillElement.getSelection();
422
+ const index = range ? range.index : this._selectionRange.index;
423
+ this.quillElement.insertText(index, this._content || '');
424
+ this.quillElement.setSelection(index + (this._content.length || 0), 0);
425
+
426
+ this._closePopups();
427
+ }
428
+
429
+ private _closePopups() {
430
+ this._closeAiPanel();
431
+ this._closeDialog();
432
+ }
433
+
434
+ private _closeAiPanel() {
435
+ const aiModule = this.quillElement.getModule('ai') as QuillAI;
436
+ if (aiModule) {
437
+ aiModule.resetComponent();
438
+ }
394
439
  }
395
440
 
396
- private _setupTitleAttributes(quill: Quill) {
397
- const toolbar = quill.container.previousSibling as HTMLElement;
398
- if (toolbar) {
399
- toolbar.querySelector('button.ql-bold')?.setAttribute('title', 'Bold');
400
- toolbar.querySelector('button.ql-italic')?.setAttribute('title', 'Italic');
401
- toolbar.querySelector('button.ql-underline')?.setAttribute('title', 'Underline');
402
- toolbar.querySelector('button.ql-strike')?.setAttribute('title', 'Strikethrough');
403
- toolbar.querySelector('button.ql-undo')?.setAttribute('title', 'Undo');
404
- toolbar.querySelector('button.ql-redo')?.setAttribute('title', 'Redo');
405
- toolbar.querySelector('button.ql-redo')?.setAttribute('title', 'Redo');
406
- toolbar.querySelector('button.ql-list[value="ordered"]')?.setAttribute('title', 'Ordered List');
407
- toolbar.querySelector('button.ql-list[value="bullet"]')?.setAttribute('title', 'Bullet List');
408
- toolbar.querySelector('button.ql-link')?.setAttribute('title', 'Link');
409
- toolbar.querySelector('button.ql-image')?.setAttribute('title', 'Image');
410
- toolbar.querySelector('button.ql-remove-formatting')?.setAttribute('title', 'Remove Formatting');
441
+ private _closeDialog() {
442
+ const dialogModule = this.quillElement.getModule('dialog') as Dialog;
443
+ if (dialogModule) {
444
+ dialogModule.close();
411
445
  }
412
446
  }
413
447
 
414
448
  render() {
415
449
  return html`
416
- <div id="editor"></div>
450
+ <zn-toolbar id="toolbar">
451
+ <slot name="tools"></slot>
452
+ </zn-toolbar>
453
+ <slot name="context-items" style="display: none;"></slot>
454
+ <div id="editor" x-editor-id="${this.id}"></div>
417
455
  <input type="text" id="editorHtml" name="${this.name}" value="${this.value}" style="display: none;">
418
456
  <div id="action-container" class="ql-toolbar ql-snow">
419
457
  <slot name="actions"></slot>
@@ -16,7 +16,7 @@
16
16
 
17
17
  display: flex;
18
18
  flex-direction: column;
19
- background-color: rgb(var(--zn-body));
19
+ background-color: rgba(var(--zn-panel), var(--zn-panel-opacity));
20
20
  margin: 0;
21
21
  padding: 0;
22
22
  max-height: 100%;
@@ -33,7 +33,6 @@
33
33
 
34
34
  .ql-toolbar {
35
35
  border: 0 solid rgb(var(--zn-border-color));
36
- border-bottom-width: 1px;
37
36
  color: rgb(var(--zn-text-heading));
38
37
  display: flex;
39
38
  flex-wrap: wrap;
@@ -63,7 +62,7 @@
63
62
  color: rgb(var(--zn-text));
64
63
  background-color: rgba(var(--zn-panel), var(--zn-panel-opacity));
65
64
  font-size: 15px;
66
- padding: var(--zn-spacing-medium);
65
+ padding: 0 var(--zn-spacing-medium);
67
66
 
68
67
  img {
69
68
  max-width: 40%;
@@ -92,7 +91,6 @@
92
91
  }
93
92
 
94
93
  #action-container.ql-toolbar {
95
- border-top-width: 1px;
96
94
  border-bottom-width: 0;
97
95
  }
98
96
 
@@ -125,7 +123,29 @@
125
123
  color: rgb(var(--zn-text))
126
124
  }
127
125
 
128
-
129
126
  .ql-code-block {
130
127
  font-size: 90%;
131
128
  }
129
+
130
+ /* Editor toolbar custom active states */
131
+ :host #toolbar zn-button.ql-active .button {
132
+ background-color: rgba(var(--zn-primary), 0.2);
133
+ }
134
+
135
+ :host #toolbar zn-dropdown zn-menu zn-menu-item[checked] .menu-item {
136
+ background-color: rgba(var(--zn-panel-highlight), var(--zn-panel-highlight-opacity));
137
+ color: rgb(var(--zn-primary));
138
+ }
139
+
140
+ :host .ql-editor hr,
141
+ :host .ql-editor .ql-hr {
142
+ border: none;
143
+ border-top: 1px solid rgb(var(--zn-border-color));
144
+ height: 0;
145
+ margin: var(--zn-spacing-small) 0;
146
+ opacity: 1;
147
+ }
148
+
149
+ #toolbar {
150
+ padding: 4px;
151
+ }