@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
@@ -0,0 +1,281 @@
1
+ import {html} from "lit";
2
+ import {litToHTML} from "../../../../utilities/lit-to-html";
3
+ import AIPanelComponent from './panel/ai-panel.component';
4
+ import AITooltipComponent from "./tooltip/ai-tooltip.component";
5
+ import Quill from "quill";
6
+ import ZnTextarea from "../../../textarea";
7
+ import type {Range} from "quill";
8
+
9
+ class QuillAI {
10
+ private _quill: Quill;
11
+ private readonly _path: string = '';
12
+ private _component!: AITooltipComponent | AIPanelComponent;
13
+ private _selectedText: string = '';
14
+ private _prompt: string = '';
15
+ private _aiResponseContent: string = '';
16
+
17
+ constructor(quill: Quill, options: { path: string }) {
18
+ this._quill = quill;
19
+ this._path = options.path;
20
+
21
+ this._initComponent();
22
+ this._attachEvents();
23
+ }
24
+
25
+ private _initComponent() {
26
+ this._component = this._createTooltipComponent()!;
27
+ this._component.addEventListener('click', (e: Event) => this._replaceTooltip(e));
28
+ this._quill.container.ownerDocument.body.appendChild(this._component);
29
+ }
30
+
31
+ private _attachEvents() {
32
+ this._quill.on(Quill.events.SELECTION_CHANGE, (range) => this._updateFromEditor(range));
33
+ }
34
+
35
+ private _latestContent(panel: HTMLElement | null | undefined): string {
36
+ if (panel === null || panel === undefined) {
37
+ panel = this._component.shadowRoot?.querySelector('.ai-panel');
38
+ }
39
+ if (panel) {
40
+ const ele = panel.querySelector('[name="editor-response-text"]')
41
+ if (ele instanceof HTMLInputElement || ele instanceof ZnTextarea) {
42
+ return ele.value;
43
+ }
44
+ }
45
+ return this._aiResponseContent
46
+ }
47
+
48
+ async processAIRequest() {
49
+ const quotedSelectedText = this._selectedText ? this._selectedText : this._quill.getText();
50
+ const panel: HTMLElement | null | undefined = this._component.shadowRoot?.querySelector('.ai-panel');
51
+
52
+ const response = await fetch(this._path, {
53
+ method: 'POST',
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ 'x-kx-fetch-style': 'zn-editor',
57
+ 'x-kx-editor-id': this._quill?.container?.getAttribute('x-editor-id') || '',
58
+ },
59
+ body: JSON.stringify({text: quotedSelectedText, prompt: this._prompt})
60
+ });
61
+
62
+ if (response.ok) {
63
+ const result: unknown = await response.text();
64
+ if (panel) {
65
+ panel.style.width = '500px';
66
+ panel.innerHTML = result as string;
67
+
68
+ this._aiResponseContent = this._latestContent(panel);
69
+ }
70
+ } else {
71
+ const result: unknown = await response.json();
72
+
73
+ if (typeof result === 'string') {
74
+ const range = this._quill.getSelection();
75
+ if (range) {
76
+ this._quill.deleteText(range.index, range.length);
77
+ this._quill.insertText(range.index, result);
78
+ this._quill.setSelection(range.index + result.length, 0);
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ private _createTooltipComponent() {
85
+ const tpl = html`
86
+ <zn-ai-tooltip></zn-ai-tooltip>`;
87
+ return litToHTML<AITooltipComponent>(tpl);
88
+ }
89
+
90
+ private _createPanelComponent() {
91
+ const tpl = html`
92
+ <zn-ai-panel></zn-ai-panel>`;
93
+ return litToHTML<AIPanelComponent>(tpl);
94
+ }
95
+
96
+ private _replaceTooltip(e: Event) {
97
+ e.preventDefault();
98
+ e.stopPropagation();
99
+
100
+ this._component.remove();
101
+
102
+ const panel = this._createPanelComponent();
103
+ if (!panel) return;
104
+
105
+ this._component = panel;
106
+ this._component.requestUpdate();
107
+
108
+ // Attach panel button triggers
109
+ this._attachPanelEvents(panel);
110
+
111
+ // TODO: Finish overlay to prevent clicking away
112
+ /* // Create and append the overlay
113
+ const overlay = document.createElement('div');
114
+ overlay.style.position = 'fixed';
115
+ overlay.style.top = '0';
116
+ overlay.style.left = '0';
117
+ overlay.style.width = '100%';
118
+ overlay.style.height = '100%';
119
+ overlay.style.backgroundColor = 'none';
120
+ overlay.style.zIndex = '10000';
121
+
122
+ // Append the overlay and panel to the body
123
+ this._quill.container.ownerDocument.body.appendChild(overlay);
124
+
125
+ this._component.style.zIndex = '10001';*/
126
+
127
+ this._quill.container.ownerDocument.body.appendChild(this._component);
128
+ this._show();
129
+ this._positionComponent();
130
+ }
131
+
132
+ private _onDocumentClick(e: MouseEvent) {
133
+ const path = e.composedPath ? e.composedPath() : [e.target as Node];
134
+ const isInsideComponent = path.includes(this._component) || path.includes(this._component.shadowRoot!);
135
+ const isInsideQuillRoot = this._quill.root.contains(path[0] as Node);
136
+
137
+ const isInsideAIPanel = path.some((node) => {
138
+ if (node instanceof HTMLElement) {
139
+ return node.tagName === 'ZN-AI-PANEL';
140
+ }
141
+ return false;
142
+ });
143
+
144
+ // If this._aiResponseContent is not empty, it means user is interacting with the panel
145
+ if (this._latestContent(null)) {
146
+ if (!isInsideAIPanel) {
147
+ e.preventDefault();
148
+ e.stopPropagation();
149
+
150
+ return;
151
+ }
152
+ }
153
+
154
+ if (!isInsideComponent && !isInsideQuillRoot && !isInsideAIPanel) {
155
+ this.resetComponent();
156
+ }
157
+ }
158
+
159
+ private _updateFromEditor(range: Range) {
160
+ // Update if component isn't AI panel
161
+ if (this._component instanceof AIPanelComponent) return;
162
+
163
+ // Display 'refine' option for selections longer than 25 characters
164
+ if (range?.length > 25) {
165
+ // Keep selected text in memory to be passed to AI later
166
+ this._selectedText = this._quill.getText(range.index, range.length);
167
+
168
+ this._show();
169
+ this._positionComponent();
170
+ } else {
171
+ this.resetComponent();
172
+ }
173
+ }
174
+
175
+ private _positionComponent() {
176
+ if (!this._component || !this._component.open) return;
177
+
178
+ const range = this._quill.getSelection();
179
+ if (!range) return;
180
+
181
+ if (this._component instanceof AITooltipComponent) {
182
+ const editorBounds = this._quill.container.getBoundingClientRect();
183
+ const endIndex = range.index + range.length;
184
+ const bounds = this._quill.getBounds(endIndex);
185
+ if (!bounds) return;
186
+
187
+ const left = editorBounds.left + bounds.left - 10; // Slight offset to the left
188
+ const top = editorBounds.top + bounds.bottom + 4;
189
+
190
+ this._component.style.left = `${Math.max(0, left)}px`;
191
+ this._component.style.top = `${top}px`;
192
+
193
+ return;
194
+ }
195
+
196
+ const positionPanel = () => {
197
+ const editorBounds = this._quill.container.getBoundingClientRect();
198
+ let bounds;
199
+ if (range.length === this._quill.getLength() - 1) {
200
+ bounds = this._quill.getBounds(range.index, range.length);
201
+ } else {
202
+ const endIndex = range.index + range.length;
203
+ bounds = this._quill.getBounds(endIndex);
204
+ }
205
+ if (!bounds) return;
206
+
207
+ const right = editorBounds.left + bounds.right; // Align right side
208
+ const bottom = editorBounds.top + bounds.top - 8; // Position above the selection
209
+
210
+ if (right > window.innerWidth) {
211
+ this._setPanelPosition(window.innerWidth - 10, bottom);
212
+ return;
213
+ }
214
+
215
+ this._setPanelPosition(right, bottom);
216
+ };
217
+
218
+ // Defer positioning to ensure the panel is fully rendered
219
+ requestAnimationFrame(positionPanel);
220
+ }
221
+
222
+ private _setPanelPosition(right: number, bottom: number) {
223
+ this._component.style.right = `${Math.max(0, window.innerWidth - right)}px`;
224
+ this._component.style.bottom = `${Math.max(0, window.innerHeight - bottom)}px`;
225
+ }
226
+
227
+ public resetComponent() {
228
+ this._hide();
229
+ this._component.remove();
230
+ this._initComponent();
231
+ }
232
+
233
+ private _show() {
234
+ if (!this._component.open) {
235
+ this._component.show();
236
+ this._quill.container.ownerDocument.addEventListener('click', (e: MouseEvent) => this._onDocumentClick(e));
237
+ this._quill.container.ownerDocument.addEventListener('keydown', this._onEscapeKey);
238
+ }
239
+ }
240
+
241
+ private _hide() {
242
+ this._component.hide();
243
+ this._quill.container.ownerDocument.removeEventListener('click', (e: MouseEvent) => this._onDocumentClick(e));
244
+ this._quill.container.ownerDocument.removeEventListener('keydown', this._onEscapeKey);
245
+ }
246
+
247
+ private _onEscapeKey = (e: KeyboardEvent) => {
248
+ if (e.key === 'Escape') {
249
+ this._hide();
250
+ this._component.remove();
251
+ this._initComponent();
252
+ }
253
+ }
254
+
255
+ private _attachPanelEvents(panel: AIPanelComponent) {
256
+ panel.refine = this._enterCustomPrompt.bind(this);
257
+ panel.refineBuiltIn = this._clickPreDefinedEvent.bind(this);
258
+ }
259
+
260
+ private _clickPreDefinedEvent(e: Event) {
261
+ if (e instanceof PointerEvent) {
262
+ const target = e.target as HTMLElement;
263
+ this._prompt = target.getAttribute('data-ai-option') || '';
264
+ if (this._prompt === "") {
265
+ for (const targetElement of e.composedPath()) {
266
+ if (!(targetElement instanceof HTMLElement)) continue;
267
+ this._prompt = targetElement.getAttribute('data-ai-option') || ''
268
+ if (this._prompt) break;
269
+ }
270
+ }
271
+ this.processAIRequest().then(r => r);
272
+ }
273
+ }
274
+
275
+ private _enterCustomPrompt(prompt: string) {
276
+ this._prompt = prompt;
277
+ this.processAIRequest().then(r => r);
278
+ }
279
+ }
280
+
281
+ export default QuillAI;
@@ -0,0 +1,102 @@
1
+ import {html, unsafeCSS} from "lit";
2
+ import {property, query} from "lit/decorators.js";
3
+ import ZincElement from "../../../../../internal/zinc-element";
4
+ import type {CSSResultGroup} from "lit";
5
+
6
+ import styles from './ai-panel.scss';
7
+
8
+ export default class AIPanelComponent extends ZincElement {
9
+ static styles: CSSResultGroup = unsafeCSS(styles);
10
+
11
+ @query('.prompt') promptInput!: HTMLTextAreaElement;
12
+
13
+ @property({type: Boolean, reflect: true}) open = false;
14
+ @property({type: Function}) refine?: (prompt: string) => void;
15
+ @property({type: Function}) refineBuiltIn?: (e: Event) => void;
16
+
17
+ public show() {
18
+ this.open = true;
19
+ }
20
+
21
+ public hide() {
22
+ this.open = false;
23
+ }
24
+
25
+ private handleTriggerKeyDown(event: KeyboardEvent) {
26
+ if (event.key === 'Enter') {
27
+ event.preventDefault();
28
+
29
+ const prompt = this.promptInput.value.trim() || '';
30
+
31
+ if (this.refine && prompt) {
32
+ this.refine(prompt);
33
+
34
+ this.promptInput.value = '';
35
+ this.promptInput.blur();
36
+ }
37
+ }
38
+ }
39
+
40
+ render() {
41
+ return html`
42
+ <zn-panel class="ai-panel" flush cosmic>
43
+ <div class="ai-panel__actions">
44
+ <zn-textarea name="prompt"
45
+ class="prompt"
46
+ inputmode="text"
47
+ rows="1"
48
+ resize="auto"
49
+ size="small"
50
+ placeholder="Enter custom prompt"
51
+ @keydown=${this.handleTriggerKeyDown}></zn-textarea>
52
+ <button
53
+ type="button"
54
+ class="item"
55
+ role="option"
56
+ aria-selected="false"
57
+ data-ai-option="improve"
58
+ @click=${this.refineBuiltIn}
59
+ >
60
+ <zn-icon src="article" size="16"></zn-icon>
61
+ <span class="label">Improve writing</span>
62
+ </button>
63
+ <button
64
+ type="button"
65
+ class="item"
66
+ role="option"
67
+ aria-selected="false"
68
+ data-ai-option="shorten"
69
+ @click=${this.refineBuiltIn}
70
+ >
71
+ <zn-icon src="compress" size="16"></zn-icon>
72
+ <span class="label">Shorten text</span>
73
+ </button>
74
+ <button
75
+ type="button"
76
+ class="item"
77
+ role="option"
78
+ aria-selected="false"
79
+ data-ai-option="expand"
80
+ @click=${this.refineBuiltIn}
81
+ >
82
+ <zn-icon src="expand" size="16"></zn-icon>
83
+ <span class="label">Expand text</span>
84
+ </button>
85
+
86
+ <button
87
+ type="button"
88
+ class="item"
89
+ role="option"
90
+ aria-selected="false"
91
+ data-ai-option="spelling"
92
+ @click=${this.refineBuiltIn}
93
+ >
94
+ <zn-icon src="spellcheck" size="16"></zn-icon>
95
+ <span class="label">Fix spelling / grammar</span>
96
+ </button>
97
+ </div>
98
+ </zn-panel>`;
99
+ }
100
+ }
101
+
102
+ AIPanelComponent.define('zn-ai-panel');
@@ -0,0 +1,83 @@
1
+ @use "../../../../../wc";
2
+
3
+ :host {
4
+ position: absolute;
5
+ z-index: var(--zn-z-index-editor-action);
6
+ display: none;
7
+ border-radius: var(--zn-border-radius-circle);
8
+ font-size: var(--zn-font-size-small);
9
+ --zn-blur: 2px;
10
+ }
11
+
12
+ :host([open]) {
13
+ display: block;
14
+ }
15
+
16
+ .ai-panel {
17
+ display: flex;
18
+ flex-grow: 1;
19
+ max-width: 100%;
20
+ position: relative;
21
+ min-width: 240px;
22
+ width: 240px;
23
+ transition: width 0.8s ease, height 0.8s ease;
24
+
25
+ &::part(base) {
26
+ margin-bottom: 0;
27
+ }
28
+
29
+ &__actions {
30
+ display: flex;
31
+ flex-direction: column;
32
+ justify-content: center;
33
+ }
34
+
35
+ .prompt {
36
+ padding: 8px;
37
+ --zn-input-border-color: var(--zn-input-border-color);
38
+ --zn-input-border-color-hover: var(--zn-input-border-color-hover);
39
+ --zn-input-text-color: var(--zn-input-text-color);
40
+ --zn-input-placeholder-color: var(--zn-input-placeholder-color);
41
+ --zn-input-border-radius: var(--zn-border-radius-medium);
42
+ --zn-input-padding: 8px 12px;
43
+ --zn-input-font-size: 14px;
44
+
45
+ &::after {
46
+ content: "";
47
+ display: block;
48
+ width: 60%;
49
+ margin: 4px auto;
50
+ border-bottom: 1px solid rgba(var(--zn-border-color), 0.4);
51
+ }
52
+
53
+ &::part(textarea) {
54
+ border-radius: var(--zn-border-radius-medium);
55
+ }
56
+ }
57
+
58
+ .item {
59
+ display: flex;
60
+ align-items: center;
61
+ gap: 4px;
62
+ padding: 8px;
63
+ width: 100%;
64
+ border: none;
65
+ background: transparent;
66
+ cursor: pointer;
67
+ font: inherit;
68
+
69
+ &[aria-selected="true"],
70
+ &:hover {
71
+ background: var(--zn-input-background-color-hover);
72
+ }
73
+ }
74
+
75
+ .label {
76
+ opacity: 0.7;
77
+ }
78
+
79
+ .empty {
80
+ padding: 8px;
81
+ opacity: 0.7;
82
+ }
83
+ }
@@ -0,0 +1,30 @@
1
+ import {html, unsafeCSS} from "lit";
2
+ import {property} from "lit/decorators.js";
3
+ import ZincElement from "../../../../../internal/zinc-element";
4
+ import type {CSSResultGroup} from "lit";
5
+
6
+ import styles from './ai-tooltip.scss';
7
+
8
+ export default class AITooltipComponent extends ZincElement {
9
+ static styles: CSSResultGroup = unsafeCSS(styles);
10
+
11
+ @property({type: Boolean, reflect: true}) open = false;
12
+
13
+ public show() {
14
+ this.open = true;
15
+ }
16
+
17
+ public hide() {
18
+ this.open = false;
19
+ }
20
+
21
+ render() {
22
+ return html`
23
+ <button type="button" class="ai-tooltip">
24
+ <zn-icon src="stylus" size="18"></zn-icon>
25
+ <span class="label">Refine</span>
26
+ </button>`;
27
+ }
28
+ }
29
+
30
+ AITooltipComponent.define('zn-ai-tooltip');
@@ -0,0 +1,28 @@
1
+ :host {
2
+ position: absolute;
3
+ z-index: var(--zn-z-index-editor-action);
4
+ display: none;
5
+ font-size: var(--zn-font-size-small);
6
+ }
7
+
8
+ :host([open]) {
9
+ display: block;
10
+ }
11
+
12
+ .ai-tooltip {
13
+ display: flex;
14
+ align-items: center;
15
+ gap: 4px;
16
+ width: 100%;
17
+ padding: 8px 10px;
18
+ cursor: pointer;
19
+ background: var(--zn-panel-background-color);
20
+ border: 1px solid var(--zn-border-color);
21
+ border-radius: var(--zn-border-radius-large);
22
+ box-shadow: var(--zn-shadow-small);
23
+
24
+ &[aria-selected="true"],
25
+ &:hover {
26
+ background: var(--zn-input-background-color-hover);
27
+ }
28
+ }
@@ -1,7 +1,7 @@
1
- import Quill from 'quill';
2
- import Toolbar from "quill/modules/toolbar";
1
+ import type Quill from 'quill';
2
+ import type Toolbar from "../toolbar/toolbar";
3
3
 
4
- type AttachmentModuleOptions = {
4
+ interface AttachmentOptions {
5
5
  upload: (file: File) => Promise<{ path: any, url: any, filename: any }>;
6
6
  onFileUploaded?: (node: HTMLElement, {url}: { url: string }) => void;
7
7
  attachmentInput?: HTMLInputElement;
@@ -13,13 +13,13 @@ const generateId = () => {
13
13
  return `${name}_${id}`;
14
14
  };
15
15
 
16
- export default class AttachmentModule {
16
+ export default class Attachment {
17
17
  private _quill: Quill;
18
- private _options: AttachmentModuleOptions;
18
+ private _options: AttachmentOptions;
19
19
 
20
20
  private _fileHolder: HTMLInputElement | null;
21
21
 
22
- constructor(quill: Quill, options: AttachmentModuleOptions) {
22
+ constructor(quill: Quill, options: AttachmentOptions) {
23
23
  this._quill = quill;
24
24
  this._options = options;
25
25
 
@@ -43,7 +43,7 @@ export default class AttachmentModule {
43
43
  }
44
44
 
45
45
  private _fileChanged() {
46
- if (!this._fileHolder || !this._fileHolder.files || this._fileHolder.files.length < 0) {
46
+ if (!this._fileHolder?.files || this._fileHolder.files.length < 0) {
47
47
  return;
48
48
  }
49
49
 
@@ -0,0 +1,115 @@
1
+ import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
2
+ import {property, state} from 'lit/decorators.js';
3
+ import ZincElement from "../../../../internal/zinc-element";
4
+
5
+ import styles from './context-menu.scss';
6
+
7
+ export interface ResultItem {
8
+ icon: string;
9
+ label: string;
10
+ format?: string;
11
+ key?: string;
12
+ value?: string | boolean;
13
+ }
14
+
15
+ export default class ContextMenuComponent extends ZincElement {
16
+ static styles: CSSResultGroup = unsafeCSS(styles);
17
+
18
+ @property({type: Boolean, reflect: true}) open = false;
19
+ @property({type: String}) query = '';
20
+ @property({type: Array}) results: ResultItem[] = [];
21
+
22
+ @state() private _activeIndex = -1;
23
+
24
+ public show() {
25
+ this.open = true;
26
+ }
27
+
28
+ public hide() {
29
+ this.open = false;
30
+ this._activeIndex = -1;
31
+ }
32
+
33
+ setPosition(left: number, top: number) {
34
+ this.style.left = `${Math.max(0, left)}px`;
35
+ this.style.top = `${top}px`;
36
+ }
37
+
38
+ setActiveIndex(index: number) {
39
+ const len = this.results?.length ?? 0;
40
+ if (!len) {
41
+ this._activeIndex = -1;
42
+ return;
43
+ }
44
+
45
+ if (index < 0) {
46
+ index = len - 1;
47
+ }
48
+ if (index >= len) {
49
+ index = 0;
50
+ }
51
+ this._activeIndex = index;
52
+ this.requestUpdate();
53
+
54
+ requestAnimationFrame(() => {
55
+ const items = Array.from(this.renderRoot.querySelectorAll<HTMLButtonElement>('[data-toolbar-option]'));
56
+ const active = items[this._activeIndex];
57
+ active?.scrollIntoView?.({block: 'nearest'});
58
+ });
59
+ }
60
+
61
+ getActiveIndex() {
62
+ return this._activeIndex;
63
+ }
64
+
65
+ private _onClickItem = (e: MouseEvent) => {
66
+ const target = e.currentTarget as HTMLElement | null;
67
+ if (!target) return;
68
+
69
+ const idx = parseInt(target.dataset.index || '-1', 10);
70
+ if (Number.isNaN(idx)) return;
71
+
72
+ this.setActiveIndex(idx);
73
+
74
+ const item = this.results?.[idx];
75
+ if (!item) return;
76
+
77
+ this.dispatchEvent(new CustomEvent('zn-format-select', {
78
+ bubbles: true,
79
+ composed: true,
80
+ detail: {icon: item.icon, label: item.label, format: item.format, value: item.value, key: item.key}
81
+ }));
82
+ }
83
+
84
+ protected willUpdate(changed: PropertyValues) {
85
+ if (changed.has('results')) {
86
+ // Reset active index when results change
87
+ this._activeIndex = this.results?.length ? 0 : -1;
88
+ }
89
+ }
90
+
91
+ render() {
92
+ return html`
93
+ <div class="header">${this.query ? `Search: ${this.query}` : 'Options'}</div>
94
+ ${Array.isArray(this.results) && this.results.length > 0 ? (
95
+ this.results.slice(0, 20).map((res, i) => html`
96
+ <button
97
+ type="button"
98
+ class="item"
99
+ role="option"
100
+ aria-selected="${String(i === this._activeIndex)}"
101
+ data-toolbar-option
102
+ data-index="${i}"
103
+ @click="${this._onClickItem}"
104
+ >
105
+ <zn-icon src="${res.icon}" size="16"></zn-icon>
106
+ <span class="label">${res.label}</span>
107
+ </button>
108
+ `)
109
+ ) : html`
110
+ <div class="empty">${this.query ? 'No results' : 'Type something'}</div>`}
111
+ `;
112
+ }
113
+ }
114
+
115
+ ContextMenuComponent.define('zn-context-menu');