@gradio/tabs 0.5.9 → 0.5.11

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @gradio/tabs
2
2
 
3
+ ## 0.5.11
4
+
5
+ ### Fixes
6
+
7
+ - [#13240](https://github.com/gradio-app/gradio/pull/13240) [`0d670ad`](https://github.com/gradio-app/gradio/commit/0d670adf41a0b510f7fd745495dce1664d38f0e5) - Fix browser freeze when a dataframe's value is set (e.g. via a tab select event), and only dispatch the tabs select event when the selected tab actually changes. Thanks @freddyaboulton!
8
+
9
+ ## 0.5.10
10
+
11
+ ### Fixes
12
+
13
+ - [#13303](https://github.com/gradio-app/gradio/pull/13303) [`79c5776`](https://github.com/gradio-app/gradio/commit/79c577680978adfb5eeaa01944635e14dc67e5f1) - Rework `Dataframe` wrapping, truncation, and column width sizing. Thanks @pngwn!
14
+
3
15
  ## 0.5.9
4
16
 
5
17
  ### Fixes
package/Index.svelte CHANGED
@@ -7,24 +7,26 @@
7
7
  import Tabs from "./shared/Tabs.svelte";
8
8
  import Walkthrough from "./shared/Walkthrough.svelte";
9
9
  import type { TabsProps, TabsEvents } from "./types";
10
- import { untrack } from "svelte";
11
10
 
12
11
  let props = $props();
13
12
  const gradio = new Gradio<TabsEvents, TabsProps>(props);
14
13
 
14
+ let old_selected = gradio.props.selected;
15
+
15
16
  $effect(() => {
16
- if (gradio.props.selected) {
17
- untrack(() => {
18
- const i = gradio.props.initial_tabs.findIndex(
19
- (t) => t.id === gradio.props.selected
20
- );
17
+ if (old_selected !== gradio.props.selected) {
18
+ const i = gradio.props.initial_tabs.findIndex(
19
+ (t) => t.id === gradio.props.selected
20
+ );
21
+ if (i >= 0) {
21
22
  gradio.dispatch("gradio_tab_select", {
22
23
  value: gradio.props.initial_tabs[i].label,
23
24
  index: i,
24
25
  id: gradio.props.initial_tabs[i].id,
25
26
  component_id: gradio.props.initial_tabs[i].component_id
26
27
  });
27
- });
28
+ }
29
+ old_selected = gradio.props.selected;
28
30
  }
29
31
  });
30
32
  </script>
package/dist/Index.svelte CHANGED
@@ -7,24 +7,26 @@
7
7
  import Tabs from "./shared/Tabs.svelte";
8
8
  import Walkthrough from "./shared/Walkthrough.svelte";
9
9
  import type { TabsProps, TabsEvents } from "./types";
10
- import { untrack } from "svelte";
11
10
 
12
11
  let props = $props();
13
12
  const gradio = new Gradio<TabsEvents, TabsProps>(props);
14
13
 
14
+ let old_selected = gradio.props.selected;
15
+
15
16
  $effect(() => {
16
- if (gradio.props.selected) {
17
- untrack(() => {
18
- const i = gradio.props.initial_tabs.findIndex(
19
- (t) => t.id === gradio.props.selected
20
- );
17
+ if (old_selected !== gradio.props.selected) {
18
+ const i = gradio.props.initial_tabs.findIndex(
19
+ (t) => t.id === gradio.props.selected
20
+ );
21
+ if (i >= 0) {
21
22
  gradio.dispatch("gradio_tab_select", {
22
23
  value: gradio.props.initial_tabs[i].label,
23
24
  index: i,
24
25
  id: gradio.props.initial_tabs[i].id,
25
26
  component_id: gradio.props.initial_tabs[i].component_id
26
27
  });
27
- });
28
+ }
29
+ old_selected = gradio.props.selected;
28
30
  }
29
31
  });
30
32
  </script>
@@ -37,16 +37,20 @@
37
37
 
38
38
  // When initial_tabs changes (e.g. a non-mounted tab's props were updated),
39
39
  // sync the internal tabs array so the tab buttons reflect the new state.
40
- // Using a function call so the $: dependency is only on initial_tabs,
41
- // not on tabs (which would cause a loop with register_tab).
40
+ // The tabs mutation is deferred via tick() because in Svelte 5 legacy mode
41
+ // $: effects track all reads inside called functions writing tabs[i]
42
+ // through the $state proxy would track `tabs` as a dependency, creating
43
+ // an infinite self-triggering loop.
42
44
  $: _sync_tabs(initial_tabs);
43
45
 
44
46
  function _sync_tabs(new_tabs: Tab[]): void {
45
- for (let i = 0; i < new_tabs.length; i++) {
46
- if (new_tabs[i] && !mounted_tab_orders.has(i)) {
47
- tabs[i] = new_tabs[i];
47
+ tick().then(() => {
48
+ for (let i = 0; i < new_tabs.length; i++) {
49
+ if (new_tabs[i] && !mounted_tab_orders.has(i)) {
50
+ tabs[i] = new_tabs[i];
51
+ }
48
52
  }
49
- }
53
+ });
50
54
  }
51
55
 
52
56
  $: has_tabs = tabs.length > 0;
@@ -70,10 +74,11 @@
70
74
 
71
75
  onMount(() => {
72
76
  if (!tab_nav_el) return;
73
- const observer = new IntersectionObserver((entries) => {
77
+ const ro = new ResizeObserver(() => {
74
78
  handle_menu_overflow();
75
79
  });
76
- observer.observe(tab_nav_el);
80
+ ro.observe(tab_nav_el);
81
+ return () => ro.disconnect();
77
82
  });
78
83
 
79
84
  setContext(TABS, {
@@ -128,33 +133,42 @@
128
133
  }
129
134
  }
130
135
 
136
+ // approximate width of the "..." overflow button including margin
137
+ const OVERFLOW_BTN_RESERVE = 48;
138
+
131
139
  async function handle_menu_overflow(): Promise<void> {
132
140
  if (!tab_nav_el) return;
133
141
 
134
142
  await tick();
135
- const tab_nav_size = tab_nav_el.getBoundingClientRect();
143
+ await new Promise((r) => requestAnimationFrame(r));
144
+
145
+ const available = tab_nav_el.clientWidth;
136
146
 
137
- let max_width = tab_nav_size.width;
138
- const tab_sizes = get_tab_sizes(tabs, tab_els);
139
- let last_visible_index = 0;
140
- const offset = tab_nav_size.left;
147
+ let cumulative = 0;
148
+ let split_index = tabs.length;
141
149
 
142
- for (let i = tabs.length - 1; i >= 0; i--) {
150
+ for (let i = 0; i < tabs.length; i++) {
143
151
  const tab = tabs[i];
144
- if (!tab) continue;
145
- const tab_rect = tab_sizes[tab.id];
146
- if (!tab_rect) continue;
147
- if (tab_rect.right - offset < max_width) {
148
- last_visible_index = i;
152
+ if (!tab || tab.visible === false || tab.visible === "hidden") continue;
153
+ const el = tab_els[tab.id];
154
+ if (!el) continue;
155
+ cumulative += el.getBoundingClientRect().width;
156
+ const has_more = tabs
157
+ .slice(i + 1)
158
+ .some((t) => t && t.visible !== false && t.visible !== "hidden");
159
+ const limit = has_more ? available - OVERFLOW_BTN_RESERVE : available;
160
+ if (cumulative > limit) {
161
+ split_index = i;
149
162
  break;
150
163
  }
151
164
  }
152
165
 
153
- overflow_tabs = tabs.slice(last_visible_index + 1);
154
- visible_tabs = tabs.slice(0, last_visible_index + 1);
166
+ visible_tabs = tabs.slice(0, split_index);
167
+ overflow_tabs = tabs.slice(split_index);
155
168
 
156
169
  overflow_has_selected_tab = handle_overflow_has_selected_tab($selected_tab);
157
- is_overflowing = overflow_tabs.length > 0;
170
+ is_overflowing =
171
+ overflow_tabs.filter((t) => t && t.visible !== false).length > 0;
158
172
  }
159
173
 
160
174
  $: overflow_has_selected_tab =
@@ -167,18 +181,6 @@
167
181
  return overflow_tabs.some((t) => t?.id === selected_tab);
168
182
  }
169
183
 
170
- function get_tab_sizes(
171
- tabs: (Tab | null)[],
172
- tab_els: Record<string | number, HTMLElement>
173
- ): Record<string | number, DOMRect> {
174
- const tab_sizes: Record<string | number, DOMRect> = {};
175
- tabs.forEach((tab) => {
176
- if (!tab) return;
177
- tab_sizes[tab.id] = tab_els[tab.id]?.getBoundingClientRect();
178
- });
179
- return tab_sizes;
180
- }
181
-
182
184
  $: tab_scale =
183
185
  tabs[$selected_tab_index >= 0 ? $selected_tab_index : 0]?.scale;
184
186
  </script>
@@ -277,9 +279,7 @@
277
279
  <style>
278
280
  .tabs {
279
281
  position: relative;
280
- display: flex;
281
282
  flex-direction: column;
282
- gap: var(--layout-gap);
283
283
  }
284
284
 
285
285
  .hide {
@@ -297,6 +297,7 @@
297
297
  position: relative;
298
298
  height: var(--size-8);
299
299
  padding-bottom: var(--size-2);
300
+ margin-bottom: var(--layout-gap);
300
301
  }
301
302
 
302
303
  .tab-container {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/tabs",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -37,16 +37,20 @@
37
37
 
38
38
  // When initial_tabs changes (e.g. a non-mounted tab's props were updated),
39
39
  // sync the internal tabs array so the tab buttons reflect the new state.
40
- // Using a function call so the $: dependency is only on initial_tabs,
41
- // not on tabs (which would cause a loop with register_tab).
40
+ // The tabs mutation is deferred via tick() because in Svelte 5 legacy mode
41
+ // $: effects track all reads inside called functions writing tabs[i]
42
+ // through the $state proxy would track `tabs` as a dependency, creating
43
+ // an infinite self-triggering loop.
42
44
  $: _sync_tabs(initial_tabs);
43
45
 
44
46
  function _sync_tabs(new_tabs: Tab[]): void {
45
- for (let i = 0; i < new_tabs.length; i++) {
46
- if (new_tabs[i] && !mounted_tab_orders.has(i)) {
47
- tabs[i] = new_tabs[i];
47
+ tick().then(() => {
48
+ for (let i = 0; i < new_tabs.length; i++) {
49
+ if (new_tabs[i] && !mounted_tab_orders.has(i)) {
50
+ tabs[i] = new_tabs[i];
51
+ }
48
52
  }
49
- }
53
+ });
50
54
  }
51
55
 
52
56
  $: has_tabs = tabs.length > 0;
@@ -70,10 +74,11 @@
70
74
 
71
75
  onMount(() => {
72
76
  if (!tab_nav_el) return;
73
- const observer = new IntersectionObserver((entries) => {
77
+ const ro = new ResizeObserver(() => {
74
78
  handle_menu_overflow();
75
79
  });
76
- observer.observe(tab_nav_el);
80
+ ro.observe(tab_nav_el);
81
+ return () => ro.disconnect();
77
82
  });
78
83
 
79
84
  setContext(TABS, {
@@ -128,33 +133,42 @@
128
133
  }
129
134
  }
130
135
 
136
+ // approximate width of the "..." overflow button including margin
137
+ const OVERFLOW_BTN_RESERVE = 48;
138
+
131
139
  async function handle_menu_overflow(): Promise<void> {
132
140
  if (!tab_nav_el) return;
133
141
 
134
142
  await tick();
135
- const tab_nav_size = tab_nav_el.getBoundingClientRect();
143
+ await new Promise((r) => requestAnimationFrame(r));
144
+
145
+ const available = tab_nav_el.clientWidth;
136
146
 
137
- let max_width = tab_nav_size.width;
138
- const tab_sizes = get_tab_sizes(tabs, tab_els);
139
- let last_visible_index = 0;
140
- const offset = tab_nav_size.left;
147
+ let cumulative = 0;
148
+ let split_index = tabs.length;
141
149
 
142
- for (let i = tabs.length - 1; i >= 0; i--) {
150
+ for (let i = 0; i < tabs.length; i++) {
143
151
  const tab = tabs[i];
144
- if (!tab) continue;
145
- const tab_rect = tab_sizes[tab.id];
146
- if (!tab_rect) continue;
147
- if (tab_rect.right - offset < max_width) {
148
- last_visible_index = i;
152
+ if (!tab || tab.visible === false || tab.visible === "hidden") continue;
153
+ const el = tab_els[tab.id];
154
+ if (!el) continue;
155
+ cumulative += el.getBoundingClientRect().width;
156
+ const has_more = tabs
157
+ .slice(i + 1)
158
+ .some((t) => t && t.visible !== false && t.visible !== "hidden");
159
+ const limit = has_more ? available - OVERFLOW_BTN_RESERVE : available;
160
+ if (cumulative > limit) {
161
+ split_index = i;
149
162
  break;
150
163
  }
151
164
  }
152
165
 
153
- overflow_tabs = tabs.slice(last_visible_index + 1);
154
- visible_tabs = tabs.slice(0, last_visible_index + 1);
166
+ visible_tabs = tabs.slice(0, split_index);
167
+ overflow_tabs = tabs.slice(split_index);
155
168
 
156
169
  overflow_has_selected_tab = handle_overflow_has_selected_tab($selected_tab);
157
- is_overflowing = overflow_tabs.length > 0;
170
+ is_overflowing =
171
+ overflow_tabs.filter((t) => t && t.visible !== false).length > 0;
158
172
  }
159
173
 
160
174
  $: overflow_has_selected_tab =
@@ -167,18 +181,6 @@
167
181
  return overflow_tabs.some((t) => t?.id === selected_tab);
168
182
  }
169
183
 
170
- function get_tab_sizes(
171
- tabs: (Tab | null)[],
172
- tab_els: Record<string | number, HTMLElement>
173
- ): Record<string | number, DOMRect> {
174
- const tab_sizes: Record<string | number, DOMRect> = {};
175
- tabs.forEach((tab) => {
176
- if (!tab) return;
177
- tab_sizes[tab.id] = tab_els[tab.id]?.getBoundingClientRect();
178
- });
179
- return tab_sizes;
180
- }
181
-
182
184
  $: tab_scale =
183
185
  tabs[$selected_tab_index >= 0 ? $selected_tab_index : 0]?.scale;
184
186
  </script>
@@ -277,9 +279,7 @@
277
279
  <style>
278
280
  .tabs {
279
281
  position: relative;
280
- display: flex;
281
282
  flex-direction: column;
282
- gap: var(--layout-gap);
283
283
  }
284
284
 
285
285
  .hide {
@@ -297,6 +297,7 @@
297
297
  position: relative;
298
298
  height: var(--size-8);
299
299
  padding-bottom: var(--size-2);
300
+ margin-bottom: var(--layout-gap);
300
301
  }
301
302
 
302
303
  .tab-container {