gradio/frontend
1413k
1<script lang="ts">2 import { createEventDispatcher } from "svelte";3 import { MarkdownCode } from "@gradio/markdown-code";4 import type { I18nFormatter } from "@gradio/utils";5 import type { CellValue } from "./types";6 import SelectionButtons from "./icons/SelectionButtons.svelte";7 import BooleanCell from "./BooleanCell.svelte";8 9 export let edit: boolean;10 export let value: CellValue = "";11 export let display_value: string | null = null;12 export let styling = "";13 export let header = false;14 export let datatype:15 | "str"16 | "markdown"17 | "html"18 | "number"19 | "bool"20 | "date"21 | "image" = "str";22 export let latex_delimiters: {23 left: string;24 right: string;25 display: boolean;26 }[];27 export let line_breaks = true;28 export let editable = true;29 export let is_static = false;30 export let max_chars: number | null = null;31 export let components: Record<string, any> = {};32 export let i18n: I18nFormatter;33 export let is_dragging = false;34 export let wrap_text = false;35 36 export let show_selection_buttons = false;37 export let coords: [number, number];38 export let on_select_column: ((col: number) => void) | null = null;39 export let on_select_row: ((row: number) => void) | null = null;40 export let el: HTMLTextAreaElement | null;41 42 const dispatch = createEventDispatcher<{43 blur: { blur_event: FocusEvent; coords: [number, number] };44 keydown: KeyboardEvent;45 }>();46 47 function truncate_text(48 text: CellValue,49 max_length: number | null = null,50 is_image = false51 ): string {52 if (is_image) return String(text);53 const str = String(text);54 if (!max_length || max_length <= 0) return str;55 if (str.length <= max_length) return str;56 return str.slice(0, max_length) + "...";57 }58 59 $: should_truncate = !edit && max_chars !== null && max_chars > 0;60 61 $: display_content = editable62 ? value63 : display_value !== null64 ? display_value65 : value;66 67 $: display_text = should_truncate68 ? truncate_text(display_content, max_chars, datatype === "image")69 : display_content;70 71 function use_focus(node: HTMLTextAreaElement): any {72 requestAnimationFrame(() => {73 node.focus();74 });75 76 return {};77 }78 79 function handle_blur(event: FocusEvent): void {80 dispatch("blur", {81 blur_event: event,82 coords: coords83 });84 }85 86 function handle_keydown(event: KeyboardEvent): void {87 dispatch("keydown", event);88 }89 90 function commit_change(checked: boolean): void {91 handle_blur({ target: { value } } as unknown as FocusEvent);92 }93 94 $: if (!edit) {95 // Shim blur on removal for Safari and Firefox96 handle_blur({ target: { value } } as unknown as FocusEvent);97 }98</script>99 100{#if edit && datatype !== "bool"}101 <textarea102 readonly={is_static}103 aria-readonly={is_static}104 aria-label={is_static ? "Cell is read-only" : "Edit cell"}105 bind:this={el}106 bind:value107 class:header108 tabindex="-1"109 on:blur={handle_blur}110 on:mousedown|stopPropagation111 on:click|stopPropagation112 use:use_focus113 on:keydown={handle_keydown}114 />115{/if}116 117{#if datatype === "bool" && typeof value === "boolean"}118 <BooleanCell bind:value {editable} on_change={commit_change} />119{:else}120 <span121 class:dragging={is_dragging}122 on:keydown={handle_keydown}123 tabindex="0"124 role="button"125 class:edit126 class:expanded={edit}127 class:multiline={header}128 on:focus|preventDefault129 style={styling}130 data-editable={editable}131 data-max-chars={max_chars}132 data-expanded={edit}133 placeholder=" "134 class:text={datatype === "str"}135 class:wrap={wrap_text}136 >137 {#if datatype === "image" && components.image}138 <svelte:component139 this={components.image}140 value={{ url: display_text }}141 show_label={false}142 label="cell-image"143 show_download_button={false}144 {i18n}145 gradio={{ dispatch: () => {} }}146 />147 {:else if datatype === "html"}148 {@html display_text}149 {:else if datatype === "markdown"}150 <MarkdownCode151 message={display_text.toLocaleString()}152 {latex_delimiters}153 {line_breaks}154 chatbot={false}155 />156 {:else}157 {display_text}158 {/if}159 </span>160{/if}161 162{#if show_selection_buttons && coords && on_select_column && on_select_row}163 <SelectionButtons164 position="column"165 {coords}166 on_click={() => on_select_column(coords[1])}167 />168 <SelectionButtons169 position="row"170 {coords}171 on_click={() => on_select_row(coords[0])}172 />173{/if}174 175<style>176 .dragging {177 cursor: crosshair !important;178 }179 180 textarea {181 position: absolute;182 flex: 1 1 0%;183 transform: translateX(-0.1px);184 outline: none;185 border: none;186 background: transparent;187 cursor: text;188 width: calc(100% - var(--size-2));189 resize: none;190 height: 100%;191 padding-left: 0;192 font-size: inherit;193 font-weight: inherit;194 line-height: var(--line-lg);195 }196 197 textarea:focus {198 outline: none;199 }200 201 span {202 flex: 1 1 0%;203 position: relative;204 display: inline-block;205 outline: none;206 -webkit-user-select: text;207 -moz-user-select: text;208 -ms-user-select: text;209 user-select: text;210 cursor: text;211 width: 100%;212 height: 100%;213 overflow: hidden;214 }215 216 span.text.expanded {217 height: auto;218 min-height: 100%;219 white-space: pre-wrap;220 word-break: break-word;221 overflow: visible;222 }223 224 .multiline {225 white-space: pre;226 overflow: hidden;227 text-overflow: ellipsis;228 }229 230 .header {231 transform: translateX(0);232 font-weight: var(--weight-bold);233 white-space: nowrap;234 overflow: hidden;235 text-overflow: ellipsis;236 margin-left: var(--size-1);237 }238 239 .edit {240 opacity: 0;241 pointer-events: none;242 }243 244 span :global(img) {245 max-height: 100px;246 width: auto;247 object-fit: contain;248 }249 250 textarea:read-only {251 cursor: not-allowed;252 }253 254 .wrap,255 .wrap.expanded {256 white-space: normal;257 word-wrap: break-word;258 overflow-wrap: break-word;259 word-wrap: break-word;260 }261</style>262 