@gradio/tabs 0.4.5 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,410 @@
1
+ <script context="module">import { TABS } from "./Tabs.svelte";
2
+ </script>
3
+
4
+ <script>import { setContext, createEventDispatcher, tick, onMount } from "svelte";
5
+ import { writable } from "svelte/store";
6
+ export let visible = true;
7
+ export let elem_id = "";
8
+ export let elem_classes = [];
9
+ export let selected;
10
+ export let initial_tabs;
11
+ let tabs = [...initial_tabs];
12
+ let stepper_container;
13
+ let show_labels_for_all = true;
14
+ let measurement_container;
15
+ let step_buttons = [];
16
+ let step_labels = [];
17
+ let label_height = 0;
18
+ let compact = false;
19
+ let recompute_overflow = true;
20
+ $: has_tabs = tabs.length > 0;
21
+ const selected_tab = writable(
22
+ selected || tabs[0]?.id || false
23
+ );
24
+ const selected_tab_index = writable(
25
+ tabs.findIndex((t) => t?.id === selected) || 0
26
+ );
27
+ const dispatch = createEventDispatcher();
28
+ async function check_overflow() {
29
+ if (!stepper_container || !measurement_container || !recompute_overflow)
30
+ return;
31
+ recompute_overflow = false;
32
+ await tick();
33
+ show_labels_for_all = true;
34
+ await tick();
35
+ const SEP_WIDTH = 50;
36
+ const button_width = step_buttons[0].getBoundingClientRect().width * step_buttons.length + SEP_WIDTH * (step_buttons.length - 1);
37
+ const containerWidth = stepper_container.getBoundingClientRect().width;
38
+ const does_it_fit = button_width < containerWidth;
39
+ if (!does_it_fit) {
40
+ show_labels_for_all = false;
41
+ compact = true;
42
+ return;
43
+ }
44
+ let max_height = 0;
45
+ let is_overlapping = false;
46
+ let last_right = 0;
47
+ for (const label of step_labels) {
48
+ const { height, width, left, right } = label.getBoundingClientRect();
49
+ if (height > max_height) {
50
+ max_height = height;
51
+ }
52
+ if (last_right && left - 10 < last_right && !is_overlapping) {
53
+ is_overlapping = true;
54
+ }
55
+ last_right = right;
56
+ }
57
+ label_height = max_height;
58
+ if (is_overlapping) {
59
+ show_labels_for_all = false;
60
+ }
61
+ }
62
+ let last_width = 0;
63
+ onMount(() => {
64
+ check_overflow();
65
+ const observer = new ResizeObserver((entries) => {
66
+ if (entries[0].contentRect.width === last_width) return;
67
+ last_width = entries[0].contentRect.width;
68
+ compact = false;
69
+ recompute_overflow = true;
70
+ check_overflow();
71
+ });
72
+ if (stepper_container) {
73
+ observer.observe(stepper_container);
74
+ }
75
+ return () => {
76
+ observer.disconnect();
77
+ };
78
+ });
79
+ setContext(TABS, {
80
+ register_tab: (tab, order) => {
81
+ tabs[order] = tab;
82
+ if ($selected_tab === false && tab.visible && tab.interactive) {
83
+ $selected_tab = tab.id;
84
+ $selected_tab_index = order;
85
+ }
86
+ return order;
87
+ },
88
+ unregister_tab: (tab, order) => {
89
+ if ($selected_tab === tab.id) {
90
+ $selected_tab = tabs[0]?.id || false;
91
+ }
92
+ tabs[order] = null;
93
+ },
94
+ selected_tab,
95
+ selected_tab_index
96
+ });
97
+ function change_tab(id, index) {
98
+ const tab_to_activate = tabs.find((t) => t?.id === id);
99
+ if (id !== void 0 && tab_to_activate && tab_to_activate.interactive && tab_to_activate.visible && $selected_tab !== tab_to_activate.id) {
100
+ selected = id;
101
+ $selected_tab = id;
102
+ $selected_tab_index = tabs.findIndex((t) => t?.id === id);
103
+ dispatch("change");
104
+ }
105
+ }
106
+ $: tabs, selected !== null && change_tab(
107
+ selected,
108
+ tabs.findIndex((t) => t?.id === selected)
109
+ );
110
+ $: tabs, check_overflow();
111
+ $: $selected_tab_index, check_overflow();
112
+ $: tab_scale = tabs[$selected_tab_index >= 0 ? $selected_tab_index : 0]?.scale;
113
+ </script>
114
+
115
+ <svelte:window on:resize={check_overflow} />
116
+
117
+ <div
118
+ class="stepper {elem_classes.join(' ')}"
119
+ class:hide={!visible}
120
+ id={elem_id}
121
+ style:flex-grow={tab_scale}
122
+ class:compact
123
+ >
124
+ {#if has_tabs}
125
+ {#if compact}
126
+ <p class="step-title">
127
+ <strong>Step {($selected_tab_index || 0) + 1}/{tabs.length}:</strong>
128
+ {tabs[$selected_tab_index]?.label || "Walkthrough"}
129
+ </p>
130
+ {/if}
131
+ <div
132
+ class="stepper-wrapper"
133
+ bind:this={stepper_container}
134
+ style:--label-height={label_height + "px"}
135
+ >
136
+ <div
137
+ class="stepper-container"
138
+ bind:this={measurement_container}
139
+ role="tablist"
140
+ >
141
+ {#each tabs as t, i}
142
+ {#if t?.visible}
143
+ <div class="step-item">
144
+ <button
145
+ bind:this={step_buttons[i]}
146
+ role="tab"
147
+ class="step-button"
148
+ class:active={t.id === $selected_tab}
149
+ class:completed={t.id < $selected_tab}
150
+ class:pending={t.id > $selected_tab}
151
+ aria-selected={t.id === $selected_tab}
152
+ aria-controls={t.elem_id}
153
+ disabled={!t.interactive || i > $selected_tab_index}
154
+ aria-disabled={!t.interactive || i > $selected_tab_index}
155
+ id={t.elem_id ? t.elem_id + "-button" : null}
156
+ data-tab-id={t.id}
157
+ on:click={() => {
158
+ if (i <= $selected_tab_index && t.id !== $selected_tab) {
159
+ change_tab(t.id, i);
160
+ dispatch("select", { value: t.label, index: i });
161
+ }
162
+ }}
163
+ >
164
+ <span class="step-number">
165
+ {#if t.id < $selected_tab}
166
+ <svg
167
+ width="12"
168
+ height="10"
169
+ viewBox="0 0 12 10"
170
+ fill="none"
171
+ xmlns="http://www.w3.org/2000/svg"
172
+ >
173
+ <path
174
+ d="M1 5L4.5 8.5L11 1.5"
175
+ stroke="currentColor"
176
+ stroke-width="2"
177
+ stroke-linecap="round"
178
+ stroke-linejoin="round"
179
+ />
180
+ </svg>
181
+ {:else}
182
+ {i + 1}
183
+ {/if}
184
+ </span>
185
+ {#if !compact}
186
+ <span
187
+ bind:this={step_labels[i]}
188
+ class="step-label"
189
+ class:visible={show_labels_for_all ||
190
+ i === $selected_tab_index}
191
+ >
192
+ {t?.label !== undefined ? t?.label : "Step " + (i + 1)}
193
+ </span>
194
+ {/if}
195
+ </button>
196
+ </div>
197
+ {#if i < tabs.length - 1 && !compact}
198
+ <div
199
+ class="step-connector"
200
+ class:completed={i < $selected_tab_index}
201
+ ></div>
202
+ {/if}
203
+ {/if}
204
+ {/each}
205
+ </div>
206
+ </div>
207
+ {/if}
208
+ <slot />
209
+ </div>
210
+
211
+ <style>
212
+ .stepper {
213
+ position: relative;
214
+ display: flex;
215
+ flex-direction: column;
216
+ gap: var(--layout-gap);
217
+ }
218
+
219
+ .compact.stepper {
220
+ gap: 0;
221
+ }
222
+
223
+ .hide {
224
+ display: none;
225
+ }
226
+
227
+ .stepper-wrapper {
228
+ display: flex;
229
+ align-items: center;
230
+ position: relative;
231
+ padding-top: var(--size-4);
232
+ padding-bottom: calc(var(--label-height) + var(--size-4));
233
+ }
234
+
235
+ .compact .stepper-wrapper {
236
+ padding-top: var(--size-2);
237
+ padding-bottom: var(--size-6);
238
+ }
239
+
240
+ .stepper-container {
241
+ display: flex;
242
+ justify-content: space-between;
243
+ align-items: flex-start;
244
+ width: 100%;
245
+ position: relative;
246
+ padding: var(--size-2);
247
+ gap: var(--size-1);
248
+ }
249
+
250
+ .compact .stepper-container {
251
+ justify-content: center;
252
+ gap: 2px;
253
+ padding: 0;
254
+ }
255
+
256
+ .step-item {
257
+ display: flex;
258
+ align-items: center;
259
+ justify-content: center;
260
+ flex: 1 1 0;
261
+ position: relative;
262
+ }
263
+
264
+ .compact .step-item {
265
+ /* flex: 0 0 auto; */
266
+ width: 100%;
267
+ }
268
+
269
+ .step-button {
270
+ position: relative;
271
+ display: flex;
272
+ flex-direction: column;
273
+ align-items: center;
274
+ justify-content: center;
275
+ gap: var(--size-1);
276
+
277
+ border: none;
278
+ background: transparent;
279
+ cursor: pointer;
280
+ border-radius: var(--radius-md);
281
+ transition: background-color 0.2s ease;
282
+ font-size: var(--text-sm);
283
+ color: var(--body-text-color-subdued);
284
+ white-space: nowrap;
285
+ z-index: 1;
286
+ position: relative;
287
+ }
288
+
289
+ .compact .step-button {
290
+ padding: 0;
291
+ width: 100%;
292
+ border: none;
293
+ }
294
+
295
+ .compact .step-number {
296
+ height: 10px;
297
+ width: 100%;
298
+ border-radius: 0;
299
+ border: none;
300
+ }
301
+
302
+ .step-button:hover:not(:disabled) {
303
+ background-color: var(--background-fill-secondary);
304
+ }
305
+
306
+ .step-button:disabled {
307
+ cursor: not-allowed;
308
+ opacity: 0.5;
309
+ }
310
+
311
+ .step-button.active {
312
+ color: var(--body-text-color);
313
+ }
314
+
315
+ .step-button.completed {
316
+ color: var(--body-text-color);
317
+ }
318
+
319
+ .step-button.pending {
320
+ color: var(--body-text-color-subdued);
321
+ }
322
+
323
+ .step-number {
324
+ display: flex;
325
+ align-items: center;
326
+ justify-content: center;
327
+ width: 32px;
328
+ height: 32px;
329
+ border-radius: 50%;
330
+ font-size: var(--text-sm);
331
+ font-weight: var(--weight-semibold);
332
+ transition: background-color 0.2s ease;
333
+ flex-shrink: 0;
334
+ }
335
+
336
+ .active .step-number {
337
+ background-color: var(--color-accent);
338
+ color: white;
339
+ box-shadow: 0 0 0 4px rgba(var(--color-accent-rgb), 0.1);
340
+ }
341
+
342
+ .completed .step-number {
343
+ background-color: var(--color-accent);
344
+ color: white;
345
+ }
346
+
347
+ .compact .completed .step-number {
348
+ color: transparent;
349
+ }
350
+
351
+ .compact .pending .step-number {
352
+ color: transparent;
353
+ background-color: var(--body-text-color-subdued);
354
+ }
355
+
356
+ .compact .active .step-number {
357
+ color: transparent;
358
+ }
359
+
360
+ .pending .step-number {
361
+ background-color: var(--button-secondary-background-fill);
362
+ color: var(--button-secondary-text-color);
363
+ }
364
+
365
+ .compact .step-item:last-child .step-number {
366
+ border-top-right-radius: var(--radius-xs);
367
+ border-bottom-right-radius: var(--radius-xs);
368
+ }
369
+ .compact .step-item:first-child .step-number {
370
+ border-top-left-radius: var(--radius-xs);
371
+ border-bottom-left-radius: var(--radius-xs);
372
+ }
373
+
374
+ .step-label {
375
+ font-size: var(--text-md);
376
+ line-height: 1.2;
377
+ text-align: center;
378
+ max-width: 120px;
379
+ position: absolute;
380
+ bottom: -20px;
381
+ display: none;
382
+ }
383
+
384
+ .step-label.visible {
385
+ display: block;
386
+ }
387
+
388
+ .step-connector {
389
+ width: 100%;
390
+ height: 2px;
391
+ background-color: var(--border-color-primary);
392
+ transition: background-color 0.3s ease;
393
+ z-index: 0;
394
+ transform: translate(0, 15px);
395
+ }
396
+
397
+ .step-connector.completed {
398
+ background-color: var(--color-accent);
399
+ }
400
+
401
+ :global(.dark) .pending .step-number {
402
+ background-color: var(--neutral-800);
403
+ color: var(--neutral-400);
404
+ border-color: var(--neutral-600);
405
+ }
406
+
407
+ :global(.dark) .step-connector {
408
+ background-color: var(--neutral-600);
409
+ }
410
+ </style>
@@ -0,0 +1,29 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import { type Tab } from "./Tabs.svelte";
3
+ import type { SelectData } from "@gradio/utils";
4
+ declare const __propDef: {
5
+ props: {
6
+ visible?: boolean | "hidden";
7
+ elem_id?: string;
8
+ elem_classes?: string[];
9
+ selected: number | string;
10
+ initial_tabs: Tab[];
11
+ };
12
+ events: {
13
+ change: CustomEvent<undefined>;
14
+ select: CustomEvent<SelectData>;
15
+ } & {
16
+ [evt: string]: CustomEvent<any>;
17
+ };
18
+ slots: {
19
+ default: {};
20
+ };
21
+ exports?: {} | undefined;
22
+ bindings?: string | undefined;
23
+ };
24
+ export type WalkthroughProps = typeof __propDef.props;
25
+ export type WalkthroughEvents = typeof __propDef.events;
26
+ export type WalkthroughSlots = typeof __propDef.slots;
27
+ export default class Walkthrough extends SvelteComponent<WalkthroughProps, WalkthroughEvents, WalkthroughSlots> {
28
+ }
29
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/tabs",
3
- "version": "0.4.5",
3
+ "version": "0.5.1",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -20,7 +20,7 @@
20
20
  "@gradio/utils": "^0.10.2"
21
21
  },
22
22
  "devDependencies": {
23
- "@gradio/preview": "^0.13.1"
23
+ "@gradio/preview": "^0.14.0"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "svelte": "^4.0.0"
@@ -5,7 +5,7 @@
5
5
  label: string;
6
6
  id: string | number;
7
7
  elem_id: string | undefined;
8
- visible: boolean;
8
+ visible: boolean | "hidden";
9
9
  interactive: boolean;
10
10
  scale: number | null;
11
11
  }
@@ -17,7 +17,7 @@
17
17
  import { writable } from "svelte/store";
18
18
  import type { SelectData } from "@gradio/utils";
19
19
 
20
- export let visible = true;
20
+ export let visible: boolean | "hidden" = true;
21
21
  export let elem_id = "";
22
22
  export let elem_classes: string[] = [];
23
23
  export let selected: number | string;
@@ -49,6 +49,7 @@
49
49
  let tab_els: Record<string | number, HTMLElement> = {};
50
50
 
51
51
  onMount(() => {
52
+ if (!tab_nav_el) return;
52
53
  const observer = new IntersectionObserver((entries) => {
53
54
  handle_menu_overflow();
54
55
  });
@@ -59,7 +60,7 @@
59
60
  register_tab: (tab: Tab, order: number) => {
60
61
  tabs[order] = tab;
61
62
 
62
- if ($selected_tab === false && tab.visible && tab.interactive) {
63
+ if ($selected_tab === false && tab.visible !== false && tab.interactive) {
63
64
  $selected_tab = tab.id;
64
65
  $selected_tab_index = order;
65
66
  }
@@ -81,7 +82,7 @@
81
82
  id !== undefined &&
82
83
  tab_to_activate &&
83
84
  tab_to_activate.interactive &&
84
- tab_to_activate.visible &&
85
+ tab_to_activate.visible !== false &&
85
86
  $selected_tab !== tab_to_activate.id
86
87
  ) {
87
88
  selected = id;
@@ -167,7 +168,8 @@
167
168
 
168
169
  <div
169
170
  class="tabs {elem_classes.join(' ')}"
170
- class:hide={!visible}
171
+ class:hide={visible === false}
172
+ class:hidden={visible === "hidden"}
171
173
  id={elem_id}
172
174
  style:flex-grow={tab_scale}
173
175
  >
@@ -175,7 +177,7 @@
175
177
  <div class="tab-wrapper">
176
178
  <div class="tab-container visually-hidden" aria-hidden="true">
177
179
  {#each tabs as t, i}
178
- {#if t?.visible}
180
+ {#if t && t?.visible !== false && t?.visible !== "hidden"}
179
181
  <button bind:this={tab_els[t.id]}>
180
182
  {t?.label}
181
183
  </button>
@@ -184,7 +186,7 @@
184
186
  </div>
185
187
  <div class="tab-container" bind:this={tab_nav_el} role="tablist">
186
188
  {#each visible_tabs as t, i}
187
- {#if t?.visible}
189
+ {#if t && t?.visible !== false}
188
190
  <button
189
191
  role="tab"
190
192
  class:selected={t.id === $selected_tab}
@@ -208,7 +210,8 @@
208
210
  </div>
209
211
  <span
210
212
  class="overflow-menu"
211
- class:hide={!is_overflowing || !overflow_tabs.some((t) => t?.visible)}
213
+ class:hide={!is_overflowing ||
214
+ !overflow_tabs.some((t) => t?.visible !== false)}
212
215
  bind:this={overflow_menu}
213
216
  >
214
217
  <button
@@ -220,7 +223,7 @@
220
223
  </button>
221
224
  <div class="overflow-dropdown" class:hide={!overflow_menu_open}>
222
225
  {#each overflow_tabs as t}
223
- {#if t?.visible}
226
+ {#if t?.visible !== false}
224
227
  <button
225
228
  on:click={() => change_tab(t?.id)}
226
229
  class:selected={t?.id === $selected_tab}
@@ -248,6 +251,10 @@
248
251
  display: none;
249
252
  }
250
253
 
254
+ .hidden {
255
+ display: none !important;
256
+ }
257
+
251
258
  .tab-wrapper {
252
259
  display: flex;
253
260
  align-items: center;