@liwe3/webcomponents 1.1.0 → 1.1.10

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 (56) hide show
  1. package/dist/AIMarkdownEditor.d.ts +35 -0
  2. package/dist/AIMarkdownEditor.d.ts.map +1 -0
  3. package/dist/AIMarkdownEditor.js +412 -0
  4. package/dist/AIMarkdownEditor.js.map +1 -0
  5. package/dist/AITextEditor.d.ts +10 -0
  6. package/dist/AITextEditor.d.ts.map +1 -1
  7. package/dist/AITextEditor.js +63 -27
  8. package/dist/AITextEditor.js.map +1 -1
  9. package/dist/ButtonToolbar.d.ts +35 -0
  10. package/dist/ButtonToolbar.d.ts.map +1 -0
  11. package/dist/ButtonToolbar.js +220 -0
  12. package/dist/ButtonToolbar.js.map +1 -0
  13. package/dist/CheckList.d.ts +31 -0
  14. package/dist/CheckList.d.ts.map +1 -0
  15. package/dist/CheckList.js +336 -0
  16. package/dist/CheckList.js.map +1 -0
  17. package/dist/ChunkUploader.d.ts +22 -0
  18. package/dist/ChunkUploader.d.ts.map +1 -1
  19. package/dist/ChunkUploader.js +245 -103
  20. package/dist/ChunkUploader.js.map +1 -1
  21. package/dist/ComicBalloon.d.ts +82 -0
  22. package/dist/ComicBalloon.d.ts.map +1 -0
  23. package/dist/ComicBalloon.js +346 -0
  24. package/dist/ComicBalloon.js.map +1 -0
  25. package/dist/Dialog.d.ts +102 -0
  26. package/dist/Dialog.d.ts.map +1 -0
  27. package/dist/Dialog.js +299 -0
  28. package/dist/Dialog.js.map +1 -0
  29. package/dist/MarkdownPreview.d.ts +25 -0
  30. package/dist/MarkdownPreview.d.ts.map +1 -0
  31. package/dist/MarkdownPreview.js +147 -0
  32. package/dist/MarkdownPreview.js.map +1 -0
  33. package/dist/ResizableCropper.d.ts +158 -0
  34. package/dist/ResizableCropper.d.ts.map +1 -0
  35. package/dist/ResizableCropper.js +562 -0
  36. package/dist/ResizableCropper.js.map +1 -0
  37. package/dist/SmartSelect.d.ts +1 -0
  38. package/dist/SmartSelect.d.ts.map +1 -1
  39. package/dist/SmartSelect.js +45 -2
  40. package/dist/SmartSelect.js.map +1 -1
  41. package/dist/index.d.ts +16 -9
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +52 -29
  44. package/dist/index.js.map +1 -1
  45. package/package.json +33 -3
  46. package/src/AIMarkdownEditor.ts +568 -0
  47. package/src/AITextEditor.ts +97 -2
  48. package/src/ButtonToolbar.ts +302 -0
  49. package/src/CheckList.ts +438 -0
  50. package/src/ChunkUploader.ts +837 -623
  51. package/src/ComicBalloon.ts +709 -0
  52. package/src/Dialog.ts +510 -0
  53. package/src/MarkdownPreview.ts +213 -0
  54. package/src/ResizableCropper.ts +1099 -0
  55. package/src/SmartSelect.ts +48 -2
  56. package/src/index.ts +110 -47
@@ -7,11 +7,14 @@ const AI_TEXT_EDITOR_API_KEY = 'ai-text-editor-api-key';
7
7
 
8
8
  export interface AITextEditorConfig {
9
9
  apiKey?: string;
10
- suggestionDelay?: number;
10
+ suggestionDelay?: number; // Delay in seconds before showing AI suggestions
11
11
  systemPrompt?: string;
12
12
  apiEndpoint?: string;
13
13
  modelName?: string;
14
14
  context?: string;
15
+ embedded?: boolean;
16
+ onStatusChange?: (hasApiKey: boolean) => void;
17
+ onLoadingChange?: (isLoading: boolean) => void;
15
18
  }
16
19
 
17
20
  export class AITextEditorElement extends HTMLElement {
@@ -28,11 +31,15 @@ export class AITextEditorElement extends HTMLElement {
28
31
  private isShowingSuggestion: boolean = false;
29
32
 
30
33
  private apiKey: string = '';
31
- private suggestionDelay: number = 1000;
34
+ private suggestionDelay: number = 1000; // Stored in milliseconds (default: 1000ms = 1 second)
32
35
  private systemPrompt: string = "You are a helpful writing assistant. Continue the user's text naturally and coherently. Provide 1-3 sentences that would logically follow their writing. Keep the same tone and style. Do not repeat what they've already written.";
33
36
  private apiEndpoint: string = 'https://api.openai.com/v1/chat/completions';
34
37
  private modelName: string = 'gpt-3.5-turbo';
35
38
  private context: string = '';
39
+
40
+ private embedded: boolean = false;
41
+ private onStatusChangeCallback?: (hasApiKey: boolean) => void;
42
+ private onLoadingChangeCallback?: (isLoading: boolean) => void;
36
43
 
37
44
  constructor() {
38
45
  super();
@@ -70,6 +77,10 @@ export class AITextEditorElement extends HTMLElement {
70
77
  background: #777;
71
78
  z-index: 10;
72
79
  }
80
+
81
+ :host([embedded]) .editor-status {
82
+ display: none;
83
+ }
73
84
 
74
85
  .editor-wrapper {
75
86
  position: relative;
@@ -97,12 +108,21 @@ export class AITextEditorElement extends HTMLElement {
97
108
  box-sizing: border-box;
98
109
  min-height: auto;
99
110
  }
111
+
112
+ :host([embedded]) .editor {
113
+ border: none;
114
+ border-radius: 0;
115
+ }
100
116
 
101
117
  .editor:focus {
102
118
  outline: none;
103
119
  border-color: #4facfe;
104
120
  box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
105
121
  }
122
+
123
+ :host([embedded]) .editor:focus {
124
+ box-shadow: none;
125
+ }
106
126
 
107
127
  .editor-background {
108
128
  position: absolute;
@@ -125,11 +145,20 @@ export class AITextEditorElement extends HTMLElement {
125
145
  color: transparent;
126
146
  box-sizing: border-box;
127
147
  }
148
+
149
+ :host([embedded]) .editor-background {
150
+ border: none;
151
+ border-radius: 0;
152
+ }
128
153
 
129
154
  .editor-wrapper:focus-within .editor-background {
130
155
  background: white;
131
156
  border-color: #4facfe;
132
157
  }
158
+
159
+ :host([embedded]) .editor-wrapper:focus-within .editor-background {
160
+ border-color: transparent;
161
+ }
133
162
 
134
163
  .suggestion-text {
135
164
  color: #bbb;
@@ -148,6 +177,10 @@ export class AITextEditorElement extends HTMLElement {
148
177
  z-index: 10;
149
178
  display: none;
150
179
  }
180
+
181
+ :host([embedded]) .loading {
182
+ display: none !important;
183
+ }
151
184
 
152
185
  .loading.show {
153
186
  display: block;
@@ -357,6 +390,11 @@ export class AITextEditorElement extends HTMLElement {
357
390
  } catch (error) {
358
391
  this.hideLoading();
359
392
  this.showError('Failed to get AI suggestion: ' + (error as Error).message);
393
+ this.dispatchEvent(new CustomEvent('oncompletionerror', {
394
+ detail: { error: (error as Error).message },
395
+ bubbles: true,
396
+ composed: true
397
+ }));
360
398
  }
361
399
  }
362
400
 
@@ -488,6 +526,9 @@ export class AITextEditorElement extends HTMLElement {
488
526
  */
489
527
  private showLoading(): void {
490
528
  this.loading.classList.add('show');
529
+ if (this.onLoadingChangeCallback) {
530
+ this.onLoadingChangeCallback(true);
531
+ }
491
532
  }
492
533
 
493
534
  /**
@@ -495,6 +536,9 @@ export class AITextEditorElement extends HTMLElement {
495
536
  */
496
537
  private hideLoading(): void {
497
538
  this.loading.classList.remove('show');
539
+ if (this.onLoadingChangeCallback) {
540
+ this.onLoadingChangeCallback(false);
541
+ }
498
542
  }
499
543
 
500
544
  /**
@@ -552,6 +596,9 @@ export class AITextEditorElement extends HTMLElement {
552
596
  this.apiKey = key;
553
597
  this._saveApiKey();
554
598
  this.editorStatus.style.backgroundColor = this.apiKey ? '#4caf50' : '#777';
599
+ if (this.onStatusChangeCallback) {
600
+ this.onStatusChangeCallback(!!this.apiKey);
601
+ }
555
602
  }
556
603
 
557
604
  /**
@@ -661,6 +708,54 @@ export class AITextEditorElement extends HTMLElement {
661
708
  return this.context;
662
709
  }
663
710
 
711
+ /**
712
+ * Configure the editor with callbacks and embedded mode
713
+ */
714
+ configure(config: Partial<AITextEditorConfig>): void {
715
+ if (config.embedded !== undefined) {
716
+ this.embedded = config.embedded;
717
+ if (this.embedded) {
718
+ this.setAttribute('embedded', '');
719
+ } else {
720
+ this.removeAttribute('embedded');
721
+ }
722
+ }
723
+
724
+ if (config.onStatusChange) {
725
+ this.onStatusChangeCallback = config.onStatusChange;
726
+ // Immediately call with current status
727
+ this.onStatusChangeCallback(!!this.apiKey);
728
+ }
729
+
730
+ if (config.onLoadingChange) {
731
+ this.onLoadingChangeCallback = config.onLoadingChange;
732
+ }
733
+
734
+ if (config.apiKey !== undefined) {
735
+ this.setApiKey(config.apiKey);
736
+ }
737
+
738
+ if (config.suggestionDelay !== undefined) {
739
+ this.setSuggestionDelay(config.suggestionDelay);
740
+ }
741
+
742
+ if (config.systemPrompt !== undefined) {
743
+ this.setSystemPrompt(config.systemPrompt);
744
+ }
745
+
746
+ if (config.apiEndpoint !== undefined) {
747
+ this.setApiEndpoint(config.apiEndpoint);
748
+ }
749
+
750
+ if (config.modelName !== undefined) {
751
+ this.setModelName(config.modelName);
752
+ }
753
+
754
+ if (config.context !== undefined) {
755
+ this.setContext(config.context);
756
+ }
757
+ }
758
+
664
759
  /**
665
760
  * Saves settings to localStorage
666
761
  */
@@ -0,0 +1,302 @@
1
+ /**
2
+ * ButtonToolbar Web Component
3
+ * A customizable toolbar with groups of buttons, supporting horizontal/vertical orientation
4
+ */
5
+
6
+ export type ButtonToolbarItem = {
7
+ id: string;
8
+ label?: string;
9
+ icon?: string; // SVG content or icon class
10
+ image?: string; // Image URL
11
+ type?: 'default' | 'info' | 'error' | 'warn' | 'success';
12
+ disabled?: boolean;
13
+ tooltip?: string;
14
+ action?: string; // Optional action name to dispatch
15
+ };
16
+
17
+ export type ButtonToolbarGroup = {
18
+ id?: string;
19
+ items: ButtonToolbarItem[];
20
+ class?: string; // Optional CSS class for the group
21
+ };
22
+
23
+ export class ButtonToolbarElement extends HTMLElement {
24
+ declare shadowRoot: ShadowRoot;
25
+ private _groups: ButtonToolbarGroup[] = [];
26
+
27
+ constructor() {
28
+ super();
29
+ this.attachShadow({ mode: 'open' });
30
+ }
31
+
32
+ static get observedAttributes(): string[] {
33
+ return ['orientation', 'groups'];
34
+ }
35
+
36
+ attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void {
37
+ if (oldValue !== newValue) {
38
+ this.render();
39
+ }
40
+ }
41
+
42
+ get orientation(): 'horizontal' | 'vertical' {
43
+ return (this.getAttribute('orientation') as 'horizontal' | 'vertical') || 'horizontal';
44
+ }
45
+
46
+ set orientation(value: 'horizontal' | 'vertical') {
47
+ this.setAttribute('orientation', value);
48
+ }
49
+
50
+ get groups(): ButtonToolbarGroup[] {
51
+ const groupsAttr = this.getAttribute('groups');
52
+ if (groupsAttr) {
53
+ try {
54
+ return JSON.parse(groupsAttr);
55
+ } catch (e) {
56
+ console.error('Invalid groups format:', e);
57
+ return [];
58
+ }
59
+ }
60
+ return this._groups;
61
+ }
62
+
63
+ set groups(value: ButtonToolbarGroup[]) {
64
+ this._groups = value;
65
+ // Also update attribute for consistency, but be careful with large data
66
+ // For now, let's just render. If we want to sync property to attribute:
67
+ // this.setAttribute('groups', JSON.stringify(value));
68
+ // But usually for complex data, property is preferred source of truth if set directly.
69
+
70
+ // SmartSelect implementation:
71
+ // set options ( opts: SelectOption[] ) {
72
+ // this.setAttribute( 'options', JSON.stringify( opts ) );
73
+ // }
74
+
75
+ // I'll follow SmartSelect pattern for consistency
76
+ this.setAttribute('groups', JSON.stringify(value));
77
+ }
78
+
79
+ connectedCallback(): void {
80
+ this.render();
81
+ }
82
+
83
+ private handleButtonClick(item: ButtonToolbarItem, event: Event): void {
84
+ if (item.disabled) return;
85
+
86
+ this.dispatchEvent(new CustomEvent('button-click', {
87
+ detail: {
88
+ id: item.id,
89
+ action: item.action || item.id,
90
+ originalEvent: event,
91
+ item
92
+ },
93
+ bubbles: true,
94
+ composed: true
95
+ }));
96
+ }
97
+
98
+ private render(): void {
99
+ if (!this.shadowRoot) return;
100
+
101
+ const style = `
102
+ :host {
103
+ display: block;
104
+ font-family: var(--liwe3-font-family, system-ui, -apple-system, sans-serif);
105
+ }
106
+
107
+ .toolbar {
108
+ display: flex;
109
+ gap: var(--liwe3-toolbar-gap, 0.5rem);
110
+ width: 100%;
111
+ box-sizing: border-box;
112
+ }
113
+
114
+ .toolbar.horizontal {
115
+ flex-direction: row;
116
+ align-items: center;
117
+ }
118
+
119
+ .toolbar.vertical {
120
+ flex-direction: column;
121
+ align-items: stretch;
122
+ }
123
+
124
+ .group {
125
+ display: flex;
126
+ gap: 1px; /* Gap between buttons in a group */
127
+ background-color: var(--liwe3-toolbar-group-bg, transparent);
128
+ border-radius: var(--liwe3-toolbar-radius, 0.375rem);
129
+ overflow: hidden;
130
+ }
131
+
132
+ .toolbar.horizontal .group {
133
+ flex-direction: row;
134
+ }
135
+
136
+ .toolbar.vertical .group {
137
+ flex-direction: column;
138
+ }
139
+
140
+ .button {
141
+ display: inline-flex;
142
+ align-items: center;
143
+ justify-content: center;
144
+ gap: 0.5rem;
145
+ padding: var(--liwe3-button-padding, 0.5rem 1rem);
146
+ border: none;
147
+ cursor: pointer;
148
+ font-size: var(--liwe3-button-font-size, 0.875rem);
149
+ line-height: 1.25;
150
+ transition: all 0.2s;
151
+ background-color: var(--liwe3-button-bg, #f3f4f6);
152
+ color: var(--liwe3-button-color, #1f2937);
153
+ min-height: var(--liwe3-button-height, 2.5rem);
154
+ }
155
+
156
+ .button:hover:not(:disabled) {
157
+ filter: brightness(0.95);
158
+ }
159
+
160
+ .button:active:not(:disabled) {
161
+ filter: brightness(0.9);
162
+ }
163
+
164
+ .button:disabled {
165
+ opacity: 0.5;
166
+ cursor: not-allowed;
167
+ }
168
+
169
+ /* Button Types */
170
+ .button.default {
171
+ background-color: var(--liwe3-button-default-bg, #f3f4f6);
172
+ color: var(--liwe3-button-default-color, #1f2937);
173
+ }
174
+
175
+ .button.info {
176
+ background-color: var(--liwe3-button-info-bg, #3b82f6);
177
+ color: var(--liwe3-button-info-color, #ffffff);
178
+ }
179
+
180
+ .button.error {
181
+ background-color: var(--liwe3-button-error-bg, #ef4444);
182
+ color: var(--liwe3-button-error-color, #ffffff);
183
+ }
184
+
185
+ .button.warn {
186
+ background-color: var(--liwe3-button-warn-bg, #f59e0b);
187
+ color: var(--liwe3-button-warn-color, #ffffff);
188
+ }
189
+
190
+ .button.success {
191
+ background-color: var(--liwe3-button-success-bg, #10b981);
192
+ color: var(--liwe3-button-success-color, #ffffff);
193
+ }
194
+
195
+ /* Content styling */
196
+ .icon {
197
+ width: 1.25em;
198
+ height: 1.25em;
199
+ display: flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+ }
203
+
204
+ .icon svg {
205
+ width: 100%;
206
+ height: 100%;
207
+ fill: currentColor;
208
+ }
209
+
210
+ .image {
211
+ width: 1.5em;
212
+ height: 1.5em;
213
+ object-fit: cover;
214
+ border-radius: 50%;
215
+ }
216
+
217
+ /* Group styling - rounded corners logic */
218
+ .toolbar.horizontal .group .button:first-child {
219
+ border-top-left-radius: var(--liwe3-toolbar-radius, 0.375rem);
220
+ border-bottom-left-radius: var(--liwe3-toolbar-radius, 0.375rem);
221
+ }
222
+
223
+ .toolbar.horizontal .group .button:last-child {
224
+ border-top-right-radius: var(--liwe3-toolbar-radius, 0.375rem);
225
+ border-bottom-right-radius: var(--liwe3-toolbar-radius, 0.375rem);
226
+ }
227
+
228
+ .toolbar.vertical .group .button:first-child {
229
+ border-top-left-radius: var(--liwe3-toolbar-radius, 0.375rem);
230
+ border-top-right-radius: var(--liwe3-toolbar-radius, 0.375rem);
231
+ }
232
+
233
+ .toolbar.vertical .group .button:last-child {
234
+ border-bottom-left-radius: var(--liwe3-toolbar-radius, 0.375rem);
235
+ border-bottom-right-radius: var(--liwe3-toolbar-radius, 0.375rem);
236
+ }
237
+ `;
238
+
239
+ const renderButton = (item: ButtonToolbarItem) => {
240
+ const btn = document.createElement('button');
241
+ btn.className = `button ${item.type || 'default'}`;
242
+ if (item.disabled) btn.disabled = true;
243
+ if (item.tooltip) btn.title = item.tooltip;
244
+
245
+ btn.onclick = (e) => this.handleButtonClick(item, e);
246
+
247
+ if (item.icon) {
248
+ const iconSpan = document.createElement('span');
249
+ iconSpan.className = 'icon';
250
+ // Check if it's an SVG string or just a class name (simple heuristic)
251
+ if (item.icon.trim().startsWith('<')) {
252
+ iconSpan.innerHTML = item.icon;
253
+ } else {
254
+ // Assuming it might be a class name if not SVG
255
+ const i = document.createElement('i');
256
+ i.className = item.icon;
257
+ iconSpan.appendChild(i);
258
+ }
259
+ btn.appendChild(iconSpan);
260
+ }
261
+
262
+ if (item.image) {
263
+ const img = document.createElement('img');
264
+ img.src = item.image;
265
+ img.className = 'image';
266
+ img.alt = item.label || '';
267
+ btn.appendChild(img);
268
+ }
269
+
270
+ if (item.label) {
271
+ const span = document.createElement('span');
272
+ span.textContent = item.label;
273
+ btn.appendChild(span);
274
+ }
275
+
276
+ return btn;
277
+ };
278
+
279
+ const container = document.createElement('div');
280
+ container.className = `toolbar ${this.orientation}`;
281
+
282
+ this._groups.forEach(group => {
283
+ const groupDiv = document.createElement('div');
284
+ groupDiv.className = `group ${group.class || ''}`;
285
+
286
+ group.items.forEach(item => {
287
+ groupDiv.appendChild(renderButton(item));
288
+ });
289
+
290
+ container.appendChild(groupDiv);
291
+ });
292
+
293
+ this.shadowRoot.innerHTML = `<style>${style}</style>`;
294
+ this.shadowRoot.appendChild(container);
295
+ }
296
+ }
297
+
298
+ export const defineButtonToolbar = (): void => {
299
+ if (typeof window !== 'undefined' && !customElements.get('liwe3-button-toolbar')) {
300
+ customElements.define('liwe3-button-toolbar', ButtonToolbarElement);
301
+ }
302
+ };