Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
inference-widgets
/
packages
/widgets
/src
/lib
/components
/InferenceWidget
/shared
/WidgetTextarea
/WidgetTextarea.svelte
| <script lang="ts"> | |
| import { tick } from "svelte"; | |
| import { delay } from "../../../../utils/ViewUtils.js"; | |
| import WidgetLabel from "../WidgetLabel/WidgetLabel.svelte"; | |
| export let label: string = ""; | |
| export let placeholder: string = "Your sentence here..."; | |
| export let value: string; | |
| export let isLoading = false; | |
| export let isDisabled = false; | |
| export let size: "small" | "big" = "small"; | |
| let containerSpanEl: HTMLSpanElement; | |
| const typingEffectSpeedMs = 12; | |
| const classNamesInput = "whitespace-pre-wrap inline font-normal text-black dark:text-white"; | |
| const classNamesOutput = "whitespace-pre-wrap inline text-blue-600 dark:text-blue-400"; | |
| export async function renderTypingEffect(outputTxt: string): Promise<void> { | |
| const spanEl = document.createElement("span"); | |
| spanEl.contentEditable = "true"; | |
| spanEl.className = classNamesOutput; | |
| containerSpanEl?.appendChild(spanEl); | |
| await tick(); | |
| // fix Chrome bug that adds `<br>` els on contentedtiable el | |
| const brElts = containerSpanEl?.querySelectorAll("br"); | |
| for (const brEl of brElts) { | |
| brEl.remove(); | |
| } | |
| await tick(); | |
| // split on whitespace or any other character to correctly render newlines \n | |
| for (const char of outputTxt.split(/(\s|.)/g)) { | |
| await delay(typingEffectSpeedMs); | |
| spanEl.textContent += char; | |
| moveCaretToEnd(); | |
| } | |
| updateInnerTextValue(); | |
| } | |
| function moveCaretToEnd() { | |
| containerSpanEl?.focus(); | |
| if (containerSpanEl) { | |
| const range = document.createRange(); | |
| range.selectNodeContents(containerSpanEl); | |
| range.collapse(false); | |
| const selection = window.getSelection(); | |
| selection?.removeAllRanges(); | |
| selection?.addRange(range); | |
| } | |
| } | |
| // handle FireFox contenteditable paste bug | |
| function handlePaste(e: ClipboardEvent) { | |
| if (isLoading) { | |
| return e.preventDefault(); | |
| } | |
| const copiedTxt = e.clipboardData?.getData("text/plain"); | |
| const selection = window.getSelection(); | |
| if (selection?.rangeCount && !!copiedTxt?.length) { | |
| const range = selection.getRangeAt(0); | |
| range.deleteContents(); | |
| const spanEl = document.createElement("span"); | |
| spanEl.contentEditable = "true"; | |
| spanEl.className = classNamesInput; | |
| spanEl.textContent = copiedTxt; | |
| range.insertNode(spanEl); | |
| } | |
| window.getSelection()?.collapseToEnd(); | |
| updateInnerTextValue(); | |
| } | |
| function updateInnerTextValue() { | |
| value = containerSpanEl?.textContent ?? ""; | |
| } | |
| export function setValue(text: string): void { | |
| containerSpanEl.textContent = text; | |
| updateInnerTextValue(); | |
| } | |
| </script> | |
| <WidgetLabel {label}> | |
| <svelte:fragment slot="after"> | |
| <!-- `whitespace-pre-wrap inline-block` are needed to get correct newlines from `el.textContent` on Chrome --> | |
| <span | |
| class="{label ? 'mt-1.5' : ''} block w-full resize-y overflow-auto py-2 px-3 {size === 'small' | |
| ? 'min-h-[42px]' | |
| : 'min-h-[144px]'} inline-block max-h-[500px] whitespace-pre-wrap rounded-lg border border-gray-200 shadow-inner outline-none focus:shadow-inner focus:ring focus:ring-blue-200 dark:bg-gray-925" | |
| role="textbox" | |
| contenteditable={!isLoading && !isDisabled} | |
| style="--placeholder: '{isDisabled ? '' : placeholder}'" | |
| spellcheck="false" | |
| dir="auto" | |
| bind:this={containerSpanEl} | |
| on:paste|preventDefault={handlePaste} | |
| on:input={updateInnerTextValue} | |
| /> | |
| </svelte:fragment> | |
| </WidgetLabel> | |
| <style> | |
| span[contenteditable]:empty::before { | |
| content: var(--placeholder); | |
| color: rgba(156, 163, 175); | |
| } | |
| </style> | |