|
<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> |
|
|