| <script context="module"> | |
| export const TABS = {}; | |
| </script> | |
| <script lang="ts"> | |
| import { setContext, createEventDispatcher } from "svelte"; | |
| import { writable } from "svelte/store"; | |
| import type { SelectData } from "@gradio/utils"; | |
| interface Tab { | |
| name: string; | |
| id: object; | |
| elem_id: string | undefined; | |
| visible: boolean; | |
| interactive: boolean; | |
| } | |
| export let visible = true; | |
| export let elem_id = "id"; | |
| export let elem_classes: string[] = []; | |
| export let selected: number | string | object; | |
| let tabs: Tab[] = []; | |
| const selected_tab = writable<false | object | number | string>(false); | |
| const selected_tab_index = writable<number>(0); | |
| const dispatch = createEventDispatcher<{ | |
| change: undefined; | |
| select: SelectData; | |
| }>(); | |
| setContext(TABS, { | |
| register_tab: (tab: Tab) => { | |
| let existingTab = tabs.find((t) => t.id === tab.id); | |
| if (existingTab) { | |
| // update existing tab with newer values | |
| let i = tabs.findIndex((t) => t.id === tab.id); | |
| tabs[i] = { ...tabs[i], ...tab }; | |
| } else { | |
| tabs.push({ | |
| name: tab.name, | |
| id: tab.id, | |
| elem_id: tab.elem_id, | |
| visible: tab.visible, | |
| interactive: tab.interactive | |
| }); | |
| } | |
| selected_tab.update((current) => { | |
| if (current === false && tab.visible && tab.interactive) { | |
| return tab.id; | |
| } | |
| let nextTab = tabs.find((t) => t.visible && t.interactive); | |
| return nextTab ? nextTab.id : current; | |
| }); | |
| tabs = tabs; | |
| return tabs.length - 1; | |
| }, | |
| unregister_tab: (tab: Tab) => { | |
| const i = tabs.findIndex((t) => t.id === tab.id); | |
| tabs.splice(i, 1); | |
| selected_tab.update((current) => | |
| current === tab.id ? tabs[i]?.id || tabs[tabs.length - 1]?.id : current | |
| ); | |
| }, | |
| selected_tab, | |
| selected_tab_index | |
| }); | |
| function change_tab(id: object | string | number): void { | |
| const tab_to_activate = tabs.find((t) => t.id === id); | |
| if ( | |
| tab_to_activate && | |
| tab_to_activate.interactive && | |
| tab_to_activate.visible | |
| ) { | |
| selected = id; | |
| $selected_tab = id; | |
| $selected_tab_index = tabs.findIndex((t) => t.id === id); | |
| dispatch("change"); | |
| } else { | |
| console.warn("Attempted to select a non-interactive or hidden tab."); | |
| } | |
| } | |
| $: tabs, selected !== null && change_tab(selected); | |
| </script> | |
| <div class="tabs {elem_classes.join(' ')}" class:hide={!visible} id={elem_id}> | |
| <div class="tab-nav scroll-hide" role="tablist"> | |
| {#each tabs as t, i (t.id)} | |
| {#if t.visible} | |
| {#if t.id === $selected_tab} | |
| <button | |
| role="tab" | |
| class="selected" | |
| aria-selected={true} | |
| aria-controls={t.elem_id} | |
| id={t.elem_id ? t.elem_id + "-button" : null} | |
| > | |
| {t.name} | |
| </button> | |
| {:else} | |
| <button | |
| role="tab" | |
| aria-selected={false} | |
| aria-controls={t.elem_id} | |
| disabled={!t.interactive} | |
| aria-disabled={!t.interactive} | |
| id={t.elem_id ? t.elem_id + "-button" : null} | |
| on:click={() => { | |
| change_tab(t.id); | |
| dispatch("select", { value: t.name, index: i }); | |
| }} | |
| > | |
| {t.name} | |
| </button> | |
| {/if} | |
| {/if} | |
| {/each} | |
| </div> | |
| <slot /> | |
| </div> | |
| <style> | |
| .tabs { | |
| position: relative; | |
| } | |
| .hide { | |
| display: none; | |
| } | |
| .tab-nav { | |
| display: flex; | |
| position: relative; | |
| flex-wrap: wrap; | |
| border-bottom: 1px solid var(--border-color-primary); | |
| } | |
| button { | |
| margin-bottom: -1px; | |
| border: 1px solid transparent; | |
| border-color: transparent; | |
| border-bottom: none; | |
| border-top-right-radius: var(--container-radius); | |
| border-top-left-radius: var(--container-radius); | |
| padding: var(--size-1) var(--size-4); | |
| color: var(--body-text-color-subdued); | |
| font-weight: var(--section-header-text-weight); | |
| font-size: var(--section-header-text-size); | |
| } | |
| button:disabled { | |
| color: var(--body-text-color-subdued); | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| button:hover { | |
| color: var(--body-text-color); | |
| } | |
| .selected { | |
| border-color: var(--border-color-primary); | |
| background: var(--background-fill-primary); | |
| color: var(--body-text-color); | |
| } | |
| .bar { | |
| display: block; | |
| position: absolute; | |
| bottom: -2px; | |
| left: 0; | |
| z-index: 999; | |
| background: var(--background-fill-primary); | |
| width: 100%; | |
| height: 2px; | |
| content: ""; | |
| } | |
| </style> | |