tts-html-utils 0.5.4__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.
Files changed (76) hide show
  1. tts_html_utils/__init__.py +0 -0
  2. tts_html_utils/core/__init__.py +0 -0
  3. tts_html_utils/core/compiler.py +215 -0
  4. tts_html_utils/core/components/__init__.py +6 -0
  5. tts_html_utils/core/components/base.py +300 -0
  6. tts_html_utils/core/components/favicon.py +17 -0
  7. tts_html_utils/core/components/flexbox.py +133 -0
  8. tts_html_utils/core/components/list.py +168 -0
  9. tts_html_utils/core/components/misc.py +237 -0
  10. tts_html_utils/core/components/navigation.py +55 -0
  11. tts_html_utils/core/components/plot.py +166 -0
  12. tts_html_utils/core/components/structure.py +159 -0
  13. tts_html_utils/core/components/table.py +392 -0
  14. tts_html_utils/core/components/text.py +105 -0
  15. tts_html_utils/css/gantt_chart/gantt_chart.css +330 -0
  16. tts_html_utils/css/pane_container/pane_container.css +8 -0
  17. tts_html_utils/django/__init__.py +0 -0
  18. tts_html_utils/django/forms/__init__.py +0 -0
  19. tts_html_utils/django/forms/time_range_form.py +56 -0
  20. tts_html_utils/django/tables/__init__.py +0 -0
  21. tts_html_utils/django/tables/filter_table.py +201 -0
  22. tts_html_utils/django/templates/filter_table/filter_table.html +39 -0
  23. tts_html_utils/django/templates/filter_table/filter_table_rows.html +22 -0
  24. tts_html_utils/django/templates/filter_table/javascript.html +108 -0
  25. tts_html_utils/django/templates/filter_table/pagination.html +18 -0
  26. tts_html_utils/gantt_chart/gantt_chart.py +167 -0
  27. tts_html_utils/javascript/filter_table/django_html_filter_table.js +85 -0
  28. tts_html_utils/javascript/filter_table/django_html_table_sort.js +78 -0
  29. tts_html_utils/javascript/filter_table/show_hide_details.js +24 -0
  30. tts_html_utils/javascript/filter_table/static_html_filter_table.js +72 -0
  31. tts_html_utils/javascript/filter_table/static_html_table_sort.js +122 -0
  32. tts_html_utils/javascript/gantt_chart/gantt_chart.js +908 -0
  33. tts_html_utils/javascript/pane_container/pane_container.js +141 -0
  34. tts_html_utils/javascript/pane_container.js +43 -0
  35. tts_html_utils/javascript/plotlyv1.58.5.js +62 -0
  36. tts_html_utils/javascript/visdiff_tables/hide_columns_without_mismatch.js +35 -0
  37. tts_html_utils/javascript/visdiff_tables/row_height_match.js +55 -0
  38. tts_html_utils/javascript/visdiff_tables/scroll_sync.js +29 -0
  39. tts_html_utils/jupyter/__init__.py +0 -0
  40. tts_html_utils/jupyter/grid.py +465 -0
  41. tts_html_utils/resource.py +225 -0
  42. tts_html_utils/resources/css/navbar.css +39 -0
  43. tts_html_utils/resources/html_templates/full_report.html +56 -0
  44. tts_html_utils/resources/html_templates/generic.html +2 -0
  45. tts_html_utils/resources/html_templates/generic_singleton.html +2 -0
  46. tts_html_utils/resources/html_templates/helpers.html +31 -0
  47. tts_html_utils/resources/html_templates/plotly_config.html +6 -0
  48. tts_html_utils/resources/html_templates/script_general.html +3 -0
  49. tts_html_utils/resources/html_templates/table.html +21 -0
  50. tts_html_utils/resources/html_templates/test.html +4 -0
  51. tts_html_utils/resources/img/favicon.png +0 -0
  52. tts_html_utils/resources/report_base.css +474 -0
  53. tts_html_utils/resources/rule_result_table.css +40 -0
  54. tts_html_utils/resources/rule_summary.css +79 -0
  55. tts_html_utils/resources/scripts/collapsible.js +40 -0
  56. tts_html_utils/resources/scripts/link_functions.js +13 -0
  57. tts_html_utils/resources/scripts/open_tab_function.js +29 -0
  58. tts_html_utils/resources/scripts/resize_bodyheight.js +7 -0
  59. tts_html_utils/resources/scripts/sorttable.js +489 -0
  60. tts_html_utils/resources/scripts/sticky_headers.js +11 -0
  61. tts_html_utils/test/__init__.py +0 -0
  62. tts_html_utils/test/test_base.py +110 -0
  63. tts_html_utils/test/test_compiler.py +227 -0
  64. tts_html_utils/test/test_list.py +127 -0
  65. tts_html_utils/test/test_misc.py +145 -0
  66. tts_html_utils/test/test_navigation.py +59 -0
  67. tts_html_utils/test/test_plot.py +150 -0
  68. tts_html_utils/test/test_resource.py +133 -0
  69. tts_html_utils/test/test_structure.py +176 -0
  70. tts_html_utils/test/test_table.py +256 -0
  71. tts_html_utils/test/test_text.py +62 -0
  72. tts_html_utils/visdiff/visdiff.py +98 -0
  73. tts_html_utils-0.5.4.dist-info/METADATA +72 -0
  74. tts_html_utils-0.5.4.dist-info/RECORD +76 -0
  75. tts_html_utils-0.5.4.dist-info/WHEEL +5 -0
  76. tts_html_utils-0.5.4.dist-info/top_level.txt +1 -0
File without changes
File without changes
@@ -0,0 +1,215 @@
1
+ #Standard Library Imports
2
+ import pdb
3
+ from collections import defaultdict
4
+ from typing import List, Tuple, Dict, Any
5
+ from pathlib import Path
6
+ import html
7
+
8
+ #Installed dependency imports
9
+ from jinja2 import Template
10
+
11
+ #Teamtools Studio Imports
12
+ from tts_utilities.logger import create_logger
13
+
14
+ #This Library Imports
15
+ from tts_html_utils.core.components.base import HtmlComponent
16
+ from tts_html_utils.core.components.misc import HTML, Head, Body, Footer, Style, Script, TtsFavicon, Title
17
+
18
+ logger = create_logger(name='html_utils.core.components.base')
19
+
20
+ def make_hashable(obj: Any):
21
+ """
22
+ Recursively converts mutable objects (dicts, lists) into immutable equivalents
23
+ (frozensets, tuples) to facilitate uniqueness checks.
24
+
25
+ This is necessary because we need to detect duplicate configuration dictionaries
26
+ when aggregating resources. Since Python `sets` cannot contain mutable dictionaries,
27
+ we must freeze them first to check if they are identical.
28
+
29
+ :param obj: The object to convert.
30
+ :return: An immutable, hashable version of the input.
31
+ """
32
+ if isinstance(obj, dict):
33
+ return frozenset((k, make_hashable(v)) for k, v in obj.items())
34
+ elif isinstance(obj, list):
35
+ return tuple(make_hashable(v) for v in obj)
36
+ elif isinstance(obj, set):
37
+ return frozenset(make_hashable(v) for v in obj)
38
+ else:
39
+ return obj
40
+
41
+ def group_by_path_with_unique_dicts(tuples: List[Tuple[Path, Dict]]) -> List[Tuple[Path, List[Dict]]]:
42
+ """
43
+ Groups resource requests by file path and removes duplicate configurations.
44
+
45
+ This is used to manage dependencies efficiently. For example, if multiple
46
+ components request the same JavaScript library with identical settings,
47
+ this ensures the library is only loaded once. If they request the same
48
+ library with *different* settings, both configurations are preserved.
49
+
50
+ :param tuples: A list of (FilePath, ConfigDict) pairs.
51
+ :return: A list of tuples where each FilePath is associated with a list of unique ConfigDicts.
52
+ """
53
+ grouped = defaultdict(set)
54
+
55
+ for path, meta in tuples:
56
+ hashable_meta = make_hashable(meta)
57
+ grouped[path].add(hashable_meta)
58
+
59
+ result = [
60
+ (path, [dict(items) for items in dicts])
61
+ for path, dicts in grouped.items()
62
+ ]
63
+
64
+ return result
65
+
66
+
67
+ class HtmlCompiler:
68
+ """
69
+ Manages the lifecycle of an HTML document, handling component aggregation
70
+ and dependency resolution.
71
+
72
+ This class abstracts away the manual construction of the `<html>` structure.
73
+ It allows you to add Python component objects from this libary to the
74
+ Head or Body, and automatically handles the "plumbing"—ensuring that any
75
+ CSS or JavaScript required by those components is injected into the document
76
+ correctly and without duplication.
77
+
78
+ :param title: The document title (displayed in the browser tab).
79
+ """
80
+ def __init__(self, title):
81
+ self.title = title
82
+ self.head_components = [TtsFavicon()]
83
+ self.body_components = list()
84
+ self.scripts = list()
85
+ self.styles = list()
86
+
87
+ def add_head_component(self, component):
88
+ """
89
+ Adds a component to the `<head>` section of the document.
90
+
91
+ The Head is used for metadata and configuration that is not directly
92
+ displayed on the page canvas (e.g., meta tags, third-party analytics scripts,
93
+ favicons, or social media open graph tags).
94
+
95
+ :param component: An `HtmlComponent` appropriate for the head section.
96
+ """
97
+ if isinstance(component, HtmlComponent):
98
+ self.head_components.append(component)
99
+ else:
100
+ raise Exception(f"{component} is not an HtmlComponent")
101
+
102
+ def add_body_component(self, component):
103
+ """
104
+ Adds a component to the `<body>` section of the document.
105
+
106
+ The Body contains the visible content of the page. Use this for
107
+ visual elements like layout containers, text, tables, and widgets.
108
+
109
+ :param component: An `HtmlComponent` appropriate for the body section.
110
+ """
111
+ if isinstance(component, HtmlComponent):
112
+ self.body_components.append(component)
113
+ else:
114
+ raise Exception(f"{component} is not an HtmlComponent")
115
+
116
+ def compile(self):
117
+ """
118
+ Assembles the final object tree for the HTML document.
119
+
120
+ This method performs dependency injection and deduplication:
121
+ 1. **Aggregation:** Scans all added components to find their required CSS/JS files.
122
+ 2. **Deduplication:** Ensures shared resources (like a common plotting library) are not included twice.
123
+ 3. **Rendering:** processes any Jinja2 templates found in the resource files.
124
+ 4. **Construction:** Builds the root `HTML` object containing the configured `Head` and `Body`.
125
+
126
+ :return: The root `HTML` object ready for final string rendering.
127
+ """
128
+ css_includes = []
129
+ js_includes = []
130
+
131
+ # 1. Collect dependencies from all components
132
+ for component in self.head_components + self.body_components:
133
+ css_includes += component.aggregate_attributes('css_includes')
134
+ js_includes += component.aggregate_attributes('js_includes')
135
+
136
+ # 2. Remove duplicates
137
+ css_includes = group_by_path_with_unique_dicts(css_includes)
138
+ js_includes = group_by_path_with_unique_dicts(js_includes)
139
+
140
+ document = HTML()
141
+ head = Head()
142
+ head.add_child(Title(self.title))
143
+
144
+ # 3. Process and attach CSS
145
+ for path, dicts in css_includes:
146
+ if isinstance(path, str):
147
+ # Direct string path inclusion (e.g. for CDNs)
148
+ head.add_child(Style(path))
149
+ continue
150
+
151
+ # Render template-based CSS files
152
+ css_template_text = path.open('r').read()
153
+ template = Template(css_template_text)
154
+ for d in dicts:
155
+ rendered_css = template.render(**d)
156
+ head.add_child(Style(rendered_css))
157
+
158
+ # 4. Process and attach JS
159
+ for path, dicts in js_includes:
160
+ if isinstance(path, str):
161
+ # Direct string path inclusion
162
+ head.add_child(Script(attr={'src': path}))
163
+ continue
164
+
165
+ # Render template-based JS files
166
+ js_template_text = path.open('r').read()
167
+ template = Template(js_template_text)
168
+ for d in dicts:
169
+ rendered_js = template.render(**d)
170
+ head.add_child(Script(rendered_js))
171
+
172
+ # 5. Attach explicit components
173
+ for head_component in self.head_components:
174
+ head.add_child(head_component)
175
+
176
+ body = Body(style={'padding': '10px'})
177
+ for body_component in self.body_components:
178
+ body.add_child(body_component)
179
+
180
+ document.add_child(head)
181
+ document.add_child(body)
182
+
183
+ return document
184
+
185
+ def render(self):
186
+ """
187
+ Compiles the object tree and serializes it into an HTML string.
188
+
189
+ :return: The complete HTML source code.
190
+ """
191
+ return self.compile().render()
192
+
193
+ def render_to_file(self, filepath):
194
+ """
195
+ Compiles the HTML and writes it to the specified file path.
196
+
197
+ :param filepath: The full destination path for the .html file.
198
+ """
199
+ with open(filepath,'w') as f:
200
+ f.write(self.render())
201
+
202
+ def render_to_jnb_iframe(self, width='100%', height='300px'):
203
+ """
204
+ Compiles the HTML and writes it to the specified file path.
205
+
206
+ :param filepath: The full destination path for the .html file.
207
+ """
208
+ escaped_html = html.escape(self.render())
209
+ return f'''
210
+ <iframe
211
+ srcdoc="{escaped_html}"
212
+ style="width: {width}; height: {height}; border: 1px solid #ddd;"
213
+ sandbox="allow-scripts allow-same-origin"
214
+ ></iframe>
215
+ '''
@@ -0,0 +1,6 @@
1
+ from .base import *
2
+ from .misc import *
3
+ from .text import *
4
+ from .list import *
5
+ from .table import *
6
+ from .plot import *
@@ -0,0 +1,300 @@
1
+ #Standard Library Imports
2
+ from abc import ABC, abstractmethod
3
+ from uuid import uuid4
4
+ import pdb
5
+
6
+ #Installed Dependecy Imports
7
+ #none yet
8
+
9
+ #Teamtools Studio Imports
10
+ from tts_utilities.util import as_list
11
+ from tts_utilities.logger import create_logger
12
+ from bs4 import BeautifulSoup
13
+
14
+ #This Library Imports
15
+ # None
16
+
17
+ logger = create_logger(name='html_utils.core.components.base')
18
+
19
+ #================================================
20
+ # :: Base Component
21
+ #================================================
22
+ class HtmlComponent(ABC):
23
+ """
24
+ The fundamental blueprint for all HTML elements in this library.
25
+
26
+ **The Concept:**
27
+ HTML is hierarchical (a tree). A `<div>` contains a `<table>`, which contains
28
+ `<tr>` rows, which contain `<td>` cells. This class provides the machinery
29
+ to replicate that structure in Python.
30
+
31
+ **How it works:**
32
+ Every specific HTML tag (like `Div`, `Span`, or `Img`) inherits from this class.
33
+ This base class handles the logic shared by *all* of them, such as:
34
+ 1. **Parent/Child Relationships:** Tracking what is inside what.
35
+ 2. **Attribute Management:** Handling IDs, CSS classes, and inline styles.
36
+ 3. **Dependency bubbling:** If a button deep inside a table requires a specific
37
+ JavaScript file, this class allows that requirement to bubble up to the
38
+ top of the document so the `HtmlCompiler` can see it.
39
+
40
+ **Note:** This is an "Abstract Base Class" (ABC). You cannot create a generic `HtmlComponent`
41
+ directly. You must use one of its subclasses that represents a real HTML tag.
42
+
43
+ :param children: Any nested components that go *inside* this element.
44
+ :type children: List of HtmlComponent objects (or plain strings), optional
45
+ :param class_name: Primary CSS class(es) to control the look/feel.
46
+ :type class_name: str or list of str, optional
47
+ :param extra_class_name: Additional CSS classes to append. Useful when extending components
48
+ where you want to keep the base class but add a modifier.
49
+ :type extra_class_name: str or list of str, optional
50
+ :param tag: The actual HTML tag name (e.g., 'div', 'a', 'span'). Defaults to the class's `TAG` constant.
51
+ :type tag: str, optional
52
+ :param id: The unique HTML `id` attribute for this element.
53
+ :type id: str, optional
54
+ :param style: Inline CSS styles (e.g., `{'color': 'red', 'margin': '10px'}`).
55
+ :type style: dict, optional
56
+ :param attr: A dictionary of any other HTML attributes not covered above (e.g., `{'href': '...', 'onclick': '...'}`).
57
+ :type attr: dict, optional
58
+ """
59
+ TAG = None
60
+ DEFAULT_CLASS = []
61
+ STYLESHEETS = []
62
+
63
+ def __init__(self, children=None, class_name=None, extra_class_name=None, tag=None, id=None, style=None, attr=None):
64
+ self.id = id
65
+ self.tag = tag or self.TAG
66
+ self.parent_id = None
67
+ self.children = []
68
+ self.js_includes = []
69
+ self.css_includes = []
70
+ self.attr = attr if attr is not None else {}
71
+
72
+ for child in as_list(children or []):
73
+ self.add_child(child)
74
+
75
+ self._class = [] + as_list(class_name or self.DEFAULT_CLASS)
76
+ if extra_class_name is not None:
77
+ self._class += as_list(extra_class_name)
78
+
79
+ self.style = style or {}
80
+
81
+ def aggregate_attributes(self, attr, lst=None):
82
+ """
83
+ The "Harvester" method.
84
+
85
+ This function recursively dives down into every child (and grandchild)
86
+ of this component to find specific attributes (usually 'js_includes' or
87
+ 'css_includes').
88
+
89
+ **Why?** When you render a full page, the top-level document needs to know about
90
+ every JavaScript file required by every tiny widget deep in the page structure.
91
+ This method bubbles those requirements up to the surface.
92
+
93
+ :param attr: The name of the attribute to collect (e.g., `'js_includes'`).
94
+ :type attr: str
95
+ :param lst: The accumulator list. If None, a new list is created.
96
+ :type lst: list, optional
97
+ :return: A list containing the collected attributes from this component and all descendents.
98
+ """
99
+ if lst is None: lst = []
100
+ v = getattr(self, attr, None)
101
+ if v is not None: lst += v #support elements without the given attr set
102
+ for child in self.children:
103
+ if not isinstance(child, HtmlComponent): continue
104
+ try:
105
+ child.aggregate_attributes(attr, lst=lst)
106
+ except:
107
+ pdb.set_trace()
108
+ return lst
109
+
110
+ def add_child(self, child):
111
+ """
112
+ Nests a new element inside this one.
113
+
114
+ :param child: The component to insert.
115
+ :type child: HtmlComponent or str
116
+ """
117
+ self.children.append(child)
118
+ if isinstance(child, HtmlComponent):
119
+ child.parent_id = self.id
120
+
121
+ @property
122
+ def class_name(self):
123
+ """
124
+ Returns the final, space-separated string of CSS classes.
125
+
126
+ This combines the default classes (from `DEFAULT_CLASS`) with any specific
127
+ classes added during initialization or via `add_class`. This string is
128
+ what actually gets written to the HTML `class="..."` attribute.
129
+ """
130
+ return " ".join(self._class)
131
+
132
+ def add_class(self, new_class):
133
+ """
134
+ Appends a new CSS class to the existing list of classes.
135
+ Useful for dynamically highlighting an element (e.g., adding 'active' or 'error').
136
+
137
+ :param new_class: The CSS class string to add.
138
+ :type new_class: str
139
+ """
140
+ if new_class not in self._class:
141
+ self._class.append(new_class)
142
+
143
+ @property
144
+ def rendered(self):
145
+ """
146
+ A convenience property that triggers the full rendering process.
147
+ Accessing `.rendered` is equivalent to calling the `.render()` method.
148
+
149
+ :return: The fully compiled HTML string for this component.
150
+ :rtype: str
151
+ """
152
+ return self.render()
153
+
154
+ @abstractmethod
155
+ def render(self):
156
+ """
157
+ This method transforms the abstract Python object into an actual HTML string.
158
+ Since this is the base class, it doesn't know *how* to render itself yet.
159
+ Specific implementations (like `HtmlComponentSimple`) provide the logic here.
160
+ """
161
+ pass
162
+
163
+ @property
164
+ def content(self):
165
+ """
166
+ A convenience property that returns the *inner* HTML of the component.
167
+
168
+ Example: If the component is `<div><b>Hello</b></div>`, `.content` returns `<b>Hello</b>`.
169
+ """
170
+ return self.render_content()
171
+
172
+ def render_content(self):
173
+ """
174
+ A hook for how an individual component should render its content as a string.
175
+ Most compoenents inherit from HtmlComponentSimple, which inherits from this.
176
+ HtmlComponentSimple defines render_content itself, but it can always be
177
+ overridden by a descendent class.
178
+ """
179
+ raise NotImplementedError(f'This class does not implement content rendering')
180
+
181
+ def recurse_stylesheets(self):
182
+ """
183
+ Crawls the component tree to find all stylesheets declared by children.
184
+
185
+ Similar to `aggregate_attributes`, this ensures that if a nested component
186
+ defines a custom `STYLESHEET`, that sheet is bubbled up and included in the
187
+ document head.
188
+ """
189
+ child_sheets = self.STYLESHEETS
190
+ for child in self.children:
191
+ if isinstance(child, HtmlComponent):
192
+ child_sheets += child.recurse_stylesheets()
193
+ return child_sheets
194
+
195
+ def pretty_print(self):
196
+ """
197
+ A debugging tool that prints the HTML in a readable, indented format.
198
+
199
+ Raw HTML strings are often one massive, unreadable line. This uses
200
+ BeautifulSoup to break it into indented lines so you can see the structure
201
+ visually.
202
+ """
203
+ html = self.render()
204
+ soup = BeautifulSoup(html, 'html.parser')
205
+ return soup.prettify()
206
+
207
+
208
+ class HtmlComponentSimple(HtmlComponent):
209
+ """
210
+ The implementation for standard "Container" HTML tags.
211
+
212
+ **Concept:**
213
+ Most HTML tags work like containers: they open, they hold content, and they close.
214
+ Examples: `<div>...</div>`, `<p>...</p>`, `<b>...</b>`.
215
+
216
+ This class implements the `render` logic for this open-close pattern.
217
+ """
218
+
219
+ def _render_open(self):
220
+ """
221
+ Internal helper: specific logic to build the *opening* tag.
222
+
223
+ It takes the Python dictionaries for style/attributes and converts them
224
+ into string format.
225
+ """
226
+ # 1. Start with the tag itself
227
+ parts = [self.tag]
228
+
229
+ # 2. Add ID if present
230
+ if self.id:
231
+ parts.append(f'id="{self.id}"')
232
+
233
+ # 3. Add Class if present
234
+ if self.class_name:
235
+ parts.append(f'class="{self.class_name}"')
236
+
237
+ # 4. Add Style if present
238
+ if self.style:
239
+ style_str = ' '.join(f'{k}: {v};' for k, v in self.style.items())
240
+ parts.append(f'style="{style_str}"')
241
+
242
+ # 5. Add other attributes if present
243
+ if self.attr:
244
+ attr_str = ' '.join(f'{k}="{v}"' for k, v in self.attr.items())
245
+ parts.append(attr_str)
246
+
247
+ # 6. Join with spaces and wrap in brackets
248
+ # ' '.join(parts) ensures there is exactly one space between attributes
249
+ # and no trailing space before the closing bracket.
250
+ return f'<{" ".join(parts)}>'
251
+
252
+ def render(self):
253
+ """
254
+ Generates the full HTML string for this element and all its children.
255
+
256
+ Structure: `<tag> [All Children Rendered Here] </tag>`
257
+ """
258
+ return f'{self._render_open()}{self.content}</{self.tag}>'
259
+
260
+ def render_content(self):
261
+ """
262
+ Iterates through the children list, asks them to render themselves,
263
+ and concatenates the results.
264
+ """
265
+ out = ''
266
+ for child in self.children:
267
+ if isinstance(child, HtmlComponent):
268
+ out += child.rendered
269
+ else:
270
+ out += str(child)
271
+ return out
272
+
273
+ class HtmlComponentSingleSimple(HtmlComponentSimple):
274
+ """
275
+ The implementation for "Void" or "Self-Closing" HTML tags.
276
+
277
+ **Concept:**
278
+ Some HTML tags do not contain other elements. They are standalone instructions.
279
+ Examples:
280
+ - `<img>`: Just places an image.
281
+ - `<br>`: Just creates a line break.
282
+ - `<input>`: Just creates a text box.
283
+
284
+ These tags **must not** have a closing tag (e.g., `</img>` is invalid HTML).
285
+ This class overrides the render method to ensure we only print the opening tag.
286
+ """
287
+ def render(self):
288
+ """
289
+ Renders the tag without a closing partner or internal content.
290
+
291
+ Structure: `<tag attribute="value">`
292
+ """
293
+ return self._render_open()
294
+
295
+ def render_content(self):
296
+ """
297
+ Disabled for HtmlComponentSingleSimple, but still here in case it's called
298
+ so it raises a useful exception.
299
+ """
300
+ raise NotImplementedError(f'No content allowed for Singleton HTML elements')
@@ -0,0 +1,17 @@
1
+ import base64
2
+ from pathlib import Path
3
+
4
+ def png_to_base64_string(image_path):
5
+ # 1. Open the image file in binary read mode ('rb')
6
+ with open(image_path, "rb") as image_file:
7
+
8
+ # 2. Read the binary data and encode it to base64
9
+ base64_bytes = base64.b64encode(image_file.read())
10
+
11
+ # 3. Decode bytes to a UTF-8 string so it can be printed
12
+ base64_string = base64_bytes.decode('utf-8')
13
+
14
+ return base64_string
15
+
16
+ favicon_data = f'data:image/png;base64,{png_to_base64_string(Path(__file__).parent.parent.parent / "resources/img/favicon.png")}'
17
+
@@ -0,0 +1,133 @@
1
+ #Standard Library Imports
2
+ import pdb
3
+
4
+ #Installed dependency imports
5
+ #None yet
6
+
7
+ #Teamtools Studio Imports
8
+ from tts_utilities.logger import create_logger
9
+
10
+ #This Library Imports
11
+ from tts_html_utils.core.components.base import HtmlComponentSimple
12
+ from tts_html_utils.core.components.misc import Div
13
+
14
+ logger = create_logger(name='tts_html_utils.core.components.flexbox')
15
+
16
+ class FlexContainer(HtmlComponentSimple):
17
+ """
18
+ A flexible container that uses CSS Flexbox for layout.
19
+
20
+ **Concept:**
21
+ The FlexContainer is a div with display:flex that allows for easy
22
+ arrangement of child elements in rows or columns with precise control
23
+ over sizing and spacing.
24
+
25
+ :param children: Child components to be arranged in the flex container.
26
+ :type children: list or single HtmlComponent
27
+ :param direction: The flex-direction property. Options: 'row', 'column', 'row-reverse', 'column-reverse'.
28
+ :type direction: str, optional
29
+ :param justify: The justify-content property. Options: 'flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly'.
30
+ :type justify: str, optional
31
+ :param align: The align-items property. Options: 'flex-start', 'flex-end', 'center', 'stretch', 'baseline'.
32
+ :type align: str, optional
33
+ :param wrap: The flex-wrap property. Options: 'nowrap', 'wrap', 'wrap-reverse'.
34
+ :type wrap: str, optional
35
+ :param gap: The gap between flex items (e.g., '5px', '1rem').
36
+ :type gap: str, optional
37
+ :param kwargs: Additional keyword arguments passed to the parent Div.
38
+ """
39
+ TAG = 'div'
40
+
41
+ def __init__(self, children=None, direction='row', justify='flex-start',
42
+ align='stretch', wrap='nowrap', gap=None, **kwargs):
43
+ # Build the style dictionary for flexbox
44
+ style = kwargs.get('style', {})
45
+ style.update({
46
+ 'display': 'flex',
47
+ 'flex-direction': direction,
48
+ 'justify-content': justify,
49
+ 'align-items': align,
50
+ 'flex-wrap': wrap
51
+ })
52
+
53
+ # Add gap if specified
54
+ if gap:
55
+ style['gap'] = gap
56
+
57
+ kwargs['style'] = style
58
+ super().__init__(children=children, **kwargs)
59
+
60
+
61
+ class FlexItem(HtmlComponentSimple):
62
+ """
63
+ A flex item that can be placed inside a FlexContainer.
64
+
65
+ **Concept:**
66
+ FlexItems are divs with flex properties that control how they grow, shrink,
67
+ and what their base size is within a FlexContainer.
68
+
69
+ :param children: Child components inside this flex item.
70
+ :type children: list or single HtmlComponent
71
+ :param width: The width of the item (e.g., '50%', '200px', 'auto').
72
+ :type width: str, optional
73
+ :param height: The height of the item (e.g., '100px', 'auto').
74
+ :type height: str, optional
75
+ :param grow: The flex-grow property (how much the item can grow). Default is 0 (no growing).
76
+ :type grow: int, optional
77
+ :param shrink: The flex-shrink property (how much the item can shrink). Default is 1 (can shrink).
78
+ :type shrink: int, optional
79
+ :param basis: The flex-basis property (base size before growing/shrinking). Default is 'auto'.
80
+ :type basis: str, optional
81
+ :param align_self: Override the container's align-items for this specific item.
82
+ :type align_self: str, optional
83
+ :param kwargs: Additional keyword arguments passed to the parent Div.
84
+ """
85
+ TAG = 'div'
86
+
87
+ def __init__(self, children=None, width=None, height=None, grow=None,
88
+ shrink=None, basis=None, align_self=None, **kwargs):
89
+ # Build the style dictionary for the flex item
90
+ style = kwargs.get('style', {})
91
+
92
+ # Add width and height if specified
93
+ if width:
94
+ style['width'] = width
95
+ if height:
96
+ style['height'] = height
97
+
98
+ # Add flex properties if specified
99
+ if grow is not None or shrink is not None or basis is not None:
100
+ flex_parts = []
101
+ flex_parts.append(str(grow) if grow is not None else '0')
102
+ flex_parts.append(str(shrink) if shrink is not None else '1')
103
+ flex_parts.append(basis if basis is not None else 'auto')
104
+ style['flex'] = ' '.join(flex_parts)
105
+
106
+ # Add align-self if specified
107
+ if align_self:
108
+ style['align-self'] = align_self
109
+
110
+ kwargs['style'] = style
111
+ super().__init__(children=children, **kwargs)
112
+
113
+
114
+ class FlexRow(FlexContainer):
115
+ """
116
+ A convenience class for a horizontal flex container.
117
+
118
+ This is a FlexContainer with direction='row' preset.
119
+ """
120
+ def __init__(self, children=None, **kwargs):
121
+ kwargs['direction'] = 'row'
122
+ super().__init__(children=children, **kwargs)
123
+
124
+
125
+ class FlexColumn(FlexContainer):
126
+ """
127
+ A convenience class for a vertical flex container.
128
+
129
+ This is a FlexContainer with direction='column' preset.
130
+ """
131
+ def __init__(self, children=None, **kwargs):
132
+ kwargs['direction'] = 'column'
133
+ super().__init__(children=children, **kwargs)