@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.
- package/dist/AIMarkdownEditor.d.ts +35 -0
- package/dist/AIMarkdownEditor.d.ts.map +1 -0
- package/dist/AIMarkdownEditor.js +412 -0
- package/dist/AIMarkdownEditor.js.map +1 -0
- package/dist/AITextEditor.d.ts +10 -0
- package/dist/AITextEditor.d.ts.map +1 -1
- package/dist/AITextEditor.js +63 -27
- package/dist/AITextEditor.js.map +1 -1
- package/dist/ButtonToolbar.d.ts +35 -0
- package/dist/ButtonToolbar.d.ts.map +1 -0
- package/dist/ButtonToolbar.js +220 -0
- package/dist/ButtonToolbar.js.map +1 -0
- package/dist/CheckList.d.ts +31 -0
- package/dist/CheckList.d.ts.map +1 -0
- package/dist/CheckList.js +336 -0
- package/dist/CheckList.js.map +1 -0
- package/dist/ChunkUploader.d.ts +22 -0
- package/dist/ChunkUploader.d.ts.map +1 -1
- package/dist/ChunkUploader.js +245 -103
- package/dist/ChunkUploader.js.map +1 -1
- package/dist/ComicBalloon.d.ts +82 -0
- package/dist/ComicBalloon.d.ts.map +1 -0
- package/dist/ComicBalloon.js +346 -0
- package/dist/ComicBalloon.js.map +1 -0
- package/dist/Dialog.d.ts +102 -0
- package/dist/Dialog.d.ts.map +1 -0
- package/dist/Dialog.js +299 -0
- package/dist/Dialog.js.map +1 -0
- package/dist/MarkdownPreview.d.ts +25 -0
- package/dist/MarkdownPreview.d.ts.map +1 -0
- package/dist/MarkdownPreview.js +147 -0
- package/dist/MarkdownPreview.js.map +1 -0
- package/dist/ResizableCropper.d.ts +158 -0
- package/dist/ResizableCropper.d.ts.map +1 -0
- package/dist/ResizableCropper.js +562 -0
- package/dist/ResizableCropper.js.map +1 -0
- package/dist/SmartSelect.d.ts +1 -0
- package/dist/SmartSelect.d.ts.map +1 -1
- package/dist/SmartSelect.js +45 -2
- package/dist/SmartSelect.js.map +1 -1
- package/dist/index.d.ts +16 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +52 -29
- package/dist/index.js.map +1 -1
- package/package.json +33 -3
- package/src/AIMarkdownEditor.ts +568 -0
- package/src/AITextEditor.ts +97 -2
- package/src/ButtonToolbar.ts +302 -0
- package/src/CheckList.ts +438 -0
- package/src/ChunkUploader.ts +837 -623
- package/src/ComicBalloon.ts +709 -0
- package/src/Dialog.ts +510 -0
- package/src/MarkdownPreview.ts +213 -0
- package/src/ResizableCropper.ts +1099 -0
- package/src/SmartSelect.ts +48 -2
- package/src/index.ts +110 -47
package/dist/AITextEditor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const
|
|
2
|
-
class
|
|
1
|
+
const l = "ai-text-editor-api-key";
|
|
2
|
+
class g extends HTMLElement {
|
|
3
3
|
constructor() {
|
|
4
|
-
super(), this.typingTimer = null, this.fullSuggestion = null, this.suggestionParagraphs = [], this.currentParagraphIndex = 0, this.isShowingSuggestion = !1, this.apiKey = "", this.suggestionDelay = 1e3, this.systemPrompt = "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.", this.apiEndpoint = "https://api.openai.com/v1/chat/completions", this.modelName = "gpt-3.5-turbo", this.context = "", this.attachShadow({ mode: "open" }), this.render(), this.init();
|
|
4
|
+
super(), this.typingTimer = null, this.fullSuggestion = null, this.suggestionParagraphs = [], this.currentParagraphIndex = 0, this.isShowingSuggestion = !1, this.apiKey = "", this.suggestionDelay = 1e3, this.systemPrompt = "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.", this.apiEndpoint = "https://api.openai.com/v1/chat/completions", this.modelName = "gpt-3.5-turbo", this.context = "", this.embedded = !1, this.attachShadow({ mode: "open" }), this.render(), this.init();
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Renders the component's HTML structure
|
|
@@ -32,6 +32,10 @@ class l extends HTMLElement {
|
|
|
32
32
|
background: #777;
|
|
33
33
|
z-index: 10;
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
:host([embedded]) .editor-status {
|
|
37
|
+
display: none;
|
|
38
|
+
}
|
|
35
39
|
|
|
36
40
|
.editor-wrapper {
|
|
37
41
|
position: relative;
|
|
@@ -59,12 +63,21 @@ class l extends HTMLElement {
|
|
|
59
63
|
box-sizing: border-box;
|
|
60
64
|
min-height: auto;
|
|
61
65
|
}
|
|
66
|
+
|
|
67
|
+
:host([embedded]) .editor {
|
|
68
|
+
border: none;
|
|
69
|
+
border-radius: 0;
|
|
70
|
+
}
|
|
62
71
|
|
|
63
72
|
.editor:focus {
|
|
64
73
|
outline: none;
|
|
65
74
|
border-color: #4facfe;
|
|
66
75
|
box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
|
|
67
76
|
}
|
|
77
|
+
|
|
78
|
+
:host([embedded]) .editor:focus {
|
|
79
|
+
box-shadow: none;
|
|
80
|
+
}
|
|
68
81
|
|
|
69
82
|
.editor-background {
|
|
70
83
|
position: absolute;
|
|
@@ -87,11 +100,20 @@ class l extends HTMLElement {
|
|
|
87
100
|
color: transparent;
|
|
88
101
|
box-sizing: border-box;
|
|
89
102
|
}
|
|
103
|
+
|
|
104
|
+
:host([embedded]) .editor-background {
|
|
105
|
+
border: none;
|
|
106
|
+
border-radius: 0;
|
|
107
|
+
}
|
|
90
108
|
|
|
91
109
|
.editor-wrapper:focus-within .editor-background {
|
|
92
110
|
background: white;
|
|
93
111
|
border-color: #4facfe;
|
|
94
112
|
}
|
|
113
|
+
|
|
114
|
+
:host([embedded]) .editor-wrapper:focus-within .editor-background {
|
|
115
|
+
border-color: transparent;
|
|
116
|
+
}
|
|
95
117
|
|
|
96
118
|
.suggestion-text {
|
|
97
119
|
color: #bbb;
|
|
@@ -110,6 +132,10 @@ class l extends HTMLElement {
|
|
|
110
132
|
z-index: 10;
|
|
111
133
|
display: none;
|
|
112
134
|
}
|
|
135
|
+
|
|
136
|
+
:host([embedded]) .loading {
|
|
137
|
+
display: none !important;
|
|
138
|
+
}
|
|
113
139
|
|
|
114
140
|
.loading.show {
|
|
115
141
|
display: block;
|
|
@@ -173,8 +199,8 @@ class l extends HTMLElement {
|
|
|
173
199
|
if (this.isShowingSuggestion && this.fullSuggestion) {
|
|
174
200
|
const s = t.substring(0, e), o = t.substring(e), a = (Array.isArray(this.suggestionParagraphs) ? this.suggestionParagraphs.slice(this.currentParagraphIndex) : []).join(" ");
|
|
175
201
|
if (a.trim().length > 0) {
|
|
176
|
-
const
|
|
177
|
-
i = this.escapeHtml(s).replace(/\n/g, "<br>") +
|
|
202
|
+
const r = s.endsWith(" ") || s === "" || a.startsWith(" ") || a === "" ? "" : " ", d = `<span class="suggestion-text">${this.escapeHtml(a)}</span>`;
|
|
203
|
+
i = this.escapeHtml(s).replace(/\n/g, "<br>") + r + d + this.escapeHtml(o).replace(/\n/g, "<br>");
|
|
178
204
|
} else
|
|
179
205
|
i = this.escapeHtml(t).replace(/\n/g, "<br>");
|
|
180
206
|
} else
|
|
@@ -224,7 +250,11 @@ class l extends HTMLElement {
|
|
|
224
250
|
const o = await this.callOpenAI(i);
|
|
225
251
|
this.hideLoading(), o && this.showSuggestion(o);
|
|
226
252
|
} catch (o) {
|
|
227
|
-
this.hideLoading(), this.showError("Failed to get AI suggestion: " + o.message)
|
|
253
|
+
this.hideLoading(), this.showError("Failed to get AI suggestion: " + o.message), this.dispatchEvent(new CustomEvent("oncompletionerror", {
|
|
254
|
+
detail: { error: o.message },
|
|
255
|
+
bubbles: !0,
|
|
256
|
+
composed: !0
|
|
257
|
+
}));
|
|
228
258
|
}
|
|
229
259
|
}
|
|
230
260
|
}
|
|
@@ -257,21 +287,21 @@ ${t}`);
|
|
|
257
287
|
],
|
|
258
288
|
max_tokens: 150,
|
|
259
289
|
temperature: 0.7
|
|
260
|
-
},
|
|
290
|
+
}, n = await fetch(this.apiEndpoint, {
|
|
261
291
|
method: "POST",
|
|
262
292
|
headers: s,
|
|
263
293
|
body: JSON.stringify(o)
|
|
264
294
|
});
|
|
265
|
-
if (!
|
|
266
|
-
let
|
|
295
|
+
if (!n.ok) {
|
|
296
|
+
let r = "API request failed";
|
|
267
297
|
try {
|
|
268
|
-
|
|
298
|
+
r = (await n.json()).error?.message || r;
|
|
269
299
|
} catch (d) {
|
|
270
|
-
console.error("Failed to parse error response:", d),
|
|
300
|
+
console.error("Failed to parse error response:", d), r = `HTTP ${n.status}: ${n.statusText}`;
|
|
271
301
|
}
|
|
272
|
-
throw new Error(
|
|
302
|
+
throw new Error(r);
|
|
273
303
|
}
|
|
274
|
-
return (await
|
|
304
|
+
return (await n.json()).choices[0]?.message?.content?.trim();
|
|
275
305
|
}
|
|
276
306
|
/**
|
|
277
307
|
* Shows an AI suggestion
|
|
@@ -296,23 +326,23 @@ ${t}`);
|
|
|
296
326
|
*/
|
|
297
327
|
acceptSuggestion() {
|
|
298
328
|
if (this.fullSuggestion && this.currentParagraphIndex < this.suggestionParagraphs.length) {
|
|
299
|
-
const t = this.editor.value, e = this.editor.selectionStart, i = this.suggestionParagraphs[this.currentParagraphIndex], s = t.substring(0, e), o = t.substring(e),
|
|
329
|
+
const t = this.editor.value, e = this.editor.selectionStart, i = this.suggestionParagraphs[this.currentParagraphIndex], s = t.substring(0, e), o = t.substring(e), n = s.endsWith(" ") || s === "" || i.startsWith(" ") || i === "" ? "" : " ", a = s + n + i + o;
|
|
300
330
|
this.editor.value = a;
|
|
301
|
-
const
|
|
302
|
-
this.editor.setSelectionRange(
|
|
331
|
+
const r = e + n.length + i.length;
|
|
332
|
+
this.editor.setSelectionRange(r, r), this.dispatchEvent(new CustomEvent("change", { detail: { value: this.editor.value } })), this.currentParagraphIndex++, this.currentParagraphIndex >= this.suggestionParagraphs.length ? this.hideSuggestion() : this.updateBackground();
|
|
303
333
|
}
|
|
304
334
|
}
|
|
305
335
|
/**
|
|
306
336
|
* Shows loading indicator
|
|
307
337
|
*/
|
|
308
338
|
showLoading() {
|
|
309
|
-
this.loading.classList.add("show");
|
|
339
|
+
this.loading.classList.add("show"), this.onLoadingChangeCallback && this.onLoadingChangeCallback(!0);
|
|
310
340
|
}
|
|
311
341
|
/**
|
|
312
342
|
* Hides loading indicator
|
|
313
343
|
*/
|
|
314
344
|
hideLoading() {
|
|
315
|
-
this.loading.classList.remove("show");
|
|
345
|
+
this.loading.classList.remove("show"), this.onLoadingChangeCallback && this.onLoadingChangeCallback(!1);
|
|
316
346
|
}
|
|
317
347
|
/**
|
|
318
348
|
* Shows an error message
|
|
@@ -351,24 +381,24 @@ ${t}`);
|
|
|
351
381
|
* Sets the API key
|
|
352
382
|
*/
|
|
353
383
|
setApiKey(t) {
|
|
354
|
-
this.apiKey = t, this._saveApiKey(), this.editorStatus.style.backgroundColor = this.apiKey ? "#4caf50" : "#777";
|
|
384
|
+
this.apiKey = t, this._saveApiKey(), this.editorStatus.style.backgroundColor = this.apiKey ? "#4caf50" : "#777", this.onStatusChangeCallback && this.onStatusChangeCallback(!!this.apiKey);
|
|
355
385
|
}
|
|
356
386
|
/**
|
|
357
387
|
* Saves API key to localStorage
|
|
358
388
|
*/
|
|
359
389
|
_saveApiKey() {
|
|
360
390
|
if (!this.apiKey) {
|
|
361
|
-
localStorage.removeItem(
|
|
391
|
+
localStorage.removeItem(l);
|
|
362
392
|
return;
|
|
363
393
|
}
|
|
364
394
|
const t = btoa(this.apiKey);
|
|
365
|
-
localStorage.setItem(
|
|
395
|
+
localStorage.setItem(l, t);
|
|
366
396
|
}
|
|
367
397
|
/**
|
|
368
398
|
* Loads API key from localStorage
|
|
369
399
|
*/
|
|
370
400
|
_loadApiKey() {
|
|
371
|
-
const t = localStorage.getItem(
|
|
401
|
+
const t = localStorage.getItem(l);
|
|
372
402
|
if (!t) {
|
|
373
403
|
this.setApiKey("");
|
|
374
404
|
return;
|
|
@@ -442,6 +472,12 @@ ${t}`);
|
|
|
442
472
|
getContext() {
|
|
443
473
|
return this.context;
|
|
444
474
|
}
|
|
475
|
+
/**
|
|
476
|
+
* Configure the editor with callbacks and embedded mode
|
|
477
|
+
*/
|
|
478
|
+
configure(t) {
|
|
479
|
+
t.embedded !== void 0 && (this.embedded = t.embedded, this.embedded ? this.setAttribute("embedded", "") : this.removeAttribute("embedded")), t.onStatusChange && (this.onStatusChangeCallback = t.onStatusChange, this.onStatusChangeCallback(!!this.apiKey)), t.onLoadingChange && (this.onLoadingChangeCallback = t.onLoadingChange), t.apiKey !== void 0 && this.setApiKey(t.apiKey), t.suggestionDelay !== void 0 && this.setSuggestionDelay(t.suggestionDelay), t.systemPrompt !== void 0 && this.setSystemPrompt(t.systemPrompt), t.apiEndpoint !== void 0 && this.setApiEndpoint(t.apiEndpoint), t.modelName !== void 0 && this.setModelName(t.modelName), t.context !== void 0 && this.setContext(t.context);
|
|
480
|
+
}
|
|
445
481
|
/**
|
|
446
482
|
* Saves settings to localStorage
|
|
447
483
|
*/
|
|
@@ -466,12 +502,12 @@ ${t}`);
|
|
|
466
502
|
}
|
|
467
503
|
}
|
|
468
504
|
}
|
|
469
|
-
const
|
|
470
|
-
typeof window < "u" && !window.customElements.get(h) && customElements.define(h,
|
|
505
|
+
const p = (h = "liwe3-ai-text-editor") => {
|
|
506
|
+
typeof window < "u" && !window.customElements.get(h) && customElements.define(h, g);
|
|
471
507
|
};
|
|
472
|
-
|
|
508
|
+
p();
|
|
473
509
|
export {
|
|
474
|
-
|
|
475
|
-
|
|
510
|
+
g as AITextEditorElement,
|
|
511
|
+
p as defineAITextEditor
|
|
476
512
|
};
|
|
477
513
|
//# sourceMappingURL=AITextEditor.js.map
|
package/dist/AITextEditor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AITextEditor.js","sources":["../src/AITextEditor.ts"],"sourcesContent":["/**\n * AITextEditor Web Component\n * A text editor with AI-powered text continuation suggestions\n */\n\nconst AI_TEXT_EDITOR_API_KEY = 'ai-text-editor-api-key';\n\nexport interface AITextEditorConfig {\n apiKey?: string;\n suggestionDelay?: number;\n systemPrompt?: string;\n apiEndpoint?: string;\n modelName?: string;\n context?: string;\n}\n\nexport class AITextEditorElement extends HTMLElement {\n declare shadowRoot: ShadowRoot;\n private editor!: HTMLTextAreaElement;\n private editorBackground!: HTMLElement;\n private loading!: HTMLElement;\n private editorStatus!: HTMLElement;\n\n private typingTimer: number | null = null;\n private fullSuggestion: string | null = null;\n private suggestionParagraphs: string[] = [];\n private currentParagraphIndex: number = 0;\n private isShowingSuggestion: boolean = false;\n\n private apiKey: string = '';\n private suggestionDelay: number = 1000;\n 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.\";\n private apiEndpoint: string = 'https://api.openai.com/v1/chat/completions';\n private modelName: string = 'gpt-3.5-turbo';\n private context: string = '';\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n this.render();\n this.init();\n }\n\n /**\n * Renders the component's HTML structure\n */\n private render(): void {\n this.shadowRoot.innerHTML = `\n <style>\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .editor-container {\n position: relative;\n height: 100%;\n display: flex;\n flex-direction: column;\n }\n\n .editor-status {\n position: absolute;\n top: 5px;\n left: 5px;\n width: 10px;\n height: 10px;\n border-radius: 100%;\n background: #777;\n z-index: 10;\n }\n\n .editor-wrapper {\n position: relative;\n width: 100%;\n flex: 1;\n display: flex;\n flex-direction: column;\n }\n\n .editor {\n width: 100%;\n height: 100%;\n border: 2px solid #e1e5e9;\n border-radius: 12px;\n padding: 20px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 14px;\n line-height: 1.6;\n resize: none;\n background: #fafbfc;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n background: transparent;\n box-sizing: border-box;\n min-height: auto;\n }\n\n .editor:focus {\n outline: none;\n border-color: #4facfe;\n box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);\n }\n\n .editor-background {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 2px solid #e1e5e9;\n border-radius: 12px;\n padding: 20px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 14px;\n line-height: 1.6;\n background: #fafbfc;\n z-index: 1;\n pointer-events: none;\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow: hidden;\n color: transparent;\n box-sizing: border-box;\n }\n\n .editor-wrapper:focus-within .editor-background {\n background: white;\n border-color: #4facfe;\n }\n\n .suggestion-text {\n color: #bbb;\n position: relative;\n }\n\n .suggestion-text.accepted {\n color: #ddd;\n text-decoration: line-through;\n }\n\n .loading {\n position: absolute;\n top: 5px;\n right: 10px;\n z-index: 10;\n display: none;\n }\n\n .loading.show {\n display: block;\n }\n\n .spinner {\n width: 10px;\n height: 10px;\n border: 2px solid #e1e5e9;\n border-top: 2px solid #4facfe;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n }\n\n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n </style>\n\n <div class=\"editor-container\">\n <div class=\"editor-status\"></div>\n <div class=\"loading\" id=\"loading\">\n <div class=\"spinner\"></div>\n </div>\n\n <div class=\"editor-wrapper\">\n <div class=\"editor-background\" id=\"editorBackground\"></div>\n <textarea\n class=\"editor\"\n id=\"editor\"\n placeholder=\"Start writing your markdown text here...\"\n ></textarea>\n </div>\n </div>\n `;\n }\n\n /**\n * Initializes the component after rendering\n */\n private init(): void {\n const editor = this.shadowRoot.getElementById('editor') as HTMLTextAreaElement;\n const editorBackground = this.shadowRoot.getElementById('editorBackground') as HTMLElement;\n const loading = this.shadowRoot.getElementById('loading') as HTMLElement;\n\n this.editorStatus = this.shadowRoot.querySelector('.editor-status') as HTMLElement;\n this.editor = editor;\n this.editorBackground = editorBackground;\n this.loading = loading;\n\n this.editor.addEventListener('input', () => {\n this.handleTextInput();\n this.updateBackground();\n this.dispatchEvent(new CustomEvent('change', { detail: { value: this.editor.value } }));\n });\n\n this.editor.addEventListener('keydown', (e) => {\n this.handleKeyDown(e);\n });\n\n this.editor.addEventListener('keyup', (e) => {\n if (this.isShowingSuggestion && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.key)) {\n this.hideSuggestion();\n }\n // Update background on cursor movement, after potentially hiding suggestion\n if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.key)) {\n this.updateBackground();\n }\n });\n\n this.editor.addEventListener('click', () => {\n if (this.isShowingSuggestion) {\n this.hideSuggestion();\n }\n // Update background on mouse click (cursor position change)\n // setTimeout ensures cursor position is updated before background redraw\n setTimeout(() => this.updateBackground(), 0);\n });\n\n this.editor.addEventListener('scroll', () => {\n this.syncScroll();\n });\n\n this._loadApiKey();\n this._loadSettings();\n this.updateBackground();\n }\n\n /**\n * Updates the background layer with current text and suggestions\n */\n private updateBackground(): void {\n const currentText = this.editor.value;\n const cursorPosition = this.editor.selectionStart;\n\n let finalHtmlContent = '';\n\n if (this.isShowingSuggestion && this.fullSuggestion) {\n const beforeCursorText = currentText.substring(0, cursorPosition);\n const afterCursorText = currentText.substring(cursorPosition);\n\n // Determine the pending (not yet accepted) part of the suggestion\n const pendingParagraphs = Array.isArray(this.suggestionParagraphs)\n ? this.suggestionParagraphs.slice(this.currentParagraphIndex)\n : [];\n const pendingSuggestionText = pendingParagraphs.join(' ');\n\n if (pendingSuggestionText.trim().length > 0) {\n const mainSpacer = (beforeCursorText.endsWith(' ') || beforeCursorText === '' || pendingSuggestionText.startsWith(' ') || pendingSuggestionText === '') ? '' : ' ';\n const suggestionBlockHtml = `<span class=\"suggestion-text\">${this.escapeHtml(pendingSuggestionText)}</span>`;\n\n finalHtmlContent =\n this.escapeHtml(beforeCursorText).replace(/\\n/g, '<br>') +\n mainSpacer +\n suggestionBlockHtml +\n this.escapeHtml(afterCursorText).replace(/\\n/g, '<br>');\n } else {\n finalHtmlContent = this.escapeHtml(currentText).replace(/\\n/g, '<br>');\n }\n } else {\n finalHtmlContent = this.escapeHtml(currentText).replace(/\\n/g, '<br>');\n }\n\n this.editorBackground.innerHTML = finalHtmlContent;\n this.syncScroll();\n }\n\n /**\n * Synchronizes scroll position between editor and background\n */\n private syncScroll(): void {\n this.editorBackground.scrollTop = this.editor.scrollTop;\n this.editorBackground.scrollLeft = this.editor.scrollLeft;\n }\n\n /**\n * Handles text input events\n */\n private handleTextInput(): void {\n this.hideSuggestion();\n\n if (this.typingTimer) {\n clearTimeout(this.typingTimer);\n }\n\n if (!this.apiKey) return;\n\n this.typingTimer = window.setTimeout(() => {\n this.requestSuggestion();\n }, this.suggestionDelay);\n }\n\n /**\n * Handles keyboard events\n */\n private handleKeyDown(e: KeyboardEvent): void {\n if (this.isShowingSuggestion) {\n if (e.key === 'Tab') {\n e.preventDefault();\n this.acceptSuggestion();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n this.hideSuggestion();\n }\n }\n }\n\n /**\n * Requests an AI suggestion for the current text\n */\n private async requestSuggestion(): Promise<void> {\n if (!this.apiKey) return;\n\n const currentText = this.editor.value;\n if (!currentText.trim()) return;\n\n // Get text up to cursor position for context\n const cursorPosition = this.editor.selectionStart;\n const textUpToCursor = currentText.substring(0, cursorPosition);\n\n // Dispatch an event before starting the AI request, allow listeners to cancel\n const proceed = this.dispatchEvent(new CustomEvent('beforeSuggestion', {\n detail: {\n text: textUpToCursor,\n context: this.context,\n apiEndpoint: this.apiEndpoint,\n modelName: this.modelName,\n systemPrompt: this.systemPrompt\n },\n cancelable: true\n }));\n\n if (proceed === false) {\n return; // aborted by listener via event.preventDefault()\n }\n\n this.showLoading();\n\n try {\n const suggestion = await this.callOpenAI(textUpToCursor);\n this.hideLoading();\n\n if (suggestion) {\n this.showSuggestion(suggestion);\n }\n } catch (error) {\n this.hideLoading();\n this.showError('Failed to get AI suggestion: ' + (error as Error).message);\n }\n }\n\n /**\n * Calls the OpenAI API for text completion\n */\n private async callOpenAI(text: string): Promise<string> {\n const parts: string[] = [];\n if (this.context && this.context.trim()) {\n parts.push(`Context:\\n${this.context.trim()}`);\n }\n parts.push(`Please continue this text naturally:\\n\\n${text}`);\n const userContent = parts.join('\\n\\n');\n\n // Prepare headers - only add Authorization if API key is provided\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json'\n };\n\n if (this.apiKey && this.apiKey.trim() !== '') {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const requestBody = {\n model: this.modelName,\n messages: [\n {\n role: 'system',\n content: this.systemPrompt\n },\n {\n role: 'user',\n content: userContent\n }\n ],\n max_tokens: 150,\n temperature: 0.7\n };\n\n const response = await fetch(this.apiEndpoint, {\n method: 'POST',\n headers: headers,\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n let errorMessage = 'API request failed';\n try {\n const errorData = await response.json();\n errorMessage = errorData.error?.message || errorMessage;\n } catch (parseError) {\n console.error('Failed to parse error response:', parseError);\n errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n }\n throw new Error(errorMessage);\n }\n\n const data = await response.json();\n return data.choices[0]?.message?.content?.trim();\n }\n\n /**\n * Shows an AI suggestion\n */\n private showSuggestion(suggestion: string): void {\n this.fullSuggestion = suggestion;\n this.suggestionParagraphs = this.splitIntoParagraphs(suggestion);\n this.currentParagraphIndex = 0;\n this.isShowingSuggestion = true;\n this.updateBackground();\n }\n\n /**\n * Splits text into paragraphs/sentences\n */\n private splitIntoParagraphs(text: string): string[] {\n // Split on periods followed by space and capital letter, or double newlines\n const sentences = text.split(/(?<=\\.)\\s+(?=[A-Z])|(?:\\n\\s*\\n)/);\n return sentences.filter(sentence => sentence.trim().length > 0);\n }\n\n /**\n * Hides the current suggestion\n */\n private hideSuggestion(): void {\n this.isShowingSuggestion = false;\n this.fullSuggestion = null;\n this.suggestionParagraphs = [];\n this.currentParagraphIndex = 0;\n this.updateBackground();\n }\n\n /**\n * Accepts the current suggestion paragraph\n */\n private acceptSuggestion(): void {\n if (this.fullSuggestion && this.currentParagraphIndex < this.suggestionParagraphs.length) {\n const currentText = this.editor.value;\n const cursorPosition = this.editor.selectionStart;\n const paragraphToAdd = this.suggestionParagraphs[this.currentParagraphIndex];\n\n // Insert at cursor position\n const beforeCursor = currentText.substring(0, cursorPosition);\n const afterCursor = currentText.substring(cursorPosition);\n\n // Refined spacer logic\n const spacer = (beforeCursor.endsWith(' ') || beforeCursor === '' || paragraphToAdd.startsWith(' ') || paragraphToAdd === '') ? '' : ' ';\n const newText = beforeCursor + spacer + paragraphToAdd + afterCursor;\n\n this.editor.value = newText;\n\n // Update cursor position to after the inserted text\n const newCursorPosition = cursorPosition + spacer.length + paragraphToAdd.length;\n this.editor.setSelectionRange(newCursorPosition, newCursorPosition);\n this.dispatchEvent(new CustomEvent('change', { detail: { value: this.editor.value } }));\n\n // Move to next paragraph or hide if no more paragraphs\n this.currentParagraphIndex++;\n if (this.currentParagraphIndex >= this.suggestionParagraphs.length) {\n this.hideSuggestion();\n } else {\n this.updateBackground();\n }\n }\n }\n\n /**\n * Shows loading indicator\n */\n private showLoading(): void {\n this.loading.classList.add('show');\n }\n\n /**\n * Hides loading indicator\n */\n private hideLoading(): void {\n this.loading.classList.remove('show');\n }\n\n /**\n * Shows an error message\n */\n private showError(message: string): void {\n console.error('AI Text Editor Error:', message);\n // Try to dispatch a custom error event that can be handled by parent\n this.dispatchEvent(new CustomEvent('error', {\n detail: { message },\n bubbles: true,\n composed: true\n }));\n }\n\n /**\n * Escapes HTML special characters\n */\n private escapeHtml(unsafe: any): string {\n if (typeof unsafe !== 'string') {\n if (unsafe === null || typeof unsafe === 'undefined') {\n return '';\n }\n unsafe = String(unsafe);\n }\n return unsafe\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n }\n\n /**\n * Sets the text content\n */\n setText(text: string): void {\n this.editor.value = text;\n this.dispatchEvent(new CustomEvent('change', { detail: { value: this.editor.value } }));\n this.hideSuggestion();\n this.updateBackground();\n }\n\n /**\n * Gets the text content\n */\n getText(): string {\n return this.editor.value;\n }\n\n /**\n * Sets the API key\n */\n setApiKey(key: string): void {\n this.apiKey = key;\n this._saveApiKey();\n this.editorStatus.style.backgroundColor = this.apiKey ? '#4caf50' : '#777';\n }\n\n /**\n * Saves API key to localStorage\n */\n private _saveApiKey(): void {\n if (!this.apiKey) {\n localStorage.removeItem(AI_TEXT_EDITOR_API_KEY);\n return;\n }\n\n // Encrypt the API key in base64 format\n const encryptedKey = btoa(this.apiKey);\n localStorage.setItem(AI_TEXT_EDITOR_API_KEY, encryptedKey);\n }\n\n /**\n * Loads API key from localStorage\n */\n private _loadApiKey(): void {\n const savedKey = localStorage.getItem(AI_TEXT_EDITOR_API_KEY);\n if (!savedKey) {\n this.setApiKey('');\n return;\n }\n\n const decryptedKey = atob(savedKey);\n this.setApiKey(decryptedKey);\n }\n\n /**\n * Gets the API key\n */\n getApiKey(): string {\n return this.apiKey;\n }\n\n /**\n * Sets the suggestion delay in seconds\n */\n setSuggestionDelay(seconds: number): void {\n this.suggestionDelay = seconds * 1000;\n }\n\n /**\n * Gets the suggestion delay in seconds\n */\n getSuggestionDelay(): number {\n return this.suggestionDelay / 1000;\n }\n\n /**\n * Sets the system prompt\n */\n setSystemPrompt(prompt: string): void {\n this.systemPrompt = prompt;\n }\n\n /**\n * Gets the system prompt\n */\n getSystemPrompt(): string {\n return this.systemPrompt;\n }\n\n /**\n * Sets the API endpoint\n */\n setApiEndpoint(endpoint: string): void {\n this.apiEndpoint = endpoint;\n this._saveSettings();\n }\n\n /**\n * Gets the API endpoint\n */\n getApiEndpoint(): string {\n return this.apiEndpoint;\n }\n\n /**\n * Sets the model name\n */\n setModelName(modelName: string): void {\n this.modelName = modelName;\n this._saveSettings();\n }\n\n /**\n * Gets the model name\n */\n getModelName(): string {\n return this.modelName;\n }\n\n /**\n * Sets the context\n */\n setContext(context: string): void {\n this.context = typeof context === 'string' ? context : '';\n }\n\n /**\n * Gets the context\n */\n getContext(): string {\n return this.context;\n }\n\n /**\n * Saves settings to localStorage\n */\n private _saveSettings(): void {\n const settings = {\n apiEndpoint: this.apiEndpoint,\n modelName: this.modelName\n };\n localStorage.setItem('ai-text-editor-settings', JSON.stringify(settings));\n }\n\n /**\n * Loads settings from localStorage\n */\n private _loadSettings(): void {\n const savedSettings = localStorage.getItem('ai-text-editor-settings');\n if (savedSettings) {\n try {\n const settings = JSON.parse(savedSettings);\n if (settings.apiEndpoint) {\n this.apiEndpoint = settings.apiEndpoint;\n }\n if (settings.modelName) {\n this.modelName = settings.modelName;\n }\n } catch (error) {\n console.warn('Failed to load saved settings:', error);\n }\n }\n }\n}\n\n/**\n * Conditionally defines the custom element if in a browser environment.\n */\nconst defineAITextEditor = (tagName: string = 'liwe3-ai-text-editor'): void => {\n if (typeof window !== 'undefined' && !window.customElements.get(tagName)) {\n customElements.define(tagName, AITextEditorElement);\n }\n};\n\n// Auto-register with default tag name\ndefineAITextEditor();\n\nexport { defineAITextEditor };\n"],"names":["AI_TEXT_EDITOR_API_KEY","AITextEditorElement","editor","editorBackground","loading","e","currentText","cursorPosition","finalHtmlContent","beforeCursorText","afterCursorText","pendingSuggestionText","mainSpacer","suggestionBlockHtml","textUpToCursor","suggestion","error","text","parts","userContent","headers","requestBody","response","errorMessage","parseError","sentence","paragraphToAdd","beforeCursor","afterCursor","spacer","newText","newCursorPosition","message","unsafe","key","encryptedKey","savedKey","decryptedKey","seconds","prompt","endpoint","modelName","context","settings","savedSettings","defineAITextEditor","tagName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AITextEditor.js","sources":["../src/AITextEditor.ts"],"sourcesContent":["/**\n * AITextEditor Web Component\n * A text editor with AI-powered text continuation suggestions\n */\n\nconst AI_TEXT_EDITOR_API_KEY = 'ai-text-editor-api-key';\n\nexport interface AITextEditorConfig {\n apiKey?: string;\n suggestionDelay?: number; // Delay in seconds before showing AI suggestions\n systemPrompt?: string;\n apiEndpoint?: string;\n modelName?: string;\n context?: string;\n embedded?: boolean;\n onStatusChange?: (hasApiKey: boolean) => void;\n onLoadingChange?: (isLoading: boolean) => void;\n}\n\nexport class AITextEditorElement extends HTMLElement {\n declare shadowRoot: ShadowRoot;\n private editor!: HTMLTextAreaElement;\n private editorBackground!: HTMLElement;\n private loading!: HTMLElement;\n private editorStatus!: HTMLElement;\n\n private typingTimer: number | null = null;\n private fullSuggestion: string | null = null;\n private suggestionParagraphs: string[] = [];\n private currentParagraphIndex: number = 0;\n private isShowingSuggestion: boolean = false;\n\n private apiKey: string = '';\n private suggestionDelay: number = 1000; // Stored in milliseconds (default: 1000ms = 1 second)\n 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.\";\n private apiEndpoint: string = 'https://api.openai.com/v1/chat/completions';\n private modelName: string = 'gpt-3.5-turbo';\n private context: string = '';\n \n private embedded: boolean = false;\n private onStatusChangeCallback?: (hasApiKey: boolean) => void;\n private onLoadingChangeCallback?: (isLoading: boolean) => void;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n this.render();\n this.init();\n }\n\n /**\n * Renders the component's HTML structure\n */\n private render(): void {\n this.shadowRoot.innerHTML = `\n <style>\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .editor-container {\n position: relative;\n height: 100%;\n display: flex;\n flex-direction: column;\n }\n\n .editor-status {\n position: absolute;\n top: 5px;\n left: 5px;\n width: 10px;\n height: 10px;\n border-radius: 100%;\n background: #777;\n z-index: 10;\n }\n \n :host([embedded]) .editor-status {\n display: none;\n }\n\n .editor-wrapper {\n position: relative;\n width: 100%;\n flex: 1;\n display: flex;\n flex-direction: column;\n }\n\n .editor {\n width: 100%;\n height: 100%;\n border: 2px solid #e1e5e9;\n border-radius: 12px;\n padding: 20px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 14px;\n line-height: 1.6;\n resize: none;\n background: #fafbfc;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n background: transparent;\n box-sizing: border-box;\n min-height: auto;\n }\n \n :host([embedded]) .editor {\n border: none;\n border-radius: 0;\n }\n\n .editor:focus {\n outline: none;\n border-color: #4facfe;\n box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);\n }\n \n :host([embedded]) .editor:focus {\n box-shadow: none;\n }\n\n .editor-background {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 2px solid #e1e5e9;\n border-radius: 12px;\n padding: 20px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 14px;\n line-height: 1.6;\n background: #fafbfc;\n z-index: 1;\n pointer-events: none;\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow: hidden;\n color: transparent;\n box-sizing: border-box;\n }\n \n :host([embedded]) .editor-background {\n border: none;\n border-radius: 0;\n }\n\n .editor-wrapper:focus-within .editor-background {\n background: white;\n border-color: #4facfe;\n }\n \n :host([embedded]) .editor-wrapper:focus-within .editor-background {\n border-color: transparent;\n }\n\n .suggestion-text {\n color: #bbb;\n position: relative;\n }\n\n .suggestion-text.accepted {\n color: #ddd;\n text-decoration: line-through;\n }\n\n .loading {\n position: absolute;\n top: 5px;\n right: 10px;\n z-index: 10;\n display: none;\n }\n \n :host([embedded]) .loading {\n display: none !important;\n }\n\n .loading.show {\n display: block;\n }\n\n .spinner {\n width: 10px;\n height: 10px;\n border: 2px solid #e1e5e9;\n border-top: 2px solid #4facfe;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n }\n\n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n </style>\n\n <div class=\"editor-container\">\n <div class=\"editor-status\"></div>\n <div class=\"loading\" id=\"loading\">\n <div class=\"spinner\"></div>\n </div>\n\n <div class=\"editor-wrapper\">\n <div class=\"editor-background\" id=\"editorBackground\"></div>\n <textarea\n class=\"editor\"\n id=\"editor\"\n placeholder=\"Start writing your markdown text here...\"\n ></textarea>\n </div>\n </div>\n `;\n }\n\n /**\n * Initializes the component after rendering\n */\n private init(): void {\n const editor = this.shadowRoot.getElementById('editor') as HTMLTextAreaElement;\n const editorBackground = this.shadowRoot.getElementById('editorBackground') as HTMLElement;\n const loading = this.shadowRoot.getElementById('loading') as HTMLElement;\n\n this.editorStatus = this.shadowRoot.querySelector('.editor-status') as HTMLElement;\n this.editor = editor;\n this.editorBackground = editorBackground;\n this.loading = loading;\n\n this.editor.addEventListener('input', () => {\n this.handleTextInput();\n this.updateBackground();\n this.dispatchEvent(new CustomEvent('change', { detail: { value: this.editor.value } }));\n });\n\n this.editor.addEventListener('keydown', (e) => {\n this.handleKeyDown(e);\n });\n\n this.editor.addEventListener('keyup', (e) => {\n if (this.isShowingSuggestion && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.key)) {\n this.hideSuggestion();\n }\n // Update background on cursor movement, after potentially hiding suggestion\n if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.key)) {\n this.updateBackground();\n }\n });\n\n this.editor.addEventListener('click', () => {\n if (this.isShowingSuggestion) {\n this.hideSuggestion();\n }\n // Update background on mouse click (cursor position change)\n // setTimeout ensures cursor position is updated before background redraw\n setTimeout(() => this.updateBackground(), 0);\n });\n\n this.editor.addEventListener('scroll', () => {\n this.syncScroll();\n });\n\n this._loadApiKey();\n this._loadSettings();\n this.updateBackground();\n }\n\n /**\n * Updates the background layer with current text and suggestions\n */\n private updateBackground(): void {\n const currentText = this.editor.value;\n const cursorPosition = this.editor.selectionStart;\n\n let finalHtmlContent = '';\n\n if (this.isShowingSuggestion && this.fullSuggestion) {\n const beforeCursorText = currentText.substring(0, cursorPosition);\n const afterCursorText = currentText.substring(cursorPosition);\n\n // Determine the pending (not yet accepted) part of the suggestion\n const pendingParagraphs = Array.isArray(this.suggestionParagraphs)\n ? this.suggestionParagraphs.slice(this.currentParagraphIndex)\n : [];\n const pendingSuggestionText = pendingParagraphs.join(' ');\n\n if (pendingSuggestionText.trim().length > 0) {\n const mainSpacer = (beforeCursorText.endsWith(' ') || beforeCursorText === '' || pendingSuggestionText.startsWith(' ') || pendingSuggestionText === '') ? '' : ' ';\n const suggestionBlockHtml = `<span class=\"suggestion-text\">${this.escapeHtml(pendingSuggestionText)}</span>`;\n\n finalHtmlContent =\n this.escapeHtml(beforeCursorText).replace(/\\n/g, '<br>') +\n mainSpacer +\n suggestionBlockHtml +\n this.escapeHtml(afterCursorText).replace(/\\n/g, '<br>');\n } else {\n finalHtmlContent = this.escapeHtml(currentText).replace(/\\n/g, '<br>');\n }\n } else {\n finalHtmlContent = this.escapeHtml(currentText).replace(/\\n/g, '<br>');\n }\n\n this.editorBackground.innerHTML = finalHtmlContent;\n this.syncScroll();\n }\n\n /**\n * Synchronizes scroll position between editor and background\n */\n private syncScroll(): void {\n this.editorBackground.scrollTop = this.editor.scrollTop;\n this.editorBackground.scrollLeft = this.editor.scrollLeft;\n }\n\n /**\n * Handles text input events\n */\n private handleTextInput(): void {\n this.hideSuggestion();\n\n if (this.typingTimer) {\n clearTimeout(this.typingTimer);\n }\n\n if (!this.apiKey) return;\n\n this.typingTimer = window.setTimeout(() => {\n this.requestSuggestion();\n }, this.suggestionDelay);\n }\n\n /**\n * Handles keyboard events\n */\n private handleKeyDown(e: KeyboardEvent): void {\n if (this.isShowingSuggestion) {\n if (e.key === 'Tab') {\n e.preventDefault();\n this.acceptSuggestion();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n this.hideSuggestion();\n }\n }\n }\n\n /**\n * Requests an AI suggestion for the current text\n */\n private async requestSuggestion(): Promise<void> {\n if (!this.apiKey) return;\n\n const currentText = this.editor.value;\n if (!currentText.trim()) return;\n\n // Get text up to cursor position for context\n const cursorPosition = this.editor.selectionStart;\n const textUpToCursor = currentText.substring(0, cursorPosition);\n\n // Dispatch an event before starting the AI request, allow listeners to cancel\n const proceed = this.dispatchEvent(new CustomEvent('beforeSuggestion', {\n detail: {\n text: textUpToCursor,\n context: this.context,\n apiEndpoint: this.apiEndpoint,\n modelName: this.modelName,\n systemPrompt: this.systemPrompt\n },\n cancelable: true\n }));\n\n if (proceed === false) {\n return; // aborted by listener via event.preventDefault()\n }\n\n this.showLoading();\n\n try {\n const suggestion = await this.callOpenAI(textUpToCursor);\n this.hideLoading();\n\n if (suggestion) {\n this.showSuggestion(suggestion);\n }\n } catch (error) {\n this.hideLoading();\n this.showError('Failed to get AI suggestion: ' + (error as Error).message);\n this.dispatchEvent(new CustomEvent('oncompletionerror', {\n detail: { error: (error as Error).message },\n bubbles: true,\n composed: true\n }));\n }\n }\n\n /**\n * Calls the OpenAI API for text completion\n */\n private async callOpenAI(text: string): Promise<string> {\n const parts: string[] = [];\n if (this.context && this.context.trim()) {\n parts.push(`Context:\\n${this.context.trim()}`);\n }\n parts.push(`Please continue this text naturally:\\n\\n${text}`);\n const userContent = parts.join('\\n\\n');\n\n // Prepare headers - only add Authorization if API key is provided\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json'\n };\n\n if (this.apiKey && this.apiKey.trim() !== '') {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const requestBody = {\n model: this.modelName,\n messages: [\n {\n role: 'system',\n content: this.systemPrompt\n },\n {\n role: 'user',\n content: userContent\n }\n ],\n max_tokens: 150,\n temperature: 0.7\n };\n\n const response = await fetch(this.apiEndpoint, {\n method: 'POST',\n headers: headers,\n body: JSON.stringify(requestBody)\n });\n\n if (!response.ok) {\n let errorMessage = 'API request failed';\n try {\n const errorData = await response.json();\n errorMessage = errorData.error?.message || errorMessage;\n } catch (parseError) {\n console.error('Failed to parse error response:', parseError);\n errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n }\n throw new Error(errorMessage);\n }\n\n const data = await response.json();\n return data.choices[0]?.message?.content?.trim();\n }\n\n /**\n * Shows an AI suggestion\n */\n private showSuggestion(suggestion: string): void {\n this.fullSuggestion = suggestion;\n this.suggestionParagraphs = this.splitIntoParagraphs(suggestion);\n this.currentParagraphIndex = 0;\n this.isShowingSuggestion = true;\n this.updateBackground();\n }\n\n /**\n * Splits text into paragraphs/sentences\n */\n private splitIntoParagraphs(text: string): string[] {\n // Split on periods followed by space and capital letter, or double newlines\n const sentences = text.split(/(?<=\\.)\\s+(?=[A-Z])|(?:\\n\\s*\\n)/);\n return sentences.filter(sentence => sentence.trim().length > 0);\n }\n\n /**\n * Hides the current suggestion\n */\n private hideSuggestion(): void {\n this.isShowingSuggestion = false;\n this.fullSuggestion = null;\n this.suggestionParagraphs = [];\n this.currentParagraphIndex = 0;\n this.updateBackground();\n }\n\n /**\n * Accepts the current suggestion paragraph\n */\n private acceptSuggestion(): void {\n if (this.fullSuggestion && this.currentParagraphIndex < this.suggestionParagraphs.length) {\n const currentText = this.editor.value;\n const cursorPosition = this.editor.selectionStart;\n const paragraphToAdd = this.suggestionParagraphs[this.currentParagraphIndex];\n\n // Insert at cursor position\n const beforeCursor = currentText.substring(0, cursorPosition);\n const afterCursor = currentText.substring(cursorPosition);\n\n // Refined spacer logic\n const spacer = (beforeCursor.endsWith(' ') || beforeCursor === '' || paragraphToAdd.startsWith(' ') || paragraphToAdd === '') ? '' : ' ';\n const newText = beforeCursor + spacer + paragraphToAdd + afterCursor;\n\n this.editor.value = newText;\n\n // Update cursor position to after the inserted text\n const newCursorPosition = cursorPosition + spacer.length + paragraphToAdd.length;\n this.editor.setSelectionRange(newCursorPosition, newCursorPosition);\n this.dispatchEvent(new CustomEvent('change', { detail: { value: this.editor.value } }));\n\n // Move to next paragraph or hide if no more paragraphs\n this.currentParagraphIndex++;\n if (this.currentParagraphIndex >= this.suggestionParagraphs.length) {\n this.hideSuggestion();\n } else {\n this.updateBackground();\n }\n }\n }\n\n /**\n * Shows loading indicator\n */\n private showLoading(): void {\n this.loading.classList.add('show');\n if (this.onLoadingChangeCallback) {\n this.onLoadingChangeCallback(true);\n }\n }\n\n /**\n * Hides loading indicator\n */\n private hideLoading(): void {\n this.loading.classList.remove('show');\n if (this.onLoadingChangeCallback) {\n this.onLoadingChangeCallback(false);\n }\n }\n\n /**\n * Shows an error message\n */\n private showError(message: string): void {\n console.error('AI Text Editor Error:', message);\n // Try to dispatch a custom error event that can be handled by parent\n this.dispatchEvent(new CustomEvent('error', {\n detail: { message },\n bubbles: true,\n composed: true\n }));\n }\n\n /**\n * Escapes HTML special characters\n */\n private escapeHtml(unsafe: any): string {\n if (typeof unsafe !== 'string') {\n if (unsafe === null || typeof unsafe === 'undefined') {\n return '';\n }\n unsafe = String(unsafe);\n }\n return unsafe\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n }\n\n /**\n * Sets the text content\n */\n setText(text: string): void {\n this.editor.value = text;\n this.dispatchEvent(new CustomEvent('change', { detail: { value: this.editor.value } }));\n this.hideSuggestion();\n this.updateBackground();\n }\n\n /**\n * Gets the text content\n */\n getText(): string {\n return this.editor.value;\n }\n\n /**\n * Sets the API key\n */\n setApiKey(key: string): void {\n this.apiKey = key;\n this._saveApiKey();\n this.editorStatus.style.backgroundColor = this.apiKey ? '#4caf50' : '#777';\n if (this.onStatusChangeCallback) {\n this.onStatusChangeCallback(!!this.apiKey);\n }\n }\n\n /**\n * Saves API key to localStorage\n */\n private _saveApiKey(): void {\n if (!this.apiKey) {\n localStorage.removeItem(AI_TEXT_EDITOR_API_KEY);\n return;\n }\n\n // Encrypt the API key in base64 format\n const encryptedKey = btoa(this.apiKey);\n localStorage.setItem(AI_TEXT_EDITOR_API_KEY, encryptedKey);\n }\n\n /**\n * Loads API key from localStorage\n */\n private _loadApiKey(): void {\n const savedKey = localStorage.getItem(AI_TEXT_EDITOR_API_KEY);\n if (!savedKey) {\n this.setApiKey('');\n return;\n }\n\n const decryptedKey = atob(savedKey);\n this.setApiKey(decryptedKey);\n }\n\n /**\n * Gets the API key\n */\n getApiKey(): string {\n return this.apiKey;\n }\n\n /**\n * Sets the suggestion delay in seconds\n */\n setSuggestionDelay(seconds: number): void {\n this.suggestionDelay = seconds * 1000;\n }\n\n /**\n * Gets the suggestion delay in seconds\n */\n getSuggestionDelay(): number {\n return this.suggestionDelay / 1000;\n }\n\n /**\n * Sets the system prompt\n */\n setSystemPrompt(prompt: string): void {\n this.systemPrompt = prompt;\n }\n\n /**\n * Gets the system prompt\n */\n getSystemPrompt(): string {\n return this.systemPrompt;\n }\n\n /**\n * Sets the API endpoint\n */\n setApiEndpoint(endpoint: string): void {\n this.apiEndpoint = endpoint;\n this._saveSettings();\n }\n\n /**\n * Gets the API endpoint\n */\n getApiEndpoint(): string {\n return this.apiEndpoint;\n }\n\n /**\n * Sets the model name\n */\n setModelName(modelName: string): void {\n this.modelName = modelName;\n this._saveSettings();\n }\n\n /**\n * Gets the model name\n */\n getModelName(): string {\n return this.modelName;\n }\n\n /**\n * Sets the context\n */\n setContext(context: string): void {\n this.context = typeof context === 'string' ? context : '';\n }\n\n /**\n * Gets the context\n */\n getContext(): string {\n return this.context;\n }\n\n /**\n * Configure the editor with callbacks and embedded mode\n */\n configure(config: Partial<AITextEditorConfig>): void {\n if (config.embedded !== undefined) {\n this.embedded = config.embedded;\n if (this.embedded) {\n this.setAttribute('embedded', '');\n } else {\n this.removeAttribute('embedded');\n }\n }\n \n if (config.onStatusChange) {\n this.onStatusChangeCallback = config.onStatusChange;\n // Immediately call with current status\n this.onStatusChangeCallback(!!this.apiKey);\n }\n \n if (config.onLoadingChange) {\n this.onLoadingChangeCallback = config.onLoadingChange;\n }\n \n if (config.apiKey !== undefined) {\n this.setApiKey(config.apiKey);\n }\n \n if (config.suggestionDelay !== undefined) {\n this.setSuggestionDelay(config.suggestionDelay);\n }\n \n if (config.systemPrompt !== undefined) {\n this.setSystemPrompt(config.systemPrompt);\n }\n \n if (config.apiEndpoint !== undefined) {\n this.setApiEndpoint(config.apiEndpoint);\n }\n \n if (config.modelName !== undefined) {\n this.setModelName(config.modelName);\n }\n \n if (config.context !== undefined) {\n this.setContext(config.context);\n }\n }\n\n /**\n * Saves settings to localStorage\n */\n private _saveSettings(): void {\n const settings = {\n apiEndpoint: this.apiEndpoint,\n modelName: this.modelName\n };\n localStorage.setItem('ai-text-editor-settings', JSON.stringify(settings));\n }\n\n /**\n * Loads settings from localStorage\n */\n private _loadSettings(): void {\n const savedSettings = localStorage.getItem('ai-text-editor-settings');\n if (savedSettings) {\n try {\n const settings = JSON.parse(savedSettings);\n if (settings.apiEndpoint) {\n this.apiEndpoint = settings.apiEndpoint;\n }\n if (settings.modelName) {\n this.modelName = settings.modelName;\n }\n } catch (error) {\n console.warn('Failed to load saved settings:', error);\n }\n }\n }\n}\n\n/**\n * Conditionally defines the custom element if in a browser environment.\n */\nconst defineAITextEditor = (tagName: string = 'liwe3-ai-text-editor'): void => {\n if (typeof window !== 'undefined' && !window.customElements.get(tagName)) {\n customElements.define(tagName, AITextEditorElement);\n }\n};\n\n// Auto-register with default tag name\ndefineAITextEditor();\n\nexport { defineAITextEditor };\n"],"names":["AI_TEXT_EDITOR_API_KEY","AITextEditorElement","editor","editorBackground","loading","e","currentText","cursorPosition","finalHtmlContent","beforeCursorText","afterCursorText","pendingSuggestionText","mainSpacer","suggestionBlockHtml","textUpToCursor","suggestion","error","text","parts","userContent","headers","requestBody","response","errorMessage","parseError","sentence","paragraphToAdd","beforeCursor","afterCursor","spacer","newText","newCursorPosition","message","unsafe","key","encryptedKey","savedKey","decryptedKey","seconds","prompt","endpoint","modelName","context","config","settings","savedSettings","defineAITextEditor","tagName"],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ButtonToolbar Web Component
|
|
3
|
+
* A customizable toolbar with groups of buttons, supporting horizontal/vertical orientation
|
|
4
|
+
*/
|
|
5
|
+
export type ButtonToolbarItem = {
|
|
6
|
+
id: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
icon?: string;
|
|
9
|
+
image?: string;
|
|
10
|
+
type?: 'default' | 'info' | 'error' | 'warn' | 'success';
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
tooltip?: string;
|
|
13
|
+
action?: string;
|
|
14
|
+
};
|
|
15
|
+
export type ButtonToolbarGroup = {
|
|
16
|
+
id?: string;
|
|
17
|
+
items: ButtonToolbarItem[];
|
|
18
|
+
class?: string;
|
|
19
|
+
};
|
|
20
|
+
export declare class ButtonToolbarElement extends HTMLElement {
|
|
21
|
+
shadowRoot: ShadowRoot;
|
|
22
|
+
private _groups;
|
|
23
|
+
constructor();
|
|
24
|
+
static get observedAttributes(): string[];
|
|
25
|
+
attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
|
|
26
|
+
get orientation(): 'horizontal' | 'vertical';
|
|
27
|
+
set orientation(value: 'horizontal' | 'vertical');
|
|
28
|
+
get groups(): ButtonToolbarGroup[];
|
|
29
|
+
set groups(value: ButtonToolbarGroup[]);
|
|
30
|
+
connectedCallback(): void;
|
|
31
|
+
private handleButtonClick;
|
|
32
|
+
private render;
|
|
33
|
+
}
|
|
34
|
+
export declare const defineButtonToolbar: () => void;
|
|
35
|
+
//# sourceMappingURL=ButtonToolbar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ButtonToolbar.d.ts","sourceRoot":"","sources":["../src/ButtonToolbar.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,qBAAa,oBAAqB,SAAQ,WAAW;IAC3C,UAAU,EAAE,UAAU,CAAC;IAC/B,OAAO,CAAC,OAAO,CAA4B;;IAO3C,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAM/F,IAAI,WAAW,IAAI,YAAY,GAAG,UAAU,CAE3C;IAED,IAAI,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,EAE/C;IAED,IAAI,MAAM,IAAI,kBAAkB,EAAE,CAWjC;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,kBAAkB,EAAE,EAcrC;IAED,iBAAiB,IAAI,IAAI;IAIzB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,MAAM;CAsMf;AAED,eAAO,MAAM,mBAAmB,QAAO,IAItC,CAAC"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
class l extends HTMLElement {
|
|
2
|
+
constructor() {
|
|
3
|
+
super(), this._groups = [], this.attachShadow({ mode: "open" });
|
|
4
|
+
}
|
|
5
|
+
static get observedAttributes() {
|
|
6
|
+
return ["orientation", "groups"];
|
|
7
|
+
}
|
|
8
|
+
attributeChangedCallback(o, i, n) {
|
|
9
|
+
i !== n && this.render();
|
|
10
|
+
}
|
|
11
|
+
get orientation() {
|
|
12
|
+
return this.getAttribute("orientation") || "horizontal";
|
|
13
|
+
}
|
|
14
|
+
set orientation(o) {
|
|
15
|
+
this.setAttribute("orientation", o);
|
|
16
|
+
}
|
|
17
|
+
get groups() {
|
|
18
|
+
const o = this.getAttribute("groups");
|
|
19
|
+
if (o)
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(o);
|
|
22
|
+
} catch (i) {
|
|
23
|
+
return console.error("Invalid groups format:", i), [];
|
|
24
|
+
}
|
|
25
|
+
return this._groups;
|
|
26
|
+
}
|
|
27
|
+
set groups(o) {
|
|
28
|
+
this._groups = o, this.setAttribute("groups", JSON.stringify(o));
|
|
29
|
+
}
|
|
30
|
+
connectedCallback() {
|
|
31
|
+
this.render();
|
|
32
|
+
}
|
|
33
|
+
handleButtonClick(o, i) {
|
|
34
|
+
o.disabled || this.dispatchEvent(new CustomEvent("button-click", {
|
|
35
|
+
detail: {
|
|
36
|
+
id: o.id,
|
|
37
|
+
action: o.action || o.id,
|
|
38
|
+
originalEvent: i,
|
|
39
|
+
item: o
|
|
40
|
+
},
|
|
41
|
+
bubbles: !0,
|
|
42
|
+
composed: !0
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
render() {
|
|
46
|
+
if (!this.shadowRoot) return;
|
|
47
|
+
const o = `
|
|
48
|
+
:host {
|
|
49
|
+
display: block;
|
|
50
|
+
font-family: var(--liwe3-font-family, system-ui, -apple-system, sans-serif);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.toolbar {
|
|
54
|
+
display: flex;
|
|
55
|
+
gap: var(--liwe3-toolbar-gap, 0.5rem);
|
|
56
|
+
width: 100%;
|
|
57
|
+
box-sizing: border-box;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.toolbar.horizontal {
|
|
61
|
+
flex-direction: row;
|
|
62
|
+
align-items: center;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.toolbar.vertical {
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
align-items: stretch;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.group {
|
|
71
|
+
display: flex;
|
|
72
|
+
gap: 1px; /* Gap between buttons in a group */
|
|
73
|
+
background-color: var(--liwe3-toolbar-group-bg, transparent);
|
|
74
|
+
border-radius: var(--liwe3-toolbar-radius, 0.375rem);
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.toolbar.horizontal .group {
|
|
79
|
+
flex-direction: row;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.toolbar.vertical .group {
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.button {
|
|
87
|
+
display: inline-flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
gap: 0.5rem;
|
|
91
|
+
padding: var(--liwe3-button-padding, 0.5rem 1rem);
|
|
92
|
+
border: none;
|
|
93
|
+
cursor: pointer;
|
|
94
|
+
font-size: var(--liwe3-button-font-size, 0.875rem);
|
|
95
|
+
line-height: 1.25;
|
|
96
|
+
transition: all 0.2s;
|
|
97
|
+
background-color: var(--liwe3-button-bg, #f3f4f6);
|
|
98
|
+
color: var(--liwe3-button-color, #1f2937);
|
|
99
|
+
min-height: var(--liwe3-button-height, 2.5rem);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.button:hover:not(:disabled) {
|
|
103
|
+
filter: brightness(0.95);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.button:active:not(:disabled) {
|
|
107
|
+
filter: brightness(0.9);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.button:disabled {
|
|
111
|
+
opacity: 0.5;
|
|
112
|
+
cursor: not-allowed;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Button Types */
|
|
116
|
+
.button.default {
|
|
117
|
+
background-color: var(--liwe3-button-default-bg, #f3f4f6);
|
|
118
|
+
color: var(--liwe3-button-default-color, #1f2937);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.button.info {
|
|
122
|
+
background-color: var(--liwe3-button-info-bg, #3b82f6);
|
|
123
|
+
color: var(--liwe3-button-info-color, #ffffff);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.button.error {
|
|
127
|
+
background-color: var(--liwe3-button-error-bg, #ef4444);
|
|
128
|
+
color: var(--liwe3-button-error-color, #ffffff);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.button.warn {
|
|
132
|
+
background-color: var(--liwe3-button-warn-bg, #f59e0b);
|
|
133
|
+
color: var(--liwe3-button-warn-color, #ffffff);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.button.success {
|
|
137
|
+
background-color: var(--liwe3-button-success-bg, #10b981);
|
|
138
|
+
color: var(--liwe3-button-success-color, #ffffff);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Content styling */
|
|
142
|
+
.icon {
|
|
143
|
+
width: 1.25em;
|
|
144
|
+
height: 1.25em;
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
justify-content: center;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.icon svg {
|
|
151
|
+
width: 100%;
|
|
152
|
+
height: 100%;
|
|
153
|
+
fill: currentColor;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.image {
|
|
157
|
+
width: 1.5em;
|
|
158
|
+
height: 1.5em;
|
|
159
|
+
object-fit: cover;
|
|
160
|
+
border-radius: 50%;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Group styling - rounded corners logic */
|
|
164
|
+
.toolbar.horizontal .group .button:first-child {
|
|
165
|
+
border-top-left-radius: var(--liwe3-toolbar-radius, 0.375rem);
|
|
166
|
+
border-bottom-left-radius: var(--liwe3-toolbar-radius, 0.375rem);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.toolbar.horizontal .group .button:last-child {
|
|
170
|
+
border-top-right-radius: var(--liwe3-toolbar-radius, 0.375rem);
|
|
171
|
+
border-bottom-right-radius: var(--liwe3-toolbar-radius, 0.375rem);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.toolbar.vertical .group .button:first-child {
|
|
175
|
+
border-top-left-radius: var(--liwe3-toolbar-radius, 0.375rem);
|
|
176
|
+
border-top-right-radius: var(--liwe3-toolbar-radius, 0.375rem);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.toolbar.vertical .group .button:last-child {
|
|
180
|
+
border-bottom-left-radius: var(--liwe3-toolbar-radius, 0.375rem);
|
|
181
|
+
border-bottom-right-radius: var(--liwe3-toolbar-radius, 0.375rem);
|
|
182
|
+
}
|
|
183
|
+
`, i = (t) => {
|
|
184
|
+
const e = document.createElement("button");
|
|
185
|
+
if (e.className = `button ${t.type || "default"}`, t.disabled && (e.disabled = !0), t.tooltip && (e.title = t.tooltip), e.onclick = (r) => this.handleButtonClick(t, r), t.icon) {
|
|
186
|
+
const r = document.createElement("span");
|
|
187
|
+
if (r.className = "icon", t.icon.trim().startsWith("<"))
|
|
188
|
+
r.innerHTML = t.icon;
|
|
189
|
+
else {
|
|
190
|
+
const a = document.createElement("i");
|
|
191
|
+
a.className = t.icon, r.appendChild(a);
|
|
192
|
+
}
|
|
193
|
+
e.appendChild(r);
|
|
194
|
+
}
|
|
195
|
+
if (t.image) {
|
|
196
|
+
const r = document.createElement("img");
|
|
197
|
+
r.src = t.image, r.className = "image", r.alt = t.label || "", e.appendChild(r);
|
|
198
|
+
}
|
|
199
|
+
if (t.label) {
|
|
200
|
+
const r = document.createElement("span");
|
|
201
|
+
r.textContent = t.label, e.appendChild(r);
|
|
202
|
+
}
|
|
203
|
+
return e;
|
|
204
|
+
}, n = document.createElement("div");
|
|
205
|
+
n.className = `toolbar ${this.orientation}`, this._groups.forEach((t) => {
|
|
206
|
+
const e = document.createElement("div");
|
|
207
|
+
e.className = `group ${t.class || ""}`, t.items.forEach((r) => {
|
|
208
|
+
e.appendChild(i(r));
|
|
209
|
+
}), n.appendChild(e);
|
|
210
|
+
}), this.shadowRoot.innerHTML = `<style>${o}</style>`, this.shadowRoot.appendChild(n);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const u = () => {
|
|
214
|
+
typeof window < "u" && !customElements.get("liwe3-button-toolbar") && customElements.define("liwe3-button-toolbar", l);
|
|
215
|
+
};
|
|
216
|
+
export {
|
|
217
|
+
l as ButtonToolbarElement,
|
|
218
|
+
u as defineButtonToolbar
|
|
219
|
+
};
|
|
220
|
+
//# sourceMappingURL=ButtonToolbar.js.map
|