gradio/frontend
1442k
1<script lang="ts">2 import EditableCell from "./EditableCell.svelte";3 import CellMenuButton from "./CellMenuButton.svelte";4 import type { I18nFormatter } from "js/core/src/gradio_helper";5 import { get_sort_status } from "./utils/sort_utils";6 import Padlock from "./icons/Padlock.svelte";7 import SortArrowUp from "./icons/SortArrowUp.svelte";8 import SortArrowDown from "./icons/SortArrowDown.svelte";9 import type { SortDirection } from "./context/dataframe_context";10 import CellMenuIcons from "./CellMenuIcons.svelte";11 import type { FilterDatatype } from "./context/dataframe_context";12 import type { Datatype } from "./utils/utils";13 import { BaseCheckbox } from "@gradio/checkbox";14 export let value: string;15 export let i: number;16 export let datatype: Datatype = "str";17 export let actual_pinned_columns: number;18 export let header_edit: number | false;19 export let selected_header: number | false;20 export let headers: string[];21 export let get_cell_width: (index: number) => string;22 export let handle_header_click: (event: MouseEvent, col: number) => void;23 export let toggle_header_menu: (event: MouseEvent, col: number) => void;24 export let end_header_edit: (event: CustomEvent<KeyboardEvent>) => void;25 export let sort_columns: { col: number; direction: SortDirection }[] = [];26 export let filter_columns: {27 col: number;28 datatype: FilterDatatype;29 filter: string;30 value: string;31 }[] = [];32 33 export let latex_delimiters: {34 left: string;35 right: string;36 display: boolean;37 }[];38 export let line_breaks: boolean;39 export let max_chars: number | undefined;40 export let editable: boolean;41 export let i18n: I18nFormatter;42 export let el: HTMLTextAreaElement | null;43 export let is_static: boolean;44 export let col_count: [number, "fixed" | "dynamic"];45 export let data: any[] = [];46 export let on_select_all:47 | ((col: number, checked: boolean) => void)48 | undefined = undefined;49 50 $: can_add_columns = col_count && col_count[1] === "dynamic";51 $: is_bool_column = datatype === "bool";52 53 $: select_all_state = (() => {54 if (!is_bool_column || data.length === 0) return "unchecked";55 const true_count = data.filter(56 (row) => row[i]?.value === true || row[i]?.value === "true"57 ).length;58 if (true_count === 0) return "unchecked";59 if (true_count === data.length) return "checked";60 return "indeterminate";61 })();62 63 $: sort_index = sort_columns.findIndex((item) => item.col === i);64 $: filter_index = filter_columns.findIndex((item) => item.col === i);65 $: sort_priority = sort_index !== -1 ? sort_index + 1 : null;66 $: current_direction =67 sort_index !== -1 ? sort_columns[sort_index].direction : null;68 69 function get_header_position(col_index: number): string {70 if (col_index >= actual_pinned_columns) {71 return "auto";72 }73 74 if (col_index === 0) {75 return "0";76 }77 78 const previous_widths = Array(col_index)79 .fill(0)80 .map((_, idx) => {81 return get_cell_width(idx);82 })83 .join(" + ");84 85 return `calc(${previous_widths})`;86 }87</script>88 89<th90 class:pinned-column={i < actual_pinned_columns}91 class:last-pinned={i === actual_pinned_columns - 1}92 class:focus={header_edit === i || selected_header === i}93 class:sorted={sort_index !== -1}94 class:filtered={filter_index !== -1}95 aria-sort={get_sort_status(value, sort_columns, headers) === "none"96 ? "none"97 : get_sort_status(value, sort_columns, headers) === "asc"98 ? "ascending"99 : "descending"}100 style="width: {get_cell_width(i)}; left: {get_header_position(i)};"101 on:click={(event) => handle_header_click(event, i)}102 on:mousedown={(event) => {103 event.preventDefault();104 event.stopPropagation();105 }}106 title={value}107>108 <div class="cell-wrap">109 <div class="header-content">110 {#if is_bool_column && editable && on_select_all}111 <div112 class="select-all-checkbox"113 role="button"114 tabindex="0"115 on:click|stopPropagation116 on:keydown|stopPropagation={(e) => {117 if (e.key === "Enter" || e.key === " ") {118 e.preventDefault();119 }120 }}121 on:mousedown|stopPropagation122 >123 <BaseCheckbox124 value={select_all_state === "checked"}125 indeterminate={select_all_state === "indeterminate"}126 label=""127 interactive={true}128 on:select={() => {129 if (on_select_all) {130 const new_value = select_all_state !== "checked";131 on_select_all(i, new_value);132 }133 }}134 />135 </div>136 {/if}137 <button138 class="header-button"139 on:click={(event) => handle_header_click(event, i)}140 on:mousedown={(event) => {141 event.preventDefault();142 event.stopPropagation();143 }}144 title={value}145 >146 <EditableCell147 {max_chars}148 bind:value149 bind:el150 {latex_delimiters}151 {line_breaks}152 edit={header_edit === i}153 on:keydown={(event) => {154 if (155 event.detail.key === "Enter" ||156 event.detail.key === "Escape" ||157 event.detail.key === "Tab"158 ) {159 end_header_edit(event);160 }161 }}162 header163 {editable}164 {is_static}165 {i18n}166 coords={[i, 0]}167 />168 {#if sort_index !== -1}169 <div class="sort-indicators">170 <span class="sort-arrow">171 {#if current_direction === "asc"}172 <SortArrowUp size={12} />173 {:else}174 <SortArrowDown size={12} />175 {/if}176 </span>177 {#if sort_columns.length > 1}178 <span class="sort-priority">179 {sort_priority}180 </span>181 {/if}182 </div>183 {/if}184 {#if filter_index !== -1}185 <div class="filter-indicators">186 <span class="filter-icon">187 <CellMenuIcons icon="filter" />188 </span>189 </div>190 {/if}191 </button>192 {#if is_static}193 <Padlock />194 {/if}195 </div>196 {#if can_add_columns}197 <CellMenuButton on_click={(event) => toggle_header_menu(event, i)} />198 {/if}199 </div>200</th>201 202<style>203 th {204 --ring-color: transparent;205 position: relative;206 outline: none;207 box-shadow: inset 0 0 0 1px var(--ring-color);208 padding: 0;209 background: var(--table-even-background-fill);210 border-right-width: 0px;211 border-left-width: 1px;212 border-style: solid;213 border-color: var(--border-color-primary);214 }215 216 th:first-child {217 border-top-left-radius: var(--table-radius);218 border-bottom-left-radius: var(--table-radius);219 border-left-width: 0px;220 }221 222 th:last-child {223 border-top-right-radius: var(--table-radius);224 border-bottom-right-radius: var(--table-radius);225 }226 227 th.focus {228 --ring-color: var(--color-accent);229 box-shadow: inset 0 0 0 2px var(--ring-color);230 z-index: 4;231 }232 233 th.focus :global(.cell-menu-button) {234 display: flex;235 }236 237 th:hover :global(.cell-menu-button) {238 display: flex;239 }240 241 .cell-wrap {242 display: flex;243 align-items: center;244 justify-content: flex-start;245 outline: none;246 min-height: var(--size-9);247 position: relative;248 height: 100%;249 padding: var(--size-2);250 box-sizing: border-box;251 margin: 0;252 gap: var(--size-1);253 overflow: visible;254 min-width: 0;255 border-radius: var(--table-radius);256 }257 258 .header-content {259 display: flex;260 align-items: center;261 overflow: hidden;262 flex-grow: 1;263 min-width: 0;264 white-space: normal;265 overflow-wrap: break-word;266 word-break: normal;267 height: 100%;268 gap: var(--size-1);269 }270 271 .header-button {272 display: flex;273 text-align: left;274 width: 100%;275 overflow: hidden;276 text-overflow: ellipsis;277 display: flex;278 align-items: center;279 position: relative;280 }281 282 .sort-indicators {283 display: flex;284 align-items: center;285 margin-left: var(--size-1);286 gap: var(--size-1);287 }288 289 .sort-arrow {290 display: flex;291 align-items: center;292 justify-content: center;293 color: var(--body-text-color);294 }295 296 .sort-priority {297 display: flex;298 align-items: center;299 justify-content: center;300 font-size: var(--size-2);301 background-color: var(--button-secondary-background-fill);302 color: var(--body-text-color);303 border-radius: var(--radius-sm);304 width: var(--size-2-5);305 height: var(--size-2-5);306 padding: var(--size-1-5);307 }308 309 .filter-indicators {310 display: flex;311 align-items: center;312 margin-left: var(--size-1);313 gap: var(--size-1);314 }315 316 .filter-icon {317 display: flex;318 align-items: center;319 justify-content: center;320 color: var(--body-text-color);321 }322 323 .pinned-column {324 position: sticky;325 z-index: 5;326 border-right: none;327 }328 329 .select-all-checkbox {330 display: flex;331 align-items: center;332 justify-content: center;333 margin-right: var(--size-1);334 flex-shrink: 0;335 }336 337 .select-all-checkbox :global(label) {338 margin: 0;339 }340 341 .select-all-checkbox :global(span) {342 display: none;343 }344</style>345 