@gradio/core 1.0.1 → 1.1.0

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.
@@ -1,6 +1,8 @@
1
1
  import { determine_interactivity, get_component, get_inputs_outputs } from "./init_utils";
2
2
  import { translate_if_needed } from "./i18n";
3
3
  import { tick } from "svelte";
4
+ import { dequal } from "dequal";
5
+ import {} from "@gradio/utils";
4
6
  import { allowed_shared_props } from "@gradio/utils";
5
7
  import { Client } from "@gradio/client";
6
8
  const type_map = {
@@ -21,6 +23,7 @@ export class AppTree {
21
23
  client;
22
24
  /** the root node of the processed layout tree */
23
25
  root = $state();
26
+ root_untracked;
24
27
  /** a set of all component IDs that are inputs to dependencies */
25
28
  #input_ids = new Set();
26
29
  /** a set of all component IDs that are outputs of dependencies */
@@ -29,13 +32,15 @@ export class AppTree {
29
32
  #pending_components = [];
30
33
  #get_callbacks = new Map();
31
34
  #set_callbacks = new Map();
35
+ #event_dispatcher;
32
36
  component_ids;
33
37
  initial_tabs = {};
34
38
  components_to_register = new Set();
35
39
  ready;
36
40
  ready_resolve;
37
41
  resolved = false;
38
- constructor(components, layout, dependencies, config, app, reactive_formatter) {
42
+ #hidden_on_startup = new Set();
43
+ constructor(components, layout, dependencies, config, app, reactive_formatter, event_dispatcher) {
39
44
  this.ready = new Promise((resolve) => {
40
45
  this.ready_resolve = resolve;
41
46
  });
@@ -66,6 +71,8 @@ export class AppTree {
66
71
  this.initial_tabs = {};
67
72
  gather_initial_tabs(this.root, this.initial_tabs);
68
73
  this.postprocess(this.root);
74
+ this.#event_dispatcher = event_dispatcher;
75
+ this.root_untracked = this.root;
69
76
  }
70
77
  reload(components, layout, dependencies, config) {
71
78
  this.#layout_payload = layout;
@@ -126,7 +133,8 @@ export class AppTree {
126
133
  (node) => handle_empty_forms(node, this.components_to_register),
127
134
  (node) => translate_props(node),
128
135
  (node) => apply_initial_tabs(node, this.initial_tabs),
129
- (node) => this.find_attached_events(node, this.#dependency_payload)
136
+ (node) => this.find_attached_events(node, this.#dependency_payload),
137
+ (node) => untrack_children_of_closed_accordions_or_inactive_tabs(node, this.components_to_register, this.#hidden_on_startup)
130
138
  ]);
131
139
  }
132
140
  find_attached_events(node, dependencies) {
@@ -211,7 +219,8 @@ export class AppTree {
211
219
  : null,
212
220
  key: component.key,
213
221
  rendered_in: component.rendered_in,
214
- documentation: component.documentation
222
+ documentation: component.documentation,
223
+ original_visibility: processed_props.shared_props.visible
215
224
  };
216
225
  return node;
217
226
  }
@@ -230,6 +239,15 @@ export class AppTree {
230
239
  }
231
240
  n.children = subtree.children;
232
241
  }
242
+ async update_visibility(node, new_state) {
243
+ node.children.forEach((child) => {
244
+ const _set_data = this.#set_callbacks.get(child.id);
245
+ if (_set_data) {
246
+ _set_data(new_state);
247
+ }
248
+ this.update_visibility(child, new_state);
249
+ });
250
+ }
233
251
  /*
234
252
  * Updates the state of a component by its ID
235
253
  * @param id the ID of the component to update
@@ -248,20 +266,44 @@ export class AppTree {
248
266
  this.root = this.traverse(this.root, [
249
267
  //@ts-ignore
250
268
  (n) => set_visibility_for_updated_node(n, id, new_state.visible),
269
+ //@ts-ignore
270
+ (n) => update_parent_visibility(n, id, new_state.visible),
251
271
  (n) => handle_visibility(n, this.#config.api_url)
252
272
  ]);
273
+ await tick();
253
274
  already_updated_visibility = true;
254
275
  }
255
276
  const _set_data = this.#set_callbacks.get(id);
256
- if (!_set_data)
257
- return;
258
- _set_data(new_state);
277
+ if (!_set_data) {
278
+ const old_value = node?.props.props.value;
279
+ // @ts-ignore
280
+ const new_props = create_props_shared_props(new_state);
281
+ node.props.shared_props = {
282
+ ...node?.props.shared_props,
283
+ ...new_props.shared_props
284
+ };
285
+ node.props.props = { ...node?.props.props, ...new_props.props };
286
+ if ("value" in new_state && !dequal(old_value, new_state.value)) {
287
+ this.#event_dispatcher(id, "change", null);
288
+ }
289
+ }
290
+ else if (_set_data) {
291
+ _set_data(new_state);
292
+ }
259
293
  if (!check_visibility || already_updated_visibility)
260
294
  return;
261
295
  // need to let the UI settle before traversing again
262
296
  // otherwise there could be
263
297
  await tick();
264
- this.root = this.traverse(this.root, (n) => handle_visibility(n, this.#config.api_url));
298
+ // Update the visibility in a way that does not
299
+ // re-render the root/tree. Doing that would nuke
300
+ // any values currently in the UI.
301
+ // @ts-ignore
302
+ await this.update_visibility(node, new_state);
303
+ const parent_node = find_parent(this.root, id);
304
+ if (parent_node)
305
+ // @ts-ignore
306
+ update_parent_visibility(parent_node, id, new_state.visible);
265
307
  }
266
308
  /**
267
309
  * Gets the current state of a component by its ID
@@ -270,15 +312,34 @@ export class AppTree {
270
312
  */
271
313
  async get_state(id) {
272
314
  const _get_data = this.#get_callbacks.get(id);
273
- const component = this.#component_payload.find((c) => c.id === id);
315
+ const component = find_node_by_id(this.root, id);
274
316
  if (!_get_data && !component)
275
317
  return null;
276
318
  if (_get_data)
277
319
  return await _get_data();
278
320
  if (component)
279
- return Promise.resolve({ value: component.props.value });
321
+ return Promise.resolve({ value: component.props.props.value });
280
322
  return null;
281
323
  }
324
+ async render_previously_invisible_children(id) {
325
+ this.root = this.traverse(this.root, [
326
+ (node) => {
327
+ if (node.id === id) {
328
+ make_visible_if_not_rendered(node, this.#hidden_on_startup);
329
+ }
330
+ return node;
331
+ },
332
+ (node) => handle_visibility(node, this.#config.api_url)
333
+ ]);
334
+ }
335
+ }
336
+ function make_visible_if_not_rendered(node, hidden_on_startup) {
337
+ node.props.shared_props.visible = hidden_on_startup.has(node.id)
338
+ ? true
339
+ : node.props.shared_props.visible;
340
+ node.children.forEach((child) => {
341
+ make_visible_if_not_rendered(child, hidden_on_startup);
342
+ });
282
343
  }
283
344
  /**
284
345
  * Process the server function names and return a dictionary of functions
@@ -302,15 +363,7 @@ export function process_server_fn(id, server_fns, app) {
302
363
  return acc;
303
364
  }, {});
304
365
  }
305
- /**
306
- * Gathers the props for a component
307
- * @param id the ID of the component
308
- * @param props the props of the component
309
- * @param dependencies the component's dependencies
310
- * @param additional any additional props to include
311
- * @returns the gathered props as an object with `shared_props` and `props` keys
312
- */
313
- function gather_props(id, props, dependencies, client, api_url, additional = {}) {
366
+ function create_props_shared_props(props) {
314
367
  const _shared_props = {};
315
368
  const _props = {};
316
369
  for (const key in props) {
@@ -322,14 +375,24 @@ function gather_props(id, props, dependencies, client, api_url, additional = {})
322
375
  else if (allowed_shared_props.includes(key)) {
323
376
  const _key = key;
324
377
  _shared_props[_key] = props[key];
325
- if (_key === "server_fns") {
326
- _shared_props.server = process_server_fn(id, props.server_fns, client);
327
- }
328
378
  }
329
379
  else {
330
380
  _props[key] = props[key];
331
381
  }
332
382
  }
383
+ return { shared_props: _shared_props, props: _props };
384
+ }
385
+ /**
386
+ * Gathers the props for a component
387
+ * @param id the ID of the component
388
+ * @param props the props of the component
389
+ * @param dependencies the component's dependencies
390
+ * @param additional any additional props to include
391
+ * @returns the gathered props as an object with `shared_props` and `props` keys
392
+ */
393
+ function gather_props(id, props, dependencies, client, api_url, additional = {}) {
394
+ const { shared_props: _shared_props, props: _props } = create_props_shared_props(props);
395
+ _shared_props.server = process_server_fn(id, props.server_fns, client);
333
396
  for (const key in additional) {
334
397
  if (allowed_shared_props.includes(key)) {
335
398
  const _key = key;
@@ -385,6 +448,38 @@ function untrack_children_of_invisible_parents(node, components_to_register) {
385
448
  }
386
449
  return node;
387
450
  }
451
+ function mark_component_invisible_if_visible(node, hidden_on_startup) {
452
+ if (node.props.shared_props.visible === true) {
453
+ hidden_on_startup.add(node.id);
454
+ node.props.shared_props.visible = false;
455
+ }
456
+ node.children.forEach((child) => {
457
+ mark_component_invisible_if_visible(child, hidden_on_startup);
458
+ });
459
+ return node;
460
+ }
461
+ function untrack_children_of_closed_accordions_or_inactive_tabs(node, components_to_register, hidden_on_startup) {
462
+ // Check if the node is an accordion or tabs
463
+ if (node.type === "accordion" && node.props.props.open === false) {
464
+ _untrack(node, components_to_register);
465
+ if (node.children) {
466
+ node.children.forEach((child) => {
467
+ mark_component_invisible_if_visible(child, hidden_on_startup);
468
+ });
469
+ }
470
+ }
471
+ if (node.type === "tabs") {
472
+ node.children.forEach((child) => {
473
+ if (child.type === "tabitem" &&
474
+ child.props.props.id !==
475
+ (node.props.props.selected || node.props.props.initial_tabs[0].id)) {
476
+ _untrack(child, components_to_register);
477
+ mark_component_invisible_if_visible(child, hidden_on_startup);
478
+ }
479
+ });
480
+ }
481
+ return node;
482
+ }
388
483
  function handle_empty_forms(node, components_to_register) {
389
484
  // Check if the node is visible
390
485
  if (node.type === "form") {
@@ -397,6 +492,27 @@ function handle_empty_forms(node, components_to_register) {
397
492
  }
398
493
  return node;
399
494
  }
495
+ function update_parent_visibility(node, child_made_visible, visibility_state) {
496
+ // This function was added to address a tricky situation:
497
+ // Form components are wrapped in a Form component automatically.
498
+ // If all the children of the Form are invisible, the Form itself is marked invisible.
499
+ // in AppTree.postprocess -> handle_empty_forms
500
+ // This is to avoid rendering empty forms in the UI. They look ugly.
501
+ // So what happens when a child inside the Form is made visible again?
502
+ // The Form needs to become visible again too.
503
+ // If the child is made invisible, the form should be too if all other children are invisible.
504
+ // However, we are not doing this now since what we want to do is fetch the latest visibility of all
505
+ // the children from the UI. However, get_data only returns the props, not the shared props.
506
+ if (node.type === "form" &&
507
+ node.children.length &&
508
+ node.children.some((child) => child.id === child_made_visible)) {
509
+ if (visibility_state === true)
510
+ node.props.shared_props.visible = true;
511
+ else if (!visibility_state && node.children.length === 1)
512
+ node.props.shared_props.visible = "hidden";
513
+ }
514
+ return node;
515
+ }
400
516
  function translate_props(node) {
401
517
  const supported_props = [
402
518
  "description",
@@ -424,6 +540,9 @@ function apply_initial_tabs(node, initial_tabs) {
424
540
  const tabs = initial_tabs[node.id].sort((a, b) => a.order - b.order);
425
541
  node.props.props.initial_tabs = tabs;
426
542
  }
543
+ else if (node.type === "tabitem") {
544
+ node.props.props.component_id = node.id;
545
+ }
427
546
  return node;
428
547
  }
429
548
  function _gather_initial_tabs(node, initial_tabs, parent_tab_id, order) {
@@ -440,7 +559,8 @@ function _gather_initial_tabs(node, initial_tabs, parent_tab_id, order) {
440
559
  elem_id: node.props.shared_props.elem_id,
441
560
  visible: node.props.shared_props.visible,
442
561
  interactive: node.props.shared_props.interactive,
443
- scale: node.props.shared_props.scale || null
562
+ scale: node.props.shared_props.scale || null,
563
+ component_id: node.id
444
564
  });
445
565
  node.props.props.order = order;
446
566
  }
@@ -473,3 +593,17 @@ function find_node_by_id(tree, id) {
473
593
  }
474
594
  return null;
475
595
  }
596
+ function find_parent(tree, id) {
597
+ if (tree.children) {
598
+ for (const child of tree.children) {
599
+ if (child.id === id) {
600
+ return tree;
601
+ }
602
+ const result = find_parent(child, id);
603
+ if (result) {
604
+ return result;
605
+ }
606
+ }
607
+ }
608
+ return null;
609
+ }
@@ -28,6 +28,7 @@ export interface ProcessedComponentMeta {
28
28
  component_class_id: string;
29
29
  key: string | number | null;
30
30
  rendered_in?: number;
31
+ original_visibility: boolean | "hidden";
31
32
  }
32
33
  /** Dictates whether a dependency is continous and/or a generator */
33
34
  export interface DependencyTypes {
package/package.json CHANGED
@@ -1,67 +1,67 @@
1
1
  {
2
2
  "name": "@gradio/core",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "devDependencies": {
6
- "@gradio/annotatedimage": "^0.10.1",
7
- "@gradio/atoms": "^0.19.0",
8
- "@gradio/box": "^0.2.26",
9
- "@gradio/browserstate": "^0.3.3",
10
- "@gradio/button": "^0.5.14",
11
- "@gradio/checkbox": "^0.5.0",
12
- "@gradio/chatbot": "^0.28.0",
13
- "@gradio/client": "^2.0.0",
14
- "@gradio/audio": "^0.20.0",
15
- "@gradio/checkboxgroup": "^0.8.0",
16
- "@gradio/accordion": "^0.5.25",
17
- "@gradio/colorpicker": "^0.5.0",
18
- "@gradio/code": "^0.16.0",
19
- "@gradio/dataframe": "^0.21.0",
6
+ "@gradio/annotatedimage": "^0.11.0",
7
+ "@gradio/atoms": "^0.20.0",
8
+ "@gradio/browserstate": "^0.3.4",
9
+ "@gradio/accordion": "^0.5.27",
10
+ "@gradio/box": "^0.2.27",
11
+ "@gradio/audio": "^0.22.0",
12
+ "@gradio/button": "^0.6.1",
13
+ "@gradio/chatbot": "^0.29.0",
14
+ "@gradio/checkbox": "^0.6.0",
15
+ "@gradio/checkboxgroup": "^0.9.0",
16
+ "@gradio/client": "^2.0.1",
17
+ "@gradio/code": "^0.17.0",
18
+ "@gradio/colorpicker": "^0.5.2",
20
19
  "@gradio/column": "^0.3.0",
21
- "@gradio/dataset": "^0.5.0",
22
- "@gradio/datetime": "^0.3.23",
23
- "@gradio/downloadbutton": "^0.4.13",
24
- "@gradio/dropdown": "^0.10.6",
25
- "@gradio/fallback": "^0.4.30",
26
- "@gradio/file": "^0.13.1",
27
- "@gradio/form": "^0.2.27",
28
- "@gradio/fileexplorer": "^0.5.42",
29
- "@gradio/gallery": "^0.15.35",
30
- "@gradio/group": "^0.3.0",
31
- "@gradio/highlightedtext": "^0.9.14",
32
- "@gradio/html": "^0.8.0",
33
- "@gradio/image": "^0.24.0",
34
- "@gradio/imageeditor": "^0.18.2",
20
+ "@gradio/dataframe": "^0.21.2",
21
+ "@gradio/dataset": "^0.5.1",
22
+ "@gradio/datetime": "^0.4.0",
23
+ "@gradio/downloadbutton": "^0.4.15",
24
+ "@gradio/fallback": "^0.4.31",
25
+ "@gradio/dropdown": "^0.11.0",
26
+ "@gradio/file": "^0.14.0",
27
+ "@gradio/gallery": "^0.16.0",
28
+ "@gradio/fileexplorer": "^0.6.0",
29
+ "@gradio/form": "^0.2.28",
30
+ "@gradio/highlightedtext": "^0.10.0",
31
+ "@gradio/group": "^0.3.1",
35
32
  "@gradio/icons": "^0.15.0",
36
- "@gradio/imageslider": "^0.3.1",
37
- "@gradio/json": "^0.5.32",
38
- "@gradio/markdown": "^0.13.23",
39
- "@gradio/label": "^0.5.22",
40
- "@gradio/model3d": "^0.15.1",
41
- "@gradio/multimodaltextbox": "^0.11.0",
42
- "@gradio/nativeplot": "^0.9.0",
43
- "@gradio/number": "^0.7.2",
44
- "@gradio/paramviewer": "^0.9.0",
45
- "@gradio/plot": "^0.9.25",
46
- "@gradio/radio": "^0.8.0",
33
+ "@gradio/image": "^0.25.0",
34
+ "@gradio/imageeditor": "^0.18.3",
35
+ "@gradio/html": "^0.9.0",
36
+ "@gradio/imageslider": "^0.4.0",
37
+ "@gradio/json": "^0.6.0",
38
+ "@gradio/label": "^0.6.0",
39
+ "@gradio/markdown": "^0.13.24",
40
+ "@gradio/model3d": "^0.16.0",
41
+ "@gradio/multimodaltextbox": "^0.11.2",
42
+ "@gradio/nativeplot": "^0.9.2",
43
+ "@gradio/paramviewer": "^0.9.1",
44
+ "@gradio/plot": "^0.10.0",
45
+ "@gradio/number": "^0.8.0",
46
+ "@gradio/radio": "^0.9.0",
47
+ "@gradio/sidebar": "^0.1.25",
47
48
  "@gradio/row": "^0.3.0",
48
- "@gradio/sidebar": "^0.1.24",
49
- "@gradio/simpledropdown": "^0.3.30",
50
- "@gradio/simpletextbox": "^0.3.31",
51
- "@gradio/slider": "^0.7.0",
52
- "@gradio/simpleimage": "^0.9.1",
53
- "@gradio/state": "^0.2.0",
54
- "@gradio/tabitem": "^0.6.2",
55
- "@gradio/tabs": "^0.5.2",
56
- "@gradio/textbox": "^0.12.0",
57
- "@gradio/statustracker": "^0.12.0",
49
+ "@gradio/simpledropdown": "^0.3.31",
50
+ "@gradio/simpleimage": "^0.9.2",
51
+ "@gradio/slider": "^0.7.2",
52
+ "@gradio/simpletextbox": "^0.3.32",
53
+ "@gradio/state": "^0.2.1",
54
+ "@gradio/statustracker": "^0.12.1",
55
+ "@gradio/tabs": "^0.5.4",
56
+ "@gradio/tabitem": "^0.6.4",
58
57
  "@gradio/theme": "^0.5.0",
59
- "@gradio/timer": "^0.4.6",
60
- "@gradio/upload": "^0.17.2",
61
- "@gradio/utils": "^0.10.4",
62
- "@gradio/uploadbutton": "^0.9.13",
63
- "@gradio/video": "^0.17.0",
64
- "@gradio/vibeeditor": "^0.3.1"
58
+ "@gradio/timer": "^0.4.7",
59
+ "@gradio/textbox": "^0.13.0",
60
+ "@gradio/upload": "^0.17.3",
61
+ "@gradio/utils": "^0.11.0",
62
+ "@gradio/uploadbutton": "^0.9.15",
63
+ "@gradio/vibeeditor": "^0.3.2",
64
+ "@gradio/video": "^0.19.0"
65
65
  },
66
66
  "msw": {
67
67
  "workerDirectory": "public"