pyglove 0.4.5.dev202411030808__py3-none-any.whl → 0.4.5.dev202411060809__py3-none-any.whl

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.

Potentially problematic release.


This version of pyglove might be problematic. Click here for more details.

Files changed (28) hide show
  1. pyglove/core/__init__.py +8 -0
  2. pyglove/core/symbolic/base.py +2 -38
  3. pyglove/core/symbolic/diff.py +14 -13
  4. pyglove/core/symbolic/object_test.py +1 -0
  5. pyglove/core/symbolic/ref.py +7 -7
  6. pyglove/core/views/__init__.py +1 -0
  7. pyglove/core/views/base.py +14 -8
  8. pyglove/core/views/base_test.py +1 -1
  9. pyglove/core/views/html/__init__.py +1 -1
  10. pyglove/core/views/html/base.py +53 -79
  11. pyglove/core/views/html/base_test.py +17 -4
  12. pyglove/core/views/html/controls/__init__.py +35 -0
  13. pyglove/core/views/html/controls/base.py +238 -0
  14. pyglove/core/views/html/controls/label.py +190 -0
  15. pyglove/core/views/html/controls/label_test.py +138 -0
  16. pyglove/core/views/html/controls/progress_bar.py +185 -0
  17. pyglove/core/views/html/controls/progress_bar_test.py +97 -0
  18. pyglove/core/views/html/controls/tab.py +169 -0
  19. pyglove/core/views/html/controls/tab_test.py +54 -0
  20. pyglove/core/views/html/controls/tooltip.py +99 -0
  21. pyglove/core/views/html/controls/tooltip_test.py +99 -0
  22. pyglove/core/views/html/tree_view.py +24 -6
  23. pyglove/core/views/html/tree_view_test.py +7 -2
  24. {pyglove-0.4.5.dev202411030808.dist-info → pyglove-0.4.5.dev202411060809.dist-info}/METADATA +1 -1
  25. {pyglove-0.4.5.dev202411030808.dist-info → pyglove-0.4.5.dev202411060809.dist-info}/RECORD +28 -18
  26. {pyglove-0.4.5.dev202411030808.dist-info → pyglove-0.4.5.dev202411060809.dist-info}/LICENSE +0 -0
  27. {pyglove-0.4.5.dev202411030808.dist-info → pyglove-0.4.5.dev202411060809.dist-info}/WHEEL +0 -0
  28. {pyglove-0.4.5.dev202411030808.dist-info → pyglove-0.4.5.dev202411060809.dist-info}/top_level.txt +0 -0
pyglove/core/__init__.py CHANGED
@@ -309,6 +309,14 @@ Html = views.Html
309
309
  to_html = views.to_html
310
310
  to_html_str = views.to_html_str
311
311
 
312
+ # NOTE(daiyip): Hack to add `controls` to `pg.views.html`.
313
+ # We exclude `html.controls` from `pyglove.core.views.html` package to avoid
314
+ # circular dependency between `pyglove.core.views.html` and
315
+ # `pyglove.core.symbolic`.
316
+ from pyglove.core.views.html import controls
317
+ views.html.controls = controls
318
+
319
+
312
320
  #
313
321
  # Symbols from `io` sub-module.
314
322
  #
@@ -31,7 +31,7 @@ from pyglove.core.symbolic import flags
31
31
  from pyglove.core.symbolic.origin import Origin
32
32
  from pyglove.core.symbolic.pure_symbolic import NonDeterministic
33
33
  from pyglove.core.symbolic.pure_symbolic import PureSymbolic
34
- from pyglove.core.views import html
34
+ from pyglove.core.views.html.base import HtmlConvertible
35
35
 
36
36
 
37
37
  class WritePermissionError(Exception):
@@ -175,7 +175,7 @@ class Symbolic(
175
175
  object_utils.Formattable,
176
176
  object_utils.JSONConvertible,
177
177
  object_utils.MaybePartial,
178
- html.HtmlTreeView.Extension
178
+ HtmlConvertible,
179
179
  ):
180
180
  """Base for all symbolic types.
181
181
 
@@ -949,42 +949,6 @@ class Symbolic(
949
949
  """Serializes current object into a JSON string."""
950
950
  return to_json_str(self, json_indent=json_indent, **kwargs)
951
951
 
952
- def _html_tree_view_content(
953
- self,
954
- *,
955
- view: html.HtmlTreeView,
956
- name: Optional[str] = None,
957
- parent: Any = None,
958
- root_path: Optional[object_utils.KeyPath] = None,
959
- extra_flags: Optional[Dict[str, Any]],
960
- **kwargs,
961
- ) -> html.Html:
962
- """Returns the content HTML for a symbolic object.."""
963
- extra_flags = extra_flags or {}
964
- hide_frozen = extra_flags.get('hide_frozen', True)
965
- hide_default_values = extra_flags.get('hide_default_values', False)
966
- use_inferred = extra_flags.get('use_inferred', False)
967
-
968
- kv = {}
969
- for k, v in self.sym_items():
970
- # Apply frozen filter.
971
- field = self.sym_attr_field(k)
972
- if hide_frozen and field and field.frozen:
973
- continue
974
-
975
- # Apply inferred value.
976
- if use_inferred and isinstance(v, Inferential):
977
- v = self.sym_inferred(k, default=v)
978
-
979
- # Apply default value filter.
980
- if field and hide_default_values and eq(v, field.default_value):
981
- continue
982
- kv[k] = v
983
- return view.complex_value(
984
- kv, name=name, parent=self, root_path=root_path,
985
- extra_flags=extra_flags, **kwargs
986
- )
987
-
988
952
  @classmethod
989
953
  def load(cls, *args, **kwargs) -> Any:
990
954
  """Loads an instance of this type using the global load handler."""
@@ -21,10 +21,10 @@ from pyglove.core.symbolic import base
21
21
  from pyglove.core.symbolic import list as pg_list
22
22
  from pyglove.core.symbolic import object as pg_object
23
23
  from pyglove.core.symbolic.pure_symbolic import PureSymbolic
24
- from pyglove.core.views import html
24
+ from pyglove.core.views.html import tree_view
25
25
 
26
26
 
27
- class Diff(PureSymbolic, pg_object.Object):
27
+ class Diff(PureSymbolic, pg_object.Object, tree_view.HtmlTreeView.Extension):
28
28
  """A value diff between two objects: a 'left' object and a 'right' object.
29
29
 
30
30
  If one of them is missing, it may be represented by pg.Diff.MISSING
@@ -149,12 +149,12 @@ class Diff(PureSymbolic, pg_object.Object):
149
149
  def _html_tree_view_summary(
150
150
  self,
151
151
  *,
152
- view: html.HtmlTreeView,
152
+ view: tree_view.HtmlTreeView,
153
153
  css_classes: Optional[Sequence[str]] = None,
154
154
  title: Optional[str] = None,
155
155
  max_summary_len_for_str: int = 80,
156
156
  **kwargs,
157
- ) -> Optional[html.Html]:
157
+ ) -> Optional[tree_view.Html]:
158
158
  # pytype: enable=annotation-type-mismatch
159
159
  if not bool(self):
160
160
  v = self.value
@@ -197,15 +197,16 @@ class Diff(PureSymbolic, pg_object.Object):
197
197
  def _html_tree_view_content(
198
198
  self,
199
199
  *,
200
- view: html.HtmlTreeView,
201
- parent: Any,
202
- root_path: object_utils.KeyPath,
200
+ view: tree_view.HtmlTreeView,
201
+ parent: Any = None,
202
+ root_path: Optional[object_utils.KeyPath] = None,
203
203
  css_classes: Optional[Sequence[str]] = None,
204
204
  **kwargs
205
- ) -> html.Html:
205
+ ) -> tree_view.Html:
206
+ root_path = root_path or object_utils.KeyPath()
206
207
  if not bool(self):
207
208
  if self.value == Diff.MISSING:
208
- root = html.Html.element(
209
+ root = tree_view.Html.element(
209
210
  'span',
210
211
  # CSS class already defined in HtmlTreeView.
211
212
  css_classes=['diff-empty']
@@ -219,7 +220,7 @@ class Diff(PureSymbolic, pg_object.Object):
219
220
  **kwargs,
220
221
  )
221
222
  elif self.is_leaf:
222
- root = html.Html.element(
223
+ root = tree_view.Html.element(
223
224
  'div',
224
225
  [
225
226
  view.render( # pylint: disable=g-long-ternary
@@ -242,7 +243,7 @@ class Diff(PureSymbolic, pg_object.Object):
242
243
  else:
243
244
  key_fn = lambda k: k
244
245
 
245
- s = html.Html()
246
+ s = tree_view.Html()
246
247
  for k, v in self.children.items():
247
248
  k = key_fn(k)
248
249
  child_path = root_path + k
@@ -263,7 +264,7 @@ class Diff(PureSymbolic, pg_object.Object):
263
264
  ),
264
265
  '</td></tr>'
265
266
  )
266
- root = html.Html.element(
267
+ root = tree_view.Html.element(
267
268
  'div',
268
269
  [
269
270
  '<table>', s, '</table>'
@@ -275,7 +276,7 @@ class Diff(PureSymbolic, pg_object.Object):
275
276
  return root
276
277
 
277
278
  def _html_tree_view_config(self) -> dict[str, Any]:
278
- return html.HtmlTreeView.get_kwargs(
279
+ return tree_view.HtmlTreeView.get_kwargs(
279
280
  super()._html_tree_view_config(),
280
281
  dict(
281
282
  css_classes=[
@@ -39,6 +39,7 @@ from pyglove.core.symbolic.object import use_init_args as pg_use_init_args
39
39
  from pyglove.core.symbolic.origin import Origin
40
40
  from pyglove.core.symbolic.pure_symbolic import NonDeterministic
41
41
  from pyglove.core.symbolic.pure_symbolic import PureSymbolic
42
+ from pyglove.core.views.html import tree_view # pylint: disable=unused-import
42
43
 
43
44
 
44
45
  MISSING_VALUE = object_utils.MISSING_VALUE
@@ -20,10 +20,10 @@ from pyglove.core import object_utils
20
20
  from pyglove.core import typing as pg_typing
21
21
  from pyglove.core.symbolic import base
22
22
  from pyglove.core.symbolic.object import Object
23
- from pyglove.core.views import html
23
+ from pyglove.core.views.html import tree_view
24
24
 
25
25
 
26
- class Ref(Object, base.Inferential):
26
+ class Ref(Object, base.Inferential, tree_view.HtmlTreeView.Extension):
27
27
  """Symbolic reference.
28
28
 
29
29
  When adding a symbolic node to a symbolic tree, it undergoes a copy operation
@@ -167,17 +167,17 @@ class Ref(Object, base.Inferential):
167
167
  def _html_tree_view_content(
168
168
  self,
169
169
  *,
170
- view: html.HtmlTreeView,
171
- **kwargs: Any) -> html.Html:
170
+ view: tree_view.HtmlTreeView,
171
+ **kwargs: Any) -> tree_view.Html:
172
172
  """Overrides `_html_content` to render the referenced value."""
173
173
  return view.content(self._value, **kwargs)
174
174
 
175
175
  def _html_tree_view_summary(
176
176
  self,
177
177
  *,
178
- view: html.HtmlTreeView,
178
+ view: tree_view.HtmlTreeView,
179
179
  title: Optional[str] = None,
180
- **kwargs: Any) -> Optional[html.Html]:
180
+ **kwargs: Any) -> Optional[tree_view.Html]:
181
181
  """Overrides `_html_content` to render the referenced value."""
182
182
  return view.summary(
183
183
  self,
@@ -188,7 +188,7 @@ class Ref(Object, base.Inferential):
188
188
  @classmethod
189
189
  @functools.cache
190
190
  def _html_tree_view_config(cls) -> dict[str, Any]:
191
- return html.HtmlTreeView.get_kwargs(
191
+ return tree_view.HtmlTreeView.get_kwargs(
192
192
  super()._html_tree_view_config(),
193
193
  dict(
194
194
  css_classes=['ref'],
@@ -24,6 +24,7 @@ view_options = base.view_options
24
24
  NodeFilter = base.NodeFilter
25
25
 
26
26
  Html = html.Html
27
+ HtmlConvertible = html.HtmlConvertible
27
28
  HtmlView = html.HtmlView
28
29
  HtmlTreeView = html.HtmlTreeView
29
30
 
@@ -272,11 +272,10 @@ class Content(object_utils.Formattable, metaclass=abc.ABCMeta):
272
272
  self._shared_parts = shared_parts
273
273
 
274
274
  for c in content:
275
+ c = self._to_content(c)
275
276
  if c is None:
276
277
  continue
277
- if callable(c):
278
- c = c()
279
- if isinstance(c, str):
278
+ elif isinstance(c, str):
280
279
  self._content_stream.write(c)
281
280
  else:
282
281
  self.write(c)
@@ -306,9 +305,7 @@ class Content(object_utils.Formattable, metaclass=abc.ABCMeta):
306
305
  """
307
306
  content_updated = False
308
307
  for p in parts:
309
- if callable(p):
310
- p = p()
311
-
308
+ p = self._to_content(p)
312
309
  if p is None:
313
310
  continue
314
311
 
@@ -342,8 +339,7 @@ class Content(object_utils.Formattable, metaclass=abc.ABCMeta):
342
339
 
343
340
  def __add__(self, other: WritableTypes) -> 'Content':
344
341
  """Operator +: Concatenates two Content objects."""
345
- if callable(other):
346
- other = other()
342
+ other = self._to_content(other)
347
343
  if not other:
348
344
  return self
349
345
  s = copy_lib.deepcopy(self)
@@ -416,6 +412,16 @@ class Content(object_utils.Formattable, metaclass=abc.ABCMeta):
416
412
  return value
417
413
  return cls(value) # pytype: disable=not-instantiable
418
414
 
415
+ @classmethod
416
+ def _to_content(cls, value: WritableTypes) -> Union['Content', str, None]:
417
+ """Returns a Content object or None from a writable type."""
418
+ if callable(value):
419
+ value = value()
420
+ if value is None:
421
+ return None
422
+ assert isinstance(value, (str, cls)), value
423
+ return value
424
+
419
425
 
420
426
  def view(
421
427
  value: Any,
@@ -1,4 +1,4 @@
1
- # Copyright 2024 The Langfun Authors
1
+ # Copyright 2024 The PyGlove Authors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -17,10 +17,10 @@
17
17
  # pylint: disable=g-bad-import-order
18
18
 
19
19
  from pyglove.core.views.html.base import Html
20
+ from pyglove.core.views.html.base import HtmlConvertible
20
21
  from pyglove.core.views.html.base import HtmlView
21
22
  from pyglove.core.views.html.base import to_html
22
23
  from pyglove.core.views.html.base import to_html_str
23
-
24
24
  from pyglove.core.views.html.tree_view import HtmlTreeView
25
25
 
26
26
  # pylint: enable=g-bad-import-order
@@ -1,4 +1,4 @@
1
- # Copyright 2024 The Langfun Authors
1
+ # Copyright 2024 The PyGlove Authors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -86,7 +86,8 @@ class Html(base.Content):
86
86
  WritableTypes = Union[ # pylint: disable=invalid-name
87
87
  str,
88
88
  'Html',
89
- Callable[[], Union[str, 'Html', None]],
89
+ 'HtmlConvertible',
90
+ Callable[[], Union[str, 'Html', 'HtmlConvertible', None]],
90
91
  None
91
92
  ]
92
93
 
@@ -274,6 +275,20 @@ class Html(base.Content):
274
275
  Html, super().from_value(value, copy=copy)
275
276
  )
276
277
 
278
+ @classmethod
279
+ def _to_content(
280
+ cls, value: WritableTypes
281
+ ) -> Union['Html', str, None]:
282
+ if callable(value):
283
+ value = value()
284
+ if value is None:
285
+ return None
286
+ elif isinstance(value, HtmlConvertible):
287
+ value = value.to_html()
288
+ elif not isinstance(value, (str, cls)):
289
+ raise TypeError(f'Not a writable value for `{cls.__name__}`: {value!r}')
290
+ return value
291
+
277
292
  #
278
293
  # Helper methods for creating templated Html objects.
279
294
  #
@@ -337,13 +352,15 @@ class Html(base.Content):
337
352
  cls,
338
353
  s: WritableTypes,
339
354
  javascript_str: bool = False
340
- ) -> WritableTypes:
355
+ ) -> Union[str, 'Html', None]:
341
356
  """Escapes an HTML writable object."""
342
357
  if s is None:
343
358
  return None
344
359
 
345
360
  if callable(s):
346
361
  s = s()
362
+ if isinstance(s, HtmlConvertible):
363
+ s = s.to_html()
347
364
 
348
365
  def _escape(s: str) -> str:
349
366
  if javascript_str:
@@ -413,87 +430,27 @@ class Html(base.Content):
413
430
  pg_typing.register_converter(str, Html, convert_fn=Html.from_value)
414
431
 
415
432
 
416
- class HtmlView(base.View):
417
- """Base class for HTML views."""
433
+ class HtmlConvertible:
434
+ """Base class for HTML convertible objects."""
418
435
 
419
- class Extension(base.View.Extension):
420
- """Base class for HtmlView extensions."""
436
+ def to_html(self, **kwargs) -> Html:
437
+ """Returns the HTML representation of the object."""
438
+ return to_html(self, **kwargs)
421
439
 
422
- def _html_style(self) -> List[str]:
423
- """Returns additional CSS style to add for this extension.
424
-
425
- Subclasses can override this method to add additional CSS style to the
426
- rendered HTML.
427
- """
428
- return []
429
-
430
- def _html_element_class(self) -> List[str]:
431
- """Returns the CSS classes for the rendered element for this node.
432
-
433
- Subclasses can override this method to add CSS classes to the
434
- rendered element of this object.
435
- """
436
- return [
437
- object_utils.camel_to_snake(self.__class__.__name__, '-')
438
- ]
439
-
440
- def to_html(
441
- self,
442
- *,
443
- name: Optional[str] = None,
444
- root_path: Optional[object_utils.KeyPath] = None,
445
- view_id: str = 'html-tree-view',
446
- **kwargs
447
- ) -> Html:
448
- """Returns the HTML representation of the object.
449
-
450
- Args:
451
- name: The name of the object.
452
- root_path: The root path of the object.
453
- view_id: The ID of the view to render the value.
454
- See `pg.views.HtmlView.dir()` for all available HTML view IDs.
455
- **kwargs: View-specific keyword arguments passed to `pg.to_html`, wich
456
- will be used to construct/override `HtmlView` settings.
457
-
458
- Returns:
459
- An rendered HTML.
460
- """
461
- return to_html(
462
- self, name=name, root_path=root_path, view_id=view_id, **kwargs
463
- )
440
+ def to_html_str(self, *, content_only: bool = False, **kwargs) -> str:
441
+ """Returns the HTML str of the object."""
442
+ return self.to_html(**kwargs).to_str(content_only=content_only)
464
443
 
465
- def to_html_str(
466
- self,
467
- *,
468
- name: Optional[str] = None,
469
- root_path: Optional[object_utils.KeyPath] = None,
470
- view_id: str = 'html-tree-view',
471
- content_only: bool = False,
472
- **kwargs
473
- ) -> str:
474
- """Returns the HTML str of the object.
475
-
476
- Args:
477
- name: The name of the object.
478
- root_path: The root path of the object.
479
- view_id: The ID of the view to render the value.
480
- See `pg.views.HtmlView.dir()` for all available HTML view IDs.
481
- content_only: If True, only the content will be returned.
482
- **kwargs: View-specific keyword arguments passed to `pg.to_html`, wich
483
- will be used to construct/override `HtmlView` settings.
484
-
485
- Returns:
486
- An rendered HTML str.
487
- """
488
- return to_html_str(
489
- self, name=name, root_path=root_path,
490
- view_id=view_id, content_only=content_only, **kwargs
491
- )
444
+ def _repr_html_(self) -> str:
445
+ return self.to_html_str()
492
446
 
493
- def _repr_html_(self) -> str:
494
- return self.to_html_str()
495
447
 
496
- @abc.abstractmethod
448
+ class HtmlView(base.View):
449
+ """Base class for HTML views."""
450
+
451
+ class Extension(base.View.Extension, HtmlConvertible):
452
+ """Base class for HtmlView extensions."""
453
+
497
454
  def render(
498
455
  self,
499
456
  value: Any,
@@ -503,6 +460,23 @@ class HtmlView(base.View):
503
460
  **kwargs
504
461
  ) -> Html:
505
462
  """Renders the input value into an HTML object."""
463
+ # For customized HtmlConvertible objects, call their `to_html()` method.
464
+ if (isinstance(value, HtmlConvertible)
465
+ and not isinstance(value, self.__class__.Extension)
466
+ and value.__class__.to_html is not HtmlConvertible.to_html):
467
+ return value.to_html(name=name, root_path=root_path, **kwargs)
468
+ return self._render(value, name=name, root_path=root_path, **kwargs)
469
+
470
+ @abc.abstractmethod
471
+ def _render(
472
+ self,
473
+ value: Any,
474
+ *,
475
+ name: Optional[str] = None,
476
+ root_path: Optional[object_utils.KeyPath] = None,
477
+ **kwargs
478
+ ) -> Html:
479
+ """View's implementation of HTML rendering."""
506
480
 
507
481
 
508
482
  def to_html(
@@ -1,4 +1,4 @@
1
- # Copyright 2024 The Langfun Authors
1
+ # Copyright 2024 The PyGlove Authors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -422,6 +422,10 @@ class SharedPartTest(TestCase):
422
422
 
423
423
  class HtmlTest(TestCase):
424
424
 
425
+ class Foo(base.HtmlConvertible):
426
+ def to_html(self, **kwargs):
427
+ return base.Html('<h1>foo</h1>')
428
+
425
429
  def test_content_init(self):
426
430
  html = Html()
427
431
  self.assertEqual(html, Html())
@@ -446,6 +450,9 @@ class HtmlTest(TestCase):
446
450
  )
447
451
  self.assertEqual(html, Html('abcdefghi'))
448
452
 
453
+ html = Html(HtmlTest.Foo())
454
+ self.assertEqual(html, Html('<h1>foo</h1>'))
455
+
449
456
  def test_basics(self):
450
457
  html = Html(
451
458
  '<h1>foo</h1>',
@@ -583,7 +590,7 @@ class HtmlTest(TestCase):
583
590
  html2.write('<div class="c">bar</div>')
584
591
  html2.write('\n<script>\nconsole.log("bar");\n</script>')
585
592
 
586
- html1.write('<div class="a">foo</div>')
593
+ html1.write(HtmlTest.Foo())
587
594
  html1.write('\n<script>\nconsole.log("foo");\n</script>\n')
588
595
  html1.write(html2)
589
596
 
@@ -603,7 +610,7 @@ class HtmlTest(TestCase):
603
610
  </script>
604
611
  </head>
605
612
  <body>
606
- <div class="a">foo</div>
613
+ <h1>foo</h1>
607
614
  <script>
608
615
  console.log("foo");
609
616
  </script>
@@ -618,7 +625,7 @@ class HtmlTest(TestCase):
618
625
  self.assert_html(
619
626
  html1.to_str(content_only=True),
620
627
  """
621
- <div class="a">foo</div>
628
+ <h1>foo</h1>
622
629
  <script>
623
630
  console.log("foo");
624
631
  </script>
@@ -641,6 +648,8 @@ class HtmlTest(TestCase):
641
648
  html2 = Html.from_value(html, copy=True)
642
649
  self.assertIsNot(html2, html)
643
650
  self.assertEqual(html2, html)
651
+ html3 = Html.from_value(HtmlTest.Foo())
652
+ self.assertEqual(html3, Html('<h1>foo</h1>'))
644
653
 
645
654
  with self.assertRaises(TypeError):
646
655
  Html.from_value(1)
@@ -694,9 +703,13 @@ class HtmlTest(TestCase):
694
703
  self.assertEqual(Html.escape('foo'), 'foo')
695
704
  self.assertEqual(Html.escape('foo"bar'), 'foo&quot;bar')
696
705
  self.assertEqual(Html.escape(Html('foo"bar')), Html('foo&quot;bar'))
706
+ self.assertEqual(Html.escape(HtmlTest.Foo()), Html('&lt;h1&gt;foo&lt;/h1&gt;'))
697
707
  self.assertEqual(Html.escape(lambda: 'foo"bar'), 'foo&quot;bar')
698
708
  self.assertEqual(Html.escape('"x=y"', javascript_str=True), '\\"x=y\\"')
699
709
  self.assertEqual(Html.escape('x\n"', javascript_str=True), 'x\\n\\"')
710
+ self.assertEqual(
711
+ Html.escape(HtmlTest.Foo(), javascript_str=True), Html('<h1>foo</h1>')
712
+ )
700
713
 
701
714
  def test_concate(self):
702
715
  self.assertIsNone(Html.concate(None))
@@ -0,0 +1,35 @@
1
+ # Copyright 2024 The PyGlove Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Common HTML controls."""
15
+
16
+ # pylint: disable=g-importing-member
17
+ # pylint: disable=g-bad-import-order
18
+
19
+ from pyglove.core.views.html.controls.base import HtmlControl
20
+
21
+
22
+ from pyglove.core.views.html.controls.label import Label
23
+ from pyglove.core.views.html.controls.label import LabelGroup
24
+ from pyglove.core.views.html.controls.label import Badge
25
+
26
+ from pyglove.core.views.html.controls.tooltip import Tooltip
27
+
28
+ from pyglove.core.views.html.controls.tab import Tab
29
+ from pyglove.core.views.html.controls.tab import TabControl
30
+
31
+ from pyglove.core.views.html.controls.progress_bar import ProgressBar
32
+ from pyglove.core.views.html.controls.progress_bar import SubProgress
33
+
34
+ # pylint: enable=g-bad-import-order
35
+ # pylint: enable=g-importing-member