@kubex/zinc 1.0.4 → 1.0.6

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 (158) hide show
  1. package/dist/custom-elements.json +1278 -191
  2. package/dist/vscode.html-custom-data.json +93 -16
  3. package/dist/web-types.json +235 -25
  4. package/dist/zn.d.ts +1313 -941
  5. package/dist/zn.min.css +1 -1
  6. package/dist/zn.min.js +2049 -931
  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/bulk-actions/bulk-actions.component.ts +152 -84
  39. package/src/components/bulk-actions/bulk-actions.scss +2 -0
  40. package/src/components/button/button.component.ts +6 -2
  41. package/src/components/button/button.scss +16 -1
  42. package/src/components/button-menu/button-menu.component.ts +23 -15
  43. package/src/components/button-menu/button-menu.scss +4 -4
  44. package/src/components/chart/chart.component.ts +14 -12
  45. package/src/components/checkbox/checkbox.component.ts +5 -1
  46. package/src/components/checkbox/checkbox.scss +1 -1
  47. package/src/components/chip/chip.component.ts +7 -3
  48. package/src/components/chip/chip.scss +9 -1
  49. package/src/components/collapsible/collapsible.component.ts +48 -2
  50. package/src/components/cols/cols.component.ts +7 -3
  51. package/src/components/cols/cols.scss +22 -14
  52. package/src/components/confirm/confirm.component.ts +1 -2
  53. package/src/components/content-block/content-block.component.ts +104 -14
  54. package/src/components/content-block/content-block.scss +8 -0
  55. package/src/components/data-select/data-select.component.ts +1 -1
  56. package/src/components/data-table/data-table.component.ts +394 -430
  57. package/src/components/data-table/data-table.scss +6 -1
  58. package/src/components/datepicker/datepicker.scss +1 -1
  59. package/src/components/dialog/dialog.component.ts +52 -33
  60. package/src/components/dialog/dialog.scss +15 -1
  61. package/src/components/dropdown/dropdown.component.ts +1 -1
  62. package/src/components/editor/editor.component.ts +196 -158
  63. package/src/components/editor/editor.scss +25 -5
  64. package/src/components/editor/modules/ai/index.ts +281 -0
  65. package/src/components/editor/modules/ai/panel/ai-panel.component.ts +102 -0
  66. package/src/components/editor/modules/ai/panel/ai-panel.scss +83 -0
  67. package/src/components/editor/modules/ai/tooltip/ai-tooltip.component.ts +30 -0
  68. package/src/components/editor/modules/ai/tooltip/ai-tooltip.scss +28 -0
  69. package/src/components/editor/modules/{attachment-module.ts → attachment/attachment.ts} +7 -7
  70. package/src/components/editor/modules/context-menu/context-menu-component.ts +115 -0
  71. package/src/components/editor/modules/context-menu/context-menu.scss +49 -0
  72. package/src/components/editor/modules/context-menu/context-menu.ts +296 -0
  73. package/src/components/editor/modules/context-menu/quick-action/index.ts +12 -0
  74. package/src/components/editor/modules/context-menu/quick-action/quick-action.component.ts +18 -0
  75. package/src/components/editor/modules/date-picker/date-picker.ts +77 -0
  76. package/src/components/editor/modules/dialog/dialog.component.ts +115 -0
  77. package/src/components/editor/modules/dialog/dialog.scss +43 -0
  78. package/src/components/editor/modules/dialog/dialog.ts +46 -0
  79. package/src/components/editor/modules/{drag-drop-module.ts → drag-drop/drag-drop.ts} +6 -6
  80. package/src/components/editor/modules/emoji/emoji.ts +106 -0
  81. package/src/components/editor/modules/emoji/headless/headless-emoji.component.ts +122 -0
  82. package/src/components/editor/modules/emoji/headless/headless-emoji.scss +52 -0
  83. package/src/components/editor/modules/emoji/headless/headless-emoji.ts +223 -0
  84. package/src/components/editor/modules/{image-resize-module/image-resize-module.ts → image-resize/image-resize.ts} +5 -5
  85. package/src/components/editor/modules/{time-tracking-module.ts → time-tracking/time-tracking.ts} +5 -5
  86. package/src/components/editor/modules/toolbar/tool/index.ts +12 -0
  87. package/src/components/editor/modules/toolbar/tool/tool.component.ts +30 -0
  88. package/src/components/editor/modules/toolbar/tool/tool.scss +5 -0
  89. package/src/components/editor/modules/toolbar/toolbar.component.ts +738 -0
  90. package/src/components/editor/modules/toolbar/toolbar.scss +98 -0
  91. package/src/components/editor/modules/toolbar/toolbar.ts +328 -0
  92. package/src/components/expanding-action/expanding-action.component.ts +2 -2
  93. package/src/components/expanding-action/expanding-action.scss +2 -2
  94. package/src/components/filter-wrapper/filter-wrapper.component.ts +91 -0
  95. package/src/components/filter-wrapper/filter-wrapper.scss +11 -0
  96. package/src/components/filter-wrapper/filter-wrapper.test.ts +10 -0
  97. package/src/components/filter-wrapper/index.ts +12 -0
  98. package/src/components/header/header.component.ts +1 -1
  99. package/src/components/header/header.scss +8 -1
  100. package/src/components/hover-container/hover-container.scss +39 -36
  101. package/src/components/icon/icon.component.ts +11 -5
  102. package/src/components/icon/icon.scss +15 -2
  103. package/src/components/inline-edit/inline-edit.component.ts +52 -6
  104. package/src/components/inline-edit/inline-edit.scss +15 -2
  105. package/src/components/input/input.scss +1 -1
  106. package/src/components/item/item.component.ts +30 -19
  107. package/src/components/item/item.scss +57 -23
  108. package/src/components/linked-select/linked-select.component.ts +5 -1
  109. package/src/components/menu/menu.component.ts +1 -0
  110. package/src/components/menu/menu.scss +1 -1
  111. package/src/components/menu-item/menu-item.component.ts +22 -4
  112. package/src/components/menu-item/menu-item.scss +54 -7
  113. package/src/components/navbar/navbar.component.ts +7 -1
  114. package/src/components/navbar/navbar.scss +26 -16
  115. package/src/components/option/option.component.ts +4 -1
  116. package/src/components/option/option.scss +9 -0
  117. package/src/components/page-nav/page-nav.component.ts +2 -2
  118. package/src/components/page-nav/page-nav.scss +4 -4
  119. package/src/components/panel/panel.component.ts +20 -9
  120. package/src/components/panel/panel.scss +5 -1
  121. package/src/components/popup/popup.component.ts +16 -2
  122. package/src/components/progress-tile/progress-tile.component.ts +10 -4
  123. package/src/components/progress-tile/progress-tile.scss +7 -4
  124. package/src/components/query-builder/query-builder.component.ts +1 -1
  125. package/src/components/radio/radio.scss +1 -1
  126. package/src/components/scroll-container/scroll-container.component.ts +56 -9
  127. package/src/components/scroll-container/scroll-container.scss +37 -2
  128. package/src/components/select/select.component.ts +3 -0
  129. package/src/components/select/select.scss +1 -1
  130. package/src/components/settings-container/index.ts +12 -0
  131. package/src/components/settings-container/settings-container.component.ts +226 -0
  132. package/src/components/settings-container/settings-container.scss +99 -0
  133. package/src/components/settings-container/settings-container.test.ts +10 -0
  134. package/src/components/skeleton/skeleton.scss +5 -4
  135. package/src/components/sp/sp.component.ts +2 -0
  136. package/src/components/sp/sp.scss +2 -0
  137. package/src/components/split-button/split-button.component.ts +24 -18
  138. package/src/components/split-pane/split-pane.component.ts +5 -5
  139. package/src/components/stat/stat.component.ts +5 -1
  140. package/src/components/stat/stat.scss +24 -1
  141. package/src/components/style/style.component.ts +25 -1
  142. package/src/components/tabs/tabs.component.ts +12 -1
  143. package/src/components/textarea/textarea.component.ts +3 -1
  144. package/src/components/textarea/textarea.scss +21 -2
  145. package/src/components/vertical-stepper/vertical-stepper.scss +1 -1
  146. package/src/internal/form.ts +3 -1
  147. package/src/internal/slot.ts +29 -3
  148. package/src/utilities/on.ts +7 -8
  149. package/src/utilities/query.ts +5 -1
  150. package/src/wc.scss +7 -0
  151. package/src/zinc.ts +5 -0
  152. package/src/components/editor/modules/dialog-module/dialog-module.component.ts +0 -289
  153. package/src/components/editor/modules/dialog-module/dialog-module.scss +0 -115
  154. package/src/components/editor/modules/dialog-module/dialog-module.ts +0 -99
  155. package/src/components/editor/modules/events/zn-show-canned-response-dialog.ts +0 -15
  156. package/src/components/editor/modules/menu-module/menu-module.component.ts +0 -149
  157. package/src/components/editor/modules/menu-module/menu-module.scss +0 -17
  158. package/src/components/editor/modules/menu-module/menu-module.ts +0 -203
@@ -1,115 +0,0 @@
1
- @use "../../../../wc";
2
-
3
- :host {
4
- @include wc.scrollbars;
5
- display: block;
6
- position: absolute;
7
- }
8
-
9
- ::backdrop {
10
- background-color: rgba(128, 128, 128, 0.6);
11
- }
12
-
13
- dialog[open] {
14
- display: grid;
15
- grid-template-rows: auto 1fr auto;
16
- gap: var(--zn-spacing-medium);
17
- }
18
-
19
- .dialog-module {
20
- background-color: rgb(var(--zn-panel));
21
- border: 1px solid rgb(var(--zn-border-color));
22
- overflow: auto;
23
- padding: var(--zn-spacing-medium) 0;
24
- border-radius: var(--zn-border-radius-medium);
25
- height: 100%;
26
- width: 100%;
27
- max-width: 975px;
28
- max-height: 650px;
29
-
30
- &__header {
31
- display: flex;
32
- align-items: center;
33
- gap: var(--zn-spacing-large);
34
- padding-inline: var(--zn-spacing-medium);
35
-
36
- &--caption {
37
- font-weight: var(--zn-font-weight-bold);
38
- font-size: var(--zn-font-size-large);
39
- color: rgba(var(--zn-text-panel-title), 100%);
40
- }
41
-
42
- &--search {
43
- flex-grow: 1;
44
- }
45
- }
46
-
47
- &__content {
48
- overflow: auto;
49
- max-height: 500px;
50
- }
51
-
52
- &__footer {
53
- display: flex;
54
- align-items: center;
55
- justify-content: space-between;
56
- gap: var(--zn-spacing-small);
57
- padding: 0 var(--zn-spacing-medium) var(--zn-spacing-medium);
58
- }
59
-
60
- .command {
61
- &__wrapper {
62
- padding: var(--zn-spacing-medium) var(--zn-spacing-x-large);
63
- cursor: pointer;
64
- border-bottom: 1px solid rgb(var(--zn-border-color));
65
- display: flex;
66
- align-items: center;
67
- gap: var(--zn-spacing-medium);
68
-
69
- &:hover {
70
- background-color: rgba(var(--zn-primary), 0.05);
71
- }
72
-
73
- &:focus-visible {
74
- outline: none;
75
- background-color: var(--zn-input-background-color-focus);
76
- border-color: var(--zn-input-border-color-focus);
77
- box-shadow: 0 0 0 var(--zn-focus-ring-width) var(--zn-input-focus-ring-color);
78
- }
79
- }
80
-
81
- &__left {
82
- width: 50%;
83
- flex-basis: 50%;
84
- }
85
-
86
- &__command {
87
- font-size: var(--zn-font-size-small);
88
- color: rgb(var(--zn-text-heading));
89
- }
90
-
91
- &__labels {
92
- display: flex;
93
- gap: var(--zn-spacing-small);
94
- flex-wrap: wrap;
95
- padding-top: var(--zn-spacing-small);
96
- font-size: var(--zn-font-size-small);
97
- }
98
-
99
- &__right {
100
- width: 50%;
101
- flex-basis: 50%;
102
- }
103
-
104
- &__title {
105
- font-size: var(--zn-font-size-small);
106
- font-weight: 500;
107
- }
108
-
109
- &__snippet {
110
- margin-top: var(--zn-spacing-small);
111
- font-size: var(--zn-font-size-small);
112
- color: rgb(var(--zn-text));
113
- }
114
- }
115
- }
@@ -1,99 +0,0 @@
1
- import './dialog-module.component';
2
- import {Delta} from 'quill';
3
- import {html} from "lit";
4
- import {litToHTML} from "../../../../utilities/lit-to-html";
5
- import type {CannedResponse} from "../../editor.component";
6
- import type {ZnCommandSelectEvent} from "../events/zn-command-select";
7
- import type Quill from 'quill';
8
- import type ZnDialogModule from './dialog-module.component';
9
-
10
- class DialogModule {
11
- private _quill: Quill;
12
- private _dialog: ZnDialogModule;
13
- private _commands: CannedResponse[] = [];
14
-
15
- constructor(quill: Quill) {
16
- this._quill = quill;
17
- this.initDialog();
18
-
19
- document.addEventListener('zn-show-canned-response-dialog', () => {
20
- this._open();
21
- });
22
- }
23
-
24
- public setCommands(commands: CannedResponse[]) {
25
- this._commands = commands;
26
- this.addCommands();
27
- }
28
-
29
- private _open() {
30
- this._dialog.dialogEl.showModal();
31
- this.attachEvents();
32
- }
33
-
34
- private _close() {
35
- this._dialog.dialogEl.close();
36
- this.detachEvents();
37
- }
38
-
39
- private initDialog() {
40
- this._dialog = this.createDialog()!;
41
- this._quill.container.ownerDocument.body.appendChild(this._dialog);
42
- this.addCommands();
43
- }
44
-
45
- private attachEvents() {
46
- document.addEventListener('zn-command-select', this.onCommandSelect);
47
- }
48
-
49
- private detachEvents() {
50
- document.removeEventListener('zn-command-select', this.onCommandSelect);
51
- }
52
-
53
- private onCommandSelect = (e: ZnCommandSelectEvent) => {
54
- const item = e.detail.item;
55
- const command = item.getAttribute('data-command');
56
- if (command) {
57
- this.triggerCommand(this._commands.find((c) => c.title === command)!);
58
- }
59
- };
60
-
61
- private createDialog() {
62
- const dialog = html`
63
- <zn-dialog-module anchor=${this._quill.container}></zn-dialog-module>`
64
-
65
- return litToHTML<ZnDialogModule>(dialog);
66
- }
67
-
68
- private addCommands() {
69
- this._dialog.setAttribute('commands', JSON.stringify(this._commands));
70
- }
71
-
72
- private triggerCommand(command: CannedResponse) {
73
- this._close();
74
- this._quill.focus();
75
-
76
- const range = this._quill.getSelection();
77
- if (range) {
78
- let insertIndex = range.index - 1;
79
- this._quill.deleteText(insertIndex, 1, 'user');
80
-
81
- const prevChar = this._quill.getText(insertIndex - 1, 1);
82
- if (prevChar !== ' ' && insertIndex > 0) {
83
- this._quill.insertText(insertIndex, ' ', 'user');
84
- insertIndex += 1;
85
- }
86
-
87
- const contentDelta = this._quill.clipboard.convert({html: command?.content});
88
- this._quill.updateContents(
89
- new Delta().retain(insertIndex).concat(contentDelta),
90
- 'user'
91
- );
92
-
93
- setTimeout(() => this._quill.setSelection(insertIndex + contentDelta.length(), 0, 'silent'), 0);
94
- this._quill.focus();
95
- }
96
- }
97
- }
98
-
99
- export default DialogModule;
@@ -1,15 +0,0 @@
1
- export type ZnShowCannedResponseDialogEvent = CustomEvent<{
2
- commands: {
3
- title: string;
4
- content: string;
5
- command: string;
6
- count: string;
7
- labels?: string[];
8
- }[];
9
- }>;
10
-
11
- declare global {
12
- interface GlobalEventHandlersEventMap {
13
- 'zn-show-canned-response-dialog': ZnShowCannedResponseDialogEvent;
14
- }
15
- }
@@ -1,149 +0,0 @@
1
- import {classMap} from "lit/directives/class-map.js";
2
- import {type CSSResultGroup, html, type TemplateResult, unsafeCSS} from "lit";
3
- import {property, query, state} from "lit/decorators.js";
4
- import {watch} from "../../../../internal/watch";
5
- import ZincElement from "../../../../internal/zinc-element";
6
- import type {CannedResponse} from "../../editor.component";
7
- import type ZnMenu from "../../../menu";
8
-
9
- import styles from './menu-module.scss';
10
-
11
- export default class MenuModuleComponent extends ZincElement {
12
- static styles: CSSResultGroup = unsafeCSS(styles);
13
-
14
- @state() private hasFocus = false;
15
-
16
- @query('zn-menu') menuEl!: ZnMenu;
17
-
18
- @query('.menu-module__content') commandList!: HTMLElement;
19
-
20
- @query('.menu-module') menuModule!: HTMLElement;
21
-
22
- @property({type: Array}) commands: CannedResponse[] = [];
23
- @property({type: Boolean, reflect: true}) open = false;
24
-
25
- private closeWatcher: CloseWatcher | null;
26
-
27
- connectedCallback() {
28
- super.connectedCallback();
29
- this.addEventListener('zn-menu-ready', this.focus);
30
- }
31
-
32
- disconnectedCallback() {
33
- super.disconnectedCallback();
34
- this.removeEventListener('zn-menu-ready', this.focus);
35
- }
36
-
37
- focus() {
38
- // Focus the first menu item if available, otherwise fallback to menuEl
39
- const firstItem = this.menuEl?.getAllItems?.()[0];
40
- if (firstItem) {
41
- firstItem.focus();
42
- } else {
43
- this.menuEl?.focus();
44
- }
45
- }
46
-
47
- show() {
48
- this.open = true;
49
- this.emit('zn-show');
50
- this.focus();
51
- }
52
-
53
- hide() {
54
- this.open = false;
55
- this.emit('zn-close');
56
- this.blur();
57
- }
58
-
59
- @watch('open', {waitUntilFirstUpdate: true})
60
- handleOpenChange() {
61
- if (this.open) {
62
- this.addOpenListeners();
63
- this.menuEl.style.display = 'block';
64
- setTimeout(() => this.focus(), 0);
65
- } else {
66
- this.menuEl.style.display = 'none';
67
- this.removeOpenListeners();
68
- }
69
- }
70
-
71
- private addOpenListeners() {
72
- if ('CloseWatcher' in window) {
73
- this.closeWatcher?.destroy();
74
- this.closeWatcher = new CloseWatcher();
75
- this.closeWatcher.onclose = () => this.requestClose('keyboard');
76
- }
77
- }
78
-
79
- private removeOpenListeners() {
80
- this.closeWatcher?.destroy();
81
- }
82
-
83
- private requestClose(source: 'close-button' | 'keyboard' | 'overlay') {
84
- const znRequestClose = this.emit('zn-request-close', {
85
- cancelable: true,
86
- detail: {source}
87
- });
88
-
89
- if (znRequestClose.defaultPrevented) {
90
- return;
91
- }
92
-
93
- this.hide();
94
- }
95
-
96
- private showDialog = () => {
97
- this.hide();
98
- this.emit('zn-show-canned-response-dialog', {detail: {commands: this.commands}});
99
- }
100
-
101
- private handleClick = (event: MouseEvent) => {
102
- const target = event.target as HTMLElement | null;
103
-
104
- if (!target) return;
105
-
106
- const item = target.hasAttribute('data-command')
107
- ? target
108
- : target.closest('[data-command]') as HTMLElement | null;
109
-
110
- if (item) {
111
- this.emit('zn-command-select', {detail: {item}});
112
- this.emit('zn-editor-update');
113
- }
114
- }
115
-
116
- private _createCommand(command: CannedResponse): TemplateResult {
117
- return html`
118
- <zn-menu-item data-command="${command.title}" @click="${this.handleClick}">
119
- <div class="command__wrapper">
120
- <div class="command__command">
121
- ${'/' + (command.command ? command.command :
122
- command.title.toLowerCase().replace(' ', '-'))}
123
- </div>
124
- <div class="command__title">
125
- ${command.title}
126
- </div>
127
- </div>
128
- </zn-menu-item>`
129
- }
130
-
131
- render() {
132
- return html`
133
- <zn-menu class="${classMap({
134
- 'menu-module': true,
135
- 'menu-module--has-focus': this.hasFocus,
136
- 'menu-module--has-results': this.commands.length > 0,
137
- 'menu-module--open': this.open,
138
- })}">
139
- ${this.commands.sort((a, b) => parseInt(b.count) - parseInt(a.count))
140
- .slice(0, 3)
141
- .map(command => this._createCommand(command))}
142
- <zn-menu-item @click="${this.showDialog}">View All</zn-menu-item>
143
- </zn-menu>
144
- `;
145
- }
146
-
147
- }
148
-
149
- MenuModuleComponent.define('zn-menu-module');
@@ -1,17 +0,0 @@
1
- .menu-module {
2
- display: none;
3
-
4
- &--open {
5
- display: block;
6
- z-index: var(--zn-z-index-dialog);
7
- }
8
- }
9
-
10
- .command {
11
- &__wrapper {
12
- display: flex;
13
- flex-direction: column;
14
- align-items: start;
15
- gap: var(--zn-spacing-2x-small);
16
- }
17
- }
@@ -1,203 +0,0 @@
1
- import './menu-module.component';
2
- import {html} from "lit";
3
- import {litToHTML} from "../../../../utilities/lit-to-html";
4
- import {type ZnCommandSelectEvent} from "../events/zn-command-select";
5
- import Quill, {Delta} from 'quill';
6
- import type {CannedResponse} from "../../editor.component";
7
- import type ZnMenuModule from './menu-module.component';
8
-
9
- interface MenuModuleOptions {
10
- cannedResponses: CannedResponse[];
11
- cannedResponsesUri: string;
12
- }
13
-
14
- export let menuOpen: boolean = false;
15
-
16
- class MenuModule {
17
- private _quill: Quill;
18
- private _menu: ZnMenuModule;
19
- private readonly _cannedResponsesUri: string = '';
20
- private _commands: CannedResponse[] = [];
21
-
22
- constructor(quill: Quill, options: MenuModuleOptions) {
23
- this._quill = quill;
24
- this._cannedResponsesUri = options.cannedResponsesUri?.trim() || '';
25
- this._commands = options.cannedResponses || [];
26
-
27
- if (this._commands.length > 0 || this._cannedResponsesUri) {
28
- this.initMenu();
29
- this.attachEvents();
30
- }
31
- }
32
-
33
- private initMenu() {
34
- this._menu = this.createMenu()!;
35
- this._quill.container.ownerDocument.body.appendChild(this._menu);
36
- this.updateMenuPosition();
37
- this.addCommands();
38
- }
39
-
40
- private attachEvents() {
41
- this._quill.on(Quill.events.TEXT_CHANGE, this.onTextChange);
42
- document.addEventListener('zn-command-select', this.onCommandSelect);
43
- document.addEventListener('click', this.onDocumentClick);
44
- this._menu.addEventListener('zn-show', () => setTimeout(() => this.updateMenuPosition(), 1));
45
- }
46
-
47
- private detachEvents() {
48
- document.removeEventListener('zn-command-select', this.onCommandSelect);
49
- document.removeEventListener('click', this.onDocumentClick);
50
- this._menu.removeEventListener('zn-show', () => setTimeout(() => this.updateMenuPosition(), 1));
51
- }
52
-
53
- private onTextChange = async (_: Delta, _oldDelta: Delta, source: string) => {
54
- if (source === Quill.sources.USER) {
55
- // if the user has typed a forward slash, we will show the menu until they type
56
- // a space or a new line. The menu will contain a list of commands that the user
57
- // can select from.
58
- const text = this._quill.getText();
59
- const index = this._quill.getSelection()?.index ?? 0;
60
- const char = index === 0 ? text.charAt(0) : text.charAt(index - 1); // Last input char
61
- const openCharacter = '/';
62
-
63
- // If the openCharacter is the first character in the editor, and menu isn't open, open it.
64
- if (index === 0 && char === openCharacter && !menuOpen) {
65
- await this._openMenu();
66
- } else if (char === openCharacter && text.charAt(index - 2) === ' ' && !menuOpen) {
67
- await this._openMenu();
68
- }
69
-
70
- // if the user has typed a space or a new line, we will close the menu if the menu
71
- // is open
72
- if (menuOpen && (char === ' ' || char === '\n')) {
73
- this._closeMenu();
74
- }
75
-
76
- if (menuOpen) {
77
- this.updateMenuPosition();
78
- }
79
-
80
- // if the user clicks away from the menu, we will close the menu
81
- document.addEventListener('click', (e) => {
82
- if (menuOpen && e.target !== this._menu) {
83
- this._closeMenu();
84
- }
85
- });
86
- }
87
- }
88
-
89
- private onCommandSelect = (e: Event) => {
90
- const event = e as ZnCommandSelectEvent;
91
- const commandTitle = event.detail.item.getAttribute('data-command');
92
- if (!commandTitle) return;
93
- const command = this._commands.find(cmd => cmd.title === commandTitle);
94
- if (command) {
95
- this.triggerCommand(command);
96
- }
97
- };
98
-
99
- private onDocumentClick = (e: MouseEvent) => {
100
- if (menuOpen && e.target !== this._menu) {
101
- this._closeMenu();
102
- }
103
- };
104
-
105
- private updateMenuPosition(menu: ZnMenuModule = this._menu) {
106
- const editorBounds = this._quill.container.getBoundingClientRect();
107
-
108
- // Get the current cursor position
109
- const range = this._quill.getSelection();
110
- if (range) {
111
- const cursorBounds = this._quill.getBounds(range.index);
112
-
113
- if (cursorBounds) {
114
- // Position the menu to the right of the cursor
115
- const maxHeight = 275;
116
- const top = editorBounds.top + cursorBounds.top;
117
- const left = editorBounds.left + cursorBounds.right + 10; // Add offset to the right
118
- const windowHeight = window.innerHeight;
119
- const hasRoom = windowHeight - top > maxHeight; // Check if there's enough room for the menu
120
-
121
- if (hasRoom) {
122
- menu.style.top = `${top}px`;
123
- } else {
124
- menu.style.top = `${top - maxHeight}px`;
125
- }
126
-
127
- menu.style.position = 'absolute';
128
- menu.style.left = `${left}px`;
129
- menu.style.maxHeight = `${maxHeight}px`;
130
- }
131
- }
132
- }
133
-
134
- private async _openMenu() {
135
- menuOpen = true;
136
- this.attachEvents();
137
- if (this._cannedResponsesUri) {
138
- await this.fetchCannedResponses();
139
- }
140
-
141
- if (this._commands.length > 0) {
142
- this._menu.show();
143
- this._menu.focus();
144
- }
145
- }
146
-
147
- private _closeMenu() {
148
- menuOpen = false;
149
- this.detachEvents();
150
- this._menu.hide();
151
- }
152
-
153
- private createMenu() {
154
- const menu = html`
155
- <zn-menu-module anchor=${this._quill.container}></zn-menu-module>`
156
-
157
- return litToHTML<ZnMenuModule>(menu);
158
- }
159
-
160
- private addCommands() {
161
- this._menu.setAttribute('commands', JSON.stringify(this._commands));
162
- }
163
-
164
- private triggerCommand(command: CannedResponse) {
165
- this._closeMenu();
166
- this._quill.focus();
167
-
168
- const range = this._quill.getSelection();
169
- if (range) {
170
- let insertIndex = range.index - 1;
171
- this._quill.deleteText(insertIndex, 1, 'user');
172
-
173
- const prevChar = this._quill.getText(insertIndex - 1, 1);
174
- if (prevChar !== ' ' && insertIndex > 0) {
175
- this._quill.insertText(insertIndex, ' ', 'user');
176
- insertIndex += 1;
177
- }
178
-
179
- const contentDelta = this._quill.clipboard.convert({html: command?.content});
180
- this._quill.updateContents(
181
- new Delta().retain(insertIndex).concat(contentDelta),
182
- 'user'
183
- );
184
-
185
- setTimeout(() => this._quill.setSelection(insertIndex + contentDelta.length(), 0, 'silent'), 0);
186
- this._quill.focus();
187
- }
188
- }
189
-
190
- private async fetchCannedResponses() {
191
- try {
192
- const response = await fetch(this._cannedResponsesUri);
193
- this._commands = await response.json() as CannedResponse[];
194
- if (this._menu) {
195
- this.addCommands();
196
- }
197
- } catch (error) {
198
- console.error('Error fetching canned responses', error);
199
- }
200
- }
201
- }
202
-
203
- export default MenuModule;