streamlit-plugins 0.7.13__tar.gz → 0.7.14__tar.gz

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 (94) hide show
  1. {streamlit_plugins-0.7.13/streamlit_plugins.egg-info → streamlit_plugins-0.7.14}/PKG-INFO +1 -1
  2. streamlit_plugins-0.7.14/streamlit_plugins/components/document_blocks/__init__.py +736 -0
  3. streamlit_plugins-0.7.14/streamlit_plugins/extension/dynamic_container/__init__.py +156 -0
  4. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14/streamlit_plugins.egg-info}/PKG-INFO +1 -1
  5. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins.egg-info/SOURCES.txt +2 -0
  6. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/LICENSE +0 -0
  7. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/README.md +0 -0
  8. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/setup.cfg +0 -0
  9. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/setup.py +0 -0
  10. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/__init__.py +0 -0
  11. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/__init__.py +0 -0
  12. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/annotated_text/__init__.py +0 -0
  13. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/input_chat_advance/__init__.py +0 -0
  14. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/__init__.py +0 -0
  15. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/asset-manifest.json +0 -0
  16. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/bootstrap.min.css +0 -0
  17. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/custom.css +0 -0
  18. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/index.html +0 -0
  19. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/css/2.4f758a59.chunk.css +0 -0
  20. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/css/2.4f758a59.chunk.css.map +0 -0
  21. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/2.524324ba.chunk.js +0 -0
  22. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/2.524324ba.chunk.js.LICENSE.txt +0 -0
  23. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/2.524324ba.chunk.js.map +0 -0
  24. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/main.34b82d10.chunk.js +0 -0
  25. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/main.34b82d10.chunk.js.map +0 -0
  26. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/runtime-main.65660c44.js +0 -0
  27. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/label_studio/frontend/build/static/js/runtime-main.65660c44.js.map +0 -0
  28. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/loader/__init__.py +0 -0
  29. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/__init__.py +0 -0
  30. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/config.py +0 -0
  31. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/CrossOriginInterface.js +0 -0
  32. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/asset-manifest.json +0 -0
  33. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/bootstrap.min.css +0 -0
  34. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/bootstrap.min.css.map +0 -0
  35. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/index.html +0 -0
  36. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/material-fonts.css +0 -0
  37. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/material.woff2 +0 -0
  38. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/st-styles.css +0 -0
  39. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/static/js/main.8bc23bfa.js +0 -0
  40. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/static/js/main.8bc23bfa.js.LICENSE.txt +0 -0
  41. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/static/js/main.8bc23bfa.js.map +0 -0
  42. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/frontend/build/style.css +0 -0
  43. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/navbar/inject_script.py +0 -0
  44. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/sankey_input/__init__.py +0 -0
  45. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/selectbox_enrich/__init__.py +0 -0
  46. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/skeleton/__init__.py +0 -0
  47. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/skeleton/template_builder.py +0 -0
  48. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/__init__.py +0 -0
  49. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/asset-manifest.json +0 -0
  50. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/index.html +0 -0
  51. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/drawsvg.js +0 -0
  52. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/drawsvg.v7.js +0 -0
  53. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/favicon.ico +0 -0
  54. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/images/sort_asc.png +0 -0
  55. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/images/sort_both.png +0 -0
  56. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/images/sort_desc.png +0 -0
  57. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/2.9cedfdc4.chunk.js +0 -0
  58. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/2.9cedfdc4.chunk.js.LICENSE.txt +0 -0
  59. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/2.9cedfdc4.chunk.js.map +0 -0
  60. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/main.1dfd8582.chunk.js +0 -0
  61. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/main.1dfd8582.chunk.js.map +0 -0
  62. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/runtime-main.8efd821a.js +0 -0
  63. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/js/runtime-main.8efd821a.js.map +0 -0
  64. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/snakeviz.css +0 -0
  65. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/snakeviz.js +0 -0
  66. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/d3.v3.min.js +0 -0
  67. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/immutable.min.js +0 -0
  68. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/jquery-3.2.1.min.js +0 -0
  69. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/jquery.dataTables.min.css +0 -0
  70. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/jquery.dataTables.min.js +0 -0
  71. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/static/vendor/lodash.min.js +0 -0
  72. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/templates/dir.html +0 -0
  73. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/snakeviz/frontend/build/templates/viz.html +0 -0
  74. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/theme_changer/__init__.py +0 -0
  75. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/theme_changer/config.py +0 -0
  76. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/theme_changer/entity.py +0 -0
  77. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/theme_changer/frontend/build/CrossOriginInterface.js +0 -0
  78. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/components/theme_changer/inject_script.py +0 -0
  79. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/extension/__init__.py +0 -0
  80. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/extension/button_group/__init__.py +0 -0
  81. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/__init__.py +0 -0
  82. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/multilit/__init__.py +0 -0
  83. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/multilit/app_wrapper.py +0 -0
  84. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/multilit/loading_engine.py +0 -0
  85. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/multilit/multilit.py +0 -0
  86. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/__init__.py +0 -0
  87. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/advanced_patterns.py +0 -0
  88. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/lab.py +0 -0
  89. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/reactive.py +0 -0
  90. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/reactlit.py +0 -0
  91. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins/framework/reactlit/test_reactive.py +0 -0
  92. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins.egg-info/dependency_links.txt +0 -0
  93. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins.egg-info/requires.txt +0 -0
  94. {streamlit_plugins-0.7.13 → streamlit_plugins-0.7.14}/streamlit_plugins.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: streamlit-plugins
3
- Version: 0.7.13
3
+ Version: 0.7.14
4
4
  Summary: Components and Frameworks to give new features to streamlit
5
5
  Home-page: https://github.com/quiradev/streamlit-plugins
6
6
  Author: Victor Quilon Ranera
@@ -0,0 +1,736 @@
1
+ import base64
2
+ import mimetypes
3
+ from io import BytesIO
4
+ from pathlib import Path
5
+ from typing import Optional, Union, Literal
6
+
7
+ from PIL import Image
8
+ from streamlit.components.v2 import component as create_component
9
+ from streamlit.elements.lib.layout_utils import Width, Height
10
+
11
+ ___HTML = """
12
+ <div id="image-viewer-container">
13
+ <div id="filters-bar" class="filters-bar"></div>
14
+ <div class="image-wrapper">
15
+ <img id="doc-image" src="" alt="Documento" />
16
+ <svg id="reading-order-parents-svg" class="reading-order-layer"></svg>
17
+ <div id="bboxes-overlay"></div>
18
+ </div>
19
+ </div>
20
+ """
21
+ __CSS = """
22
+ /* COMPONENTE */
23
+ #image-viewer-container {
24
+ flex: 1;
25
+ max-width: 550px;
26
+ background: #fff;
27
+ padding: 10px;
28
+ border-radius: 8px;
29
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
30
+ }
31
+
32
+ .image-wrapper {
33
+ position: relative;
34
+ display: inline-block;
35
+ width: 100%;
36
+ }
37
+
38
+ #doc-image {
39
+ display: block;
40
+ width: 100%;
41
+ height: auto;
42
+ border-radius: 4px;
43
+ }
44
+
45
+ #bboxes-overlay {
46
+ position: absolute;
47
+ top: 0;
48
+ left: 0;
49
+ width: 100%;
50
+ height: 100%;
51
+ }
52
+
53
+ /* CAJAS */
54
+ .bbox {
55
+ position: absolute;
56
+ box-sizing: border-box;
57
+ cursor: pointer;
58
+ /* Usamos variables CSS para inyectar los colores desde JS */
59
+ border: 2px solid var(--border-color);
60
+ background-color: var(--bg-normal);
61
+ transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
62
+ }
63
+
64
+ .bbox:hover {
65
+ background-color: var(--bg-hover);
66
+ }
67
+
68
+ .bbox-tooltip {
69
+ position: absolute;
70
+ bottom: 100%;
71
+ left: -2px;
72
+ color: #fff;
73
+ padding: 0.25rem 0.5rem;
74
+ font-size: 0.875rem;
75
+ font-weight: bold;
76
+ white-space: nowrap;
77
+ border-radius: 3px 3px 3px 0;
78
+ opacity: 0;
79
+ visibility: hidden;
80
+ transition: opacity 0.15s ease;
81
+ pointer-events: none;
82
+ z-index: 100;
83
+ }
84
+
85
+ .bbox:hover > .bbox-tooltip {
86
+ opacity: 1;
87
+ visibility: visible;
88
+ }
89
+
90
+ .bbox-parent {
91
+ z-index: 10;
92
+ }
93
+
94
+ .bbox-child {
95
+ opacity: 0;
96
+ pointer-events: none;
97
+ z-index: 20;
98
+ }
99
+
100
+ /* Manejo del hover nativo a nivel de grupo (Reemplaza los eventos de JS) */
101
+ .bbox-group:hover .bbox-child {
102
+ opacity: 1;
103
+ pointer-events: auto;
104
+ }
105
+
106
+ /* ESTADO SELECCIONADO AL HACER CLICK */
107
+ .bbox.is-selected {
108
+ background-color: var(--bg-hover) !important;
109
+ border-width: 3px !important;
110
+ box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.9), 0 0 10px rgba(0, 0, 0, 0.6);
111
+ /* Estas dos líneas aseguran que un hijo seleccionado siempre sea visible */
112
+ opacity: 1 !important;
113
+ pointer-events: auto !important;
114
+ }
115
+
116
+ .bbox.is-selected > .bbox-tooltip {
117
+ opacity: 1;
118
+ visibility: visible;
119
+ }
120
+
121
+ .bbox-group:has(.bbox-child.is-selected) .bbox-parent > .bbox-tooltip {
122
+ opacity: 1;
123
+ visibility: visible;
124
+ }
125
+
126
+ /* BARRA DE FILTROS */
127
+ .filters-bar {
128
+ display: flex;
129
+ gap: 8px;
130
+ flex-wrap: wrap;
131
+ margin-bottom: 12px;
132
+ min-height: 28px;
133
+ }
134
+
135
+ .filter-btn {
136
+ border: 1px solid #ccc;
137
+ padding: 0.25rem 0.5rem;
138
+ border-radius: 12px;
139
+ font-size: 0.875rem;
140
+ font-weight: bold;
141
+ cursor: pointer;
142
+ transition: all 0.2s ease;
143
+ }
144
+
145
+ .filter-btn:hover {
146
+ opacity: 0.85;
147
+ }
148
+
149
+ /* Las clases activas usarán los estilos personalizados del objeto */
150
+ .filter-btn.is-active {
151
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
152
+ }
153
+
154
+ /* OCULTAR CAJAS FILTRADAS */
155
+ .bbox.is-filtered-out {
156
+ display: none !important;
157
+ }
158
+
159
+ /* PADRE "FANTASMA" (No hace match, pero su hijo sí) */
160
+ .bbox.is-ghost {
161
+ border-color: #b0b0b0 !important;
162
+ background-color: rgba(176, 176, 176, 0.15) !important;
163
+ pointer-events: none !important; /* No se puede hacer click */
164
+ z-index: 5 !important;
165
+ }
166
+
167
+ /* Tooltip del padre fantasma (siempre visible y en gris) */
168
+ .bbox.is-ghost > .bbox-tooltip {
169
+ background-color: #b0b0b0 !important;
170
+ opacity: 1 !important;
171
+ visibility: visible !important;
172
+ }
173
+
174
+ /* HIJOS QUE HACEN MATCH (Visibles de inmediato sin necesidad de hover) */
175
+ .bbox-child.is-forced-visible {
176
+ opacity: 1 !important;
177
+ pointer-events: auto !important;
178
+ z-index: 25 !important;
179
+ }
180
+
181
+ /* CAPA SVG ORDEN DE LECTURA */
182
+ .reading-order-layer {
183
+ position: absolute;
184
+ top: 0;
185
+ left: 0;
186
+ width: 100%;
187
+ height: 100%;
188
+ pointer-events: none; /* Crucial para no bloquear los clicks de las cajas */
189
+ z-index: 30; /* Por debajo del hover/selección, por encima de las hijas */
190
+ display: none; /* Oculto por defecto */
191
+ }
192
+
193
+ /* Cuando se marca el checkbox HTML */
194
+ .reading-order-layer.is-visible {
195
+ display: block;
196
+ }
197
+
198
+ /* Animación de la línea controlada por variable CSS --ro-speed */
199
+ .ro-line {
200
+ fill: none;
201
+ stroke-width: 2.5;
202
+ stroke-dasharray: 6;
203
+ animation: dash-animation var(--ro-speed, 20s) linear infinite;
204
+ }
205
+
206
+ /* SVG local de cada grupo (para los hijos) */
207
+ .children-ro-layer {
208
+ position: absolute;
209
+ top: 0;
210
+ left: 0;
211
+ width: 100%;
212
+ height: 100%;
213
+ pointer-events: none;
214
+ z-index: 25; /* Por debajo de los tooltips, por encima de las cajas */
215
+ opacity: 0; /* Oculto por defecto */
216
+ transition: opacity 0.2s ease;
217
+ }
218
+
219
+ /* Solo se muestra si pasas el ratón por el grupo, o si el padre o un hijo tienen .is-selected */
220
+ /* Solo se muestra si el ratón pasa por el grupo O si está seleccionado,
221
+ PERO ÚNICAMENTE si el SVG general de padres está visible */
222
+ .image-wrapper:has(#reading-order-parents-svg.is-visible) .bbox-group:hover .children-ro-layer,
223
+ .image-wrapper:has(#reading-order-parents-svg.is-visible) .bbox-group:has(.bbox.is-selected) .children-ro-layer {
224
+ opacity: 1 !important;
225
+ }
226
+ /* Las clases de animación que ya tenías */
227
+ .ro-line-child {
228
+ fill: none;
229
+ stroke-width: 2;
230
+ stroke-dasharray: 4;
231
+ animation: dash-animation var(--ro-child-speed, 15s) linear infinite;
232
+ }
233
+
234
+ @keyframes dash-animation {
235
+ to { stroke-dashoffset: -200; }
236
+ }
237
+ """
238
+ __JS = """
239
+ // =====================================================================
240
+ // 1. LÓGICA DEL COMPONENTE (Exportable a Streamlit V2)
241
+ // =====================================================================
242
+
243
+ function hexToRgba(hex, alpha) {
244
+ const r = parseInt(hex.slice(1, 3), 16);
245
+ const g = parseInt(hex.slice(3, 5), 16);
246
+ const b = parseInt(hex.slice(5, 7), 16);
247
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
248
+ }
249
+
250
+ // ponytail: normalize bbox from polygon/bbox format or legacy x,y,w,h
251
+ // converts pixel coords to % if document_size provided, otherwise returns as-is
252
+ function extractBBoxCoords(box, docSize) {
253
+ let coords = null;
254
+
255
+ if (box.bbox && Array.isArray(box.bbox)) {
256
+ // bbox format: [x, y, x2, y2]
257
+ coords = { x: box.bbox[0], y: box.bbox[1], x2: box.bbox[2], y2: box.bbox[3] };
258
+ } else if (box.polygon && Array.isArray(box.polygon) && box.polygon.length >= 2) {
259
+ // polygon format: [[x1,y1], [x2,y2], ...]
260
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
261
+ box.polygon.forEach(([x, y]) => {
262
+ minX = Math.min(minX, x);
263
+ minY = Math.min(minY, y);
264
+ maxX = Math.max(maxX, x);
265
+ maxY = Math.max(maxY, y);
266
+ });
267
+ coords = { x: minX, y: minY, x2: maxX, y2: maxY };
268
+ } else if (box.x !== undefined && box.y !== undefined && box.w !== undefined && box.h !== undefined) {
269
+ // legacy format: x, y, w, h (assuming percentages)
270
+ coords = { x: box.x, y: box.y, x2: box.x + box.w, y2: box.y + box.h, isLegacy: true };
271
+ }
272
+
273
+ if (!coords) return null;
274
+
275
+ // normalize to percentages if docSize provided and coords are in pixels
276
+ if (docSize && docSize.width && docSize.height && !coords.isLegacy) {
277
+ const isPixel = coords.x > 100 || coords.y > 100 || coords.x2 > 100 || coords.y2 > 100;
278
+ if (isPixel) {
279
+ coords.x = (coords.x / docSize.width) * 100;
280
+ coords.y = (coords.y / docSize.height) * 100;
281
+ coords.x2 = (coords.x2 / docSize.width) * 100;
282
+ coords.y2 = (coords.y2 / docSize.height) * 100;
283
+ }
284
+ }
285
+
286
+ return coords;
287
+ }
288
+
289
+ // Función auxiliar para gestionar la selección visual sin repintar todo el DOM
290
+ function selectBox(boxElement) {
291
+ // 1. Buscamos si hay algún elemento previamente seleccionado y le quitamos la clase
292
+ const currentSelected = document.querySelector('.bbox.is-selected');
293
+ if (currentSelected) {
294
+ currentSelected.classList.remove('is-selected');
295
+ }
296
+ // 2. Añadimos la clase al nuevo elemento clickeado
297
+ if (boxElement !== currentSelected) {
298
+ boxElement.classList.add('is-selected');
299
+ }
300
+ }
301
+
302
+ export default function(component) {
303
+ const { setStateValue, setTriggerValue, parentElement, data } = component;
304
+
305
+ const imgEl = parentElement.querySelector('#doc-image');
306
+ const overlay = parentElement.querySelector('#bboxes-overlay');
307
+ const filtersBar = parentElement.querySelector('#filters-bar');
308
+ const svgParents = parentElement.querySelector('#reading-order-parents-svg');
309
+
310
+ if (data && data.imageUrl && imgEl.src !== data.imageUrl) {
311
+ imgEl.src = data.imageUrl;
312
+ }
313
+
314
+ overlay.innerHTML = '';
315
+ filtersBar.innerHTML = '';
316
+ if (svgParents) svgParents.innerHTML = '';
317
+ if (!data || !data.blocks) return;
318
+
319
+ // 0. DOCUMENTO SIZE (para normalizar coordenadas en píxeles a porcentajes)
320
+ const docSize = data.document_size || null;
321
+
322
+ // 1. EXTRAEMOS ESTILOS Y CONFIGURACIÓN GLOBAL
323
+ const boxStyles = data.styles && data.styles.blocks ? data.styles.blocks : {};
324
+ const normalAlpha = boxStyles.normalAlpha !== undefined ? boxStyles.normalAlpha : 0.15;
325
+ const hoverAlpha = boxStyles.hoverAlpha !== undefined ? boxStyles.hoverAlpha : 0.35;
326
+ const childNormalAlpha = boxStyles.childNormalAlpha !== undefined ? boxStyles.childNormalAlpha : 0.25;
327
+ const childHoverAlpha = boxStyles.childHoverAlpha !== undefined ? boxStyles.childHoverAlpha : 0.50;
328
+
329
+ const initialSelectedId = data.selected_id || null;
330
+
331
+ // Configuración de Orden de Lectura
332
+ // ponytail: orderPath is true (use natural order), string (use path), or null (skip)
333
+ const orderPath = data.reading_order === true || typeof data.reading_order === 'string' ? data.reading_order : null;
334
+ const roStyles = data.styles && data.styles.reading_order ? data.styles.reading_order : {};
335
+ const cColor = roStyles.children_color || '#00e5ff';
336
+ const cSpeed = roStyles.children_dashAnimationSpeed !== undefined ? roStyles.children_dashAnimationSpeed : 1;
337
+ const cMarkerHTML = roStyles.children_markerHeadHTML || `
338
+ <marker id="arrowHeadC" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="4" markerHeight="4" orient="auto-start-reverse">
339
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="${cColor}" />
340
+ </marker>
341
+ `;
342
+
343
+ // 2. CREACIÓN DE CAJAS Y SVGs LOCALES (HIJOS)
344
+ data.blocks.forEach(parentBox => {
345
+ const pCoords = extractBBoxCoords(parentBox, docSize);
346
+ if (!pCoords) return;
347
+
348
+ const group = document.createElement('div');
349
+ group.className = 'bbox-group';
350
+ group.style.position = 'absolute';
351
+ const pW = pCoords.x2 - pCoords.x;
352
+ const pH = pCoords.y2 - pCoords.y;
353
+ const isPercent = true;
354
+ const unit = '%';
355
+ group.style.left = `${pCoords.x}${unit}`;
356
+ group.style.top = `${pCoords.y}${unit}`;
357
+ group.style.width = `${pW}${unit}`;
358
+ group.style.height = `${pH}${unit}`;
359
+
360
+ // --- CAJA PADRE ---
361
+ const pDiv = document.createElement('div');
362
+ pDiv.className = 'bbox bbox-parent';
363
+ pDiv.id = parentBox.id;
364
+ pDiv.style.width = '100%';
365
+ pDiv.style.height = '100%';
366
+
367
+ if (initialSelectedId === parentBox.id) pDiv.classList.add('is-selected');
368
+
369
+ const pColor = parentBox.color || '#ff4b4b';
370
+ pDiv.style.setProperty('--border-color', pColor);
371
+ pDiv.style.setProperty('--bg-normal', hexToRgba(pColor, normalAlpha));
372
+ pDiv.style.setProperty('--bg-hover', hexToRgba(pColor, hoverAlpha));
373
+
374
+ const pTooltip = document.createElement('div');
375
+ pTooltip.className = 'bbox-tooltip';
376
+ pTooltip.style.backgroundColor = pColor;
377
+ pTooltip.textContent = parentBox.type;
378
+ pDiv.appendChild(pTooltip);
379
+
380
+ if (data.set_state_on === 'hover') {
381
+ pDiv.onmouseenter = () => {
382
+ selectBox(pDiv);
383
+ if (typeof setStateValue === 'function') setStateValue('selected_id', parentBox.id);
384
+ };
385
+ pDiv.onmouseleave = () => {
386
+ if (typeof setStateValue === 'function') setStateValue('selected_id', null);
387
+ };
388
+ } else {
389
+ pDiv.onclick = (e) => {
390
+ e.stopPropagation();
391
+ selectBox(pDiv);
392
+ if (typeof setStateValue === 'function') setStateValue('selected_id', parentBox.id);
393
+ };
394
+ }
395
+
396
+ group.appendChild(pDiv);
397
+
398
+ // --- CAJAS HIJAS ---
399
+ if (parentBox.children && parentBox.children.length > 0) {
400
+ parentBox.children.forEach(childBox => {
401
+ const cCoords = extractBBoxCoords(childBox, docSize);
402
+ if (!cCoords) return;
403
+
404
+ const cDiv = document.createElement('div');
405
+ cDiv.className = 'bbox bbox-child';
406
+ cDiv.id = childBox.id;
407
+
408
+ if (initialSelectedId === childBox.id) cDiv.classList.add('is-selected');
409
+
410
+ const relX = ((cCoords.x - pCoords.x) / pW) * 100;
411
+ const relY = ((cCoords.y - pCoords.y) / pH) * 100;
412
+ const cW = cCoords.x2 - cCoords.x;
413
+ const cH = cCoords.y2 - cCoords.y;
414
+ const relW = (cW / pW) * 100;
415
+ const relH = (cH / pH) * 100;
416
+
417
+ cDiv.style.left = `${relX}%`;
418
+ cDiv.style.top = `${relY}%`;
419
+ cDiv.style.width = `${relW}%`;
420
+ cDiv.style.height = `${relH}%`;
421
+
422
+ const cColorBox = childBox.color || '#00cc66';
423
+ cDiv.style.setProperty('--border-color', cColorBox);
424
+ cDiv.style.setProperty('--bg-normal', hexToRgba(cColorBox, childNormalAlpha));
425
+ cDiv.style.setProperty('--bg-hover', hexToRgba(cColorBox, childHoverAlpha));
426
+
427
+ const cTooltip = document.createElement('div');
428
+ cTooltip.className = 'bbox-tooltip';
429
+ cTooltip.style.backgroundColor = cColorBox;
430
+ cTooltip.textContent = childBox.type;
431
+ cDiv.appendChild(cTooltip);
432
+
433
+ if (data.set_state_on === 'hover') {
434
+ cDiv.onmouseenter = () => {
435
+ selectBox(cDiv);
436
+ if (typeof setStateValue === 'function') setStateValue('selected_id', childBox.id);
437
+ };
438
+ cDiv.onmouseleave = () => {
439
+ if (typeof setStateValue === 'function') setStateValue('selected_id', null);
440
+ };
441
+ } else {
442
+ cDiv.onclick = (e) => {
443
+ e.stopPropagation();
444
+ selectBox(cDiv);
445
+ if (typeof setStateValue === 'function') setStateValue('selected_id', childBox.id);
446
+ };
447
+ }
448
+
449
+ group.appendChild(cDiv);
450
+ });
451
+ }
452
+
453
+ // --- ORDEN DE LECTURA (SVG LOCAL PARA HIJOS) ---
454
+ if (orderPath && parentBox.children && parentBox.children.length >= 2) {
455
+ const orderedChildren = parentBox.children.map((child, index) => {
456
+ const orderVal = orderPath === true ? index : parseInt(orderPath.split('.').reduce((acc, part) => acc && acc[part], child), 10);
457
+ return { child, orderVal };
458
+ }).filter(item => !isNaN(item.orderVal)).sort((a, b) => a.orderVal - b.orderVal);
459
+
460
+ if (orderedChildren.length > 1) {
461
+ const localSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
462
+ localSvg.setAttribute('class', 'children-ro-layer');
463
+ localSvg.style.setProperty('--ro-child-speed', `${15 * cSpeed}s`);
464
+
465
+ const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
466
+ defs.innerHTML = cMarkerHTML;
467
+ localSvg.appendChild(defs);
468
+
469
+ for (let i = 0; i < orderedChildren.length - 1; i++) {
470
+ const current = orderedChildren[i].child;
471
+ const next = orderedChildren[i + 1].child;
472
+
473
+ const curCoords = extractBBoxCoords(current, docSize);
474
+ const nextCoords = extractBBoxCoords(next, docSize);
475
+ if (!curCoords || !nextCoords) continue;
476
+
477
+ // Centro superior absoluto
478
+ const curW = curCoords.x2 - curCoords.x;
479
+ const nextW = nextCoords.x2 - nextCoords.x;
480
+ const absX1 = curCoords.x + (curW / 2);
481
+ const absY1 = curCoords.y;
482
+ const absX2 = nextCoords.x + (nextW / 2);
483
+ const absY2 = nextCoords.y;
484
+
485
+ // Convertimos a porcentajes relativos al grupo padre
486
+ const relX1 = ((absX1 - pCoords.x) / pW) * 100;
487
+ const relY1 = ((absY1 - pCoords.y) / pH) * 100;
488
+ const relX2 = ((absX2 - pCoords.x) / pW) * 100;
489
+ const relY2 = ((absY2 - pCoords.y) / pH) * 100;
490
+
491
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
492
+ line.setAttribute('x1', `${relX1}%`);
493
+ line.setAttribute('y1', `${relY1}%`);
494
+ line.setAttribute('x2', `${relX2}%`);
495
+ line.setAttribute('y2', `${relY2}%`);
496
+ line.setAttribute('class', 'ro-line-child');
497
+ line.style.stroke = cColor;
498
+
499
+ const matchIdC = cMarkerHTML.match(/id="([^"]+)"/);
500
+ line.setAttribute('marker-end', `url(#${matchIdC ? matchIdC[1] : 'arrowHeadC'})`);
501
+
502
+ localSvg.appendChild(line);
503
+ }
504
+ group.appendChild(localSvg);
505
+ }
506
+ }
507
+
508
+ overlay.appendChild(group);
509
+ });
510
+
511
+ // 3. MOTOR DE FILTROS
512
+ if (data.filters) {
513
+ const activeFilters = new Set();
514
+ const filterStylesConfig = data.styles && data.styles.filters ? data.styles.filters : {};
515
+
516
+ Object.entries(data.filters).forEach(([filterName, expression]) => {
517
+ const btn = document.createElement('button');
518
+ btn.className = 'filter-btn';
519
+ btn.textContent = filterName;
520
+ btn.style.background = '#f0f0f0';
521
+ btn.style.color = '#333';
522
+
523
+ const customStyle = filterStylesConfig[filterName] || {};
524
+
525
+ btn.onclick = () => {
526
+ const isActive = btn.classList.toggle('is-active');
527
+
528
+ if (isActive) {
529
+ activeFilters.add(expression);
530
+ Object.assign(btn.style, customStyle);
531
+ } else {
532
+ activeFilters.delete(expression);
533
+ btn.style.background = '#f0f0f0';
534
+ btn.style.color = '#333';
535
+ }
536
+
537
+ if (activeFilters.size === 0) {
538
+ parentElement.querySelectorAll('.bbox').forEach(el => {
539
+ el.classList.remove('is-filtered-out', 'is-ghost', 'is-forced-visible');
540
+ });
541
+ return;
542
+ }
543
+
544
+ const parsedFilters = Array.from(activeFilters).map(expr => {
545
+ const match = expr.match(/^([\\w.]+)\\s*([=><])\\s*(.*)$/);
546
+ return match ? { path: match[1], operator: match[2], target: match[3] } : null;
547
+ }).filter(Boolean);
548
+
549
+ const evaluateBox = (boxData) => {
550
+ return parsedFilters.some(({ path, operator, target }) => {
551
+ const resolvedValue = path.split('.').reduce((acc, part) => acc && acc[part], boxData);
552
+ const evaluate = (v) => {
553
+ if (v === undefined || v === null) return false;
554
+ if (operator === '=') {
555
+ const regexMatch = target.match(/^\\/(.*)\\/([gimsuy]*)$/);
556
+ if (regexMatch) {
557
+ try { return new RegExp(regexMatch[1], regexMatch[2]).test(String(v)); } catch(e) { return false; }
558
+ }
559
+ return String(v) === String(target);
560
+ }
561
+ if (operator === '>') return Number(v) > Number(target);
562
+ if (operator === '<') return Number(v) < Number(target);
563
+ return false;
564
+ };
565
+ return Array.isArray(resolvedValue) ? resolvedValue.some(evaluate) : evaluate(resolvedValue);
566
+ });
567
+ };
568
+
569
+ data.blocks.forEach(parentBox => {
570
+ const pMatch = evaluateBox(parentBox);
571
+ const matchingChildren = [];
572
+
573
+ if (parentBox.children) {
574
+ parentBox.children.forEach(childBox => {
575
+ if (evaluateBox(childBox)) matchingChildren.push(childBox.id);
576
+ });
577
+ }
578
+
579
+ const pEl = document.getElementById(parentBox.id);
580
+ if (!pEl) return;
581
+
582
+ if (pMatch || matchingChildren.length > 0) {
583
+ pEl.classList.remove('is-filtered-out');
584
+ if (!pMatch && matchingChildren.length > 0) pEl.classList.add('is-ghost');
585
+ else pEl.classList.remove('is-ghost');
586
+ } else {
587
+ pEl.classList.add('is-filtered-out');
588
+ pEl.classList.remove('is-ghost');
589
+ }
590
+
591
+ if (parentBox.children) {
592
+ parentBox.children.forEach(childBox => {
593
+ const cEl = document.getElementById(childBox.id);
594
+ if (!cEl) return;
595
+ if (matchingChildren.includes(childBox.id)) {
596
+ cEl.classList.remove('is-filtered-out');
597
+ cEl.classList.add('is-forced-visible');
598
+ } else {
599
+ cEl.classList.add('is-filtered-out');
600
+ cEl.classList.remove('is-forced-visible');
601
+ }
602
+ });
603
+ }
604
+ });
605
+ };
606
+ filtersBar.appendChild(btn);
607
+ });
608
+ }
609
+
610
+ // 4. ORDEN DE LECTURA (SVG GLOBAL PARA PADRES)
611
+ if (orderPath && svgParents) {
612
+ svgParents.classList.add('is-visible');
613
+ const pColor = roStyles.color || '#ff9800';
614
+ const pSpeed = roStyles.dashAnimationSpeed !== undefined ? roStyles.dashAnimationSpeed : 1;
615
+ svgParents.style.setProperty('--ro-speed', `${20 * pSpeed}s`);
616
+
617
+ const orderedParents = data.blocks.map((box, index) => {
618
+ const orderVal = orderPath === true ? index : parseInt(orderPath.split('.').reduce((acc, part) => acc && acc[part], box), 10);
619
+ return { box, orderVal };
620
+ }).filter(item => !isNaN(item.orderVal)).sort((a, b) => a.orderVal - b.orderVal);
621
+
622
+ if (orderedParents.length > 1) {
623
+ const defsP = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
624
+ defsP.innerHTML = roStyles.markerHeadHTML || `
625
+ <marker id="arrowHeadP" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
626
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="${pColor}" />
627
+ </marker>
628
+ `;
629
+ svgParents.appendChild(defsP);
630
+
631
+ for (let i = 0; i < orderedParents.length - 1; i++) {
632
+ const current = orderedParents[i].box;
633
+ const next = orderedParents[i + 1].box;
634
+
635
+ const curCoords = extractBBoxCoords(current, docSize);
636
+ const nextCoords = extractBBoxCoords(next, docSize);
637
+ if (!curCoords || !nextCoords) continue;
638
+
639
+ const curW = curCoords.x2 - curCoords.x;
640
+ const nextW = nextCoords.x2 - nextCoords.x;
641
+
642
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
643
+ line.setAttribute('x1', `${curCoords.x + (curW / 2)}%`);
644
+ line.setAttribute('y1', `${curCoords.y}%`);
645
+ line.setAttribute('x2', `${nextCoords.x + (nextW / 2)}%`);
646
+ line.setAttribute('y2', `${nextCoords.y}%`);
647
+ line.setAttribute('class', 'ro-line');
648
+ line.style.stroke = pColor;
649
+
650
+ const matchId = (roStyles.markerHeadHTML || '').match(/id="([^"]+)"/);
651
+ line.setAttribute('marker-end', `url(#${matchId ? matchId[1] : 'arrowHeadP'})`);
652
+ svgParents.appendChild(line);
653
+ }
654
+ }
655
+ }
656
+ };
657
+ """
658
+
659
+ _DOCUMENT_BLOCKS_COMPONENT = create_component(
660
+ "document_blocks",
661
+ html=___HTML,
662
+ css=__CSS,
663
+ js=__JS,
664
+ isolate_styles=False,
665
+ )
666
+
667
+ ImageLike = Union[
668
+ str,
669
+ Path,
670
+ Image.Image
671
+ ]
672
+
673
+
674
+ def _to_image_src(image: ImageLike) -> str:
675
+ """Normalize supported image inputs into an HTML img.src-compatible string."""
676
+ if isinstance(image, str):
677
+ # Accept URL, relative path served by app, or already-encoded data URL.
678
+ return image
679
+
680
+ if isinstance(image, Path):
681
+ mime_type = mimetypes.guess_type(image.name)[0] or "image/png"
682
+ encoded = base64.b64encode(image.read_bytes()).decode("ascii")
683
+ return f"data:{mime_type};base64,{encoded}"
684
+
685
+ if isinstance(image, Image.Image):
686
+ img_format = (image.format or "PNG").upper()
687
+ mime_type = Image.MIME.get(img_format, f"image/{img_format.lower()}")
688
+ buffer = BytesIO()
689
+ image.save(buffer, format=img_format)
690
+ encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
691
+ return f"data:{mime_type};base64,{encoded}"
692
+
693
+ raise ValueError("Unsupported image type")
694
+
695
+
696
+ def st_document_blocks(
697
+ image: ImageLike,
698
+ filters: dict,
699
+ blocks: list,
700
+ document_size: Optional[tuple[float | int, float | int]] = None,
701
+ reading_order: Optional[str | bool] = None,
702
+ set_state_on: Literal["hover", "click"] = "click",
703
+ default_selected: Optional[str | int] = None,
704
+ custom_styles: Optional[dict] = None,
705
+ key: Optional[str] = None, width: Width = "stretch", height: Height = "content"
706
+ ) -> Optional[str|int]:
707
+ image_url = _to_image_src(image)
708
+
709
+ doc_size = None
710
+ if document_size:
711
+ doc_size = {"width": document_size[0], "height": document_size[1]}
712
+
713
+ data = {
714
+ "imageUrl": image_url,
715
+ "filters": filters,
716
+ "reading_order": reading_order,
717
+ "blocks": blocks,
718
+ "document_size": doc_size,
719
+ "set_state_on": set_state_on,
720
+ "selected_id": default_selected,
721
+ "styles": custom_styles,
722
+ }
723
+
724
+ result = _DOCUMENT_BLOCKS_COMPONENT(
725
+ key=key,
726
+ data=data,
727
+ default={'selected_id': default_selected},
728
+ on_selected_id_change=lambda: None,
729
+ width=width,
730
+ height=height,
731
+ )
732
+
733
+ return result.get("selected_id")
734
+
735
+
736
+ __all__ = ["st_document_blocks"]
@@ -0,0 +1,156 @@
1
+ import contextlib
2
+ from dataclasses import dataclass
3
+ from typing import Generator, Any, Optional
4
+
5
+ import streamlit as st
6
+ from streamlit.components.v2 import component as create_component
7
+ from streamlit.elements.lib.layout_utils import Width, Height, HorizontalAlignment, VerticalAlignment, Gap
8
+
9
+ ___HTML = """"""
10
+ __CSS = """
11
+ .scroll-select {
12
+ animation: scroll-select-animation 1s ease-in-out;
13
+ }
14
+ @keyframes scroll-select-animation {
15
+ 0% { background-color: color-mix(in srgb, var(--scroll-bg-color) 50%, transparent); }
16
+ 100% { background-color: transparent; }
17
+ }
18
+ """
19
+ __JS = """
20
+ export default function(component) {
21
+ const { data } = component;
22
+ document.querySelector(`.st-key-${data.sourceContainerKey}-${data.targetContainerKey}-sync`).style.display = "none";
23
+ setTimeout(function() {
24
+ const sourceContainer = document.querySelector(data.sourceContainerSelector);
25
+ const targetContainer = document.querySelector(data.targetContainerSelector);
26
+ const scrollTo = document.querySelector(data.scrollToSelector);
27
+
28
+ const syncSize = () => {
29
+ const sourceHeight = sourceContainer.clientHeight;
30
+ const sourceWidth = sourceContainer.clientWidth;
31
+
32
+ if (data.mimicVertical) {
33
+ targetContainer.style.height = `${sourceHeight}px`;
34
+ targetContainer.parentElement.style.height = `${sourceHeight}px`;
35
+ }
36
+ if (data.mimicHorizontal) {
37
+ targetContainer.style.width = `${sourceWidth}px`;
38
+ targetContainer.parentElement.style.width = `${sourceWidth}px`;
39
+ }
40
+ };
41
+
42
+ // Set overflow
43
+ if (data.mimicVertical && !data.mimicHorizontal) {
44
+ targetContainer.style.overflowY = 'auto';
45
+ }
46
+ if (data.mimicHorizontal && !data.mimicVertical) {
47
+ targetContainer.style.overflowX = 'auto';
48
+ }
49
+ if (data.mimicVertical && data.mimicHorizontal) {
50
+ targetContainer.style.overflow = 'auto';
51
+ }
52
+
53
+ syncSize();
54
+
55
+ // Observer pattern: sync on resize
56
+ const observer = new ResizeObserver(syncSize);
57
+ observer.observe(sourceContainer);
58
+ window.addEventListener('resize', syncSize);
59
+
60
+ if (scrollTo) {
61
+ const previousActive = document.activeElement;
62
+ targetContainer.focus({ preventScroll: true });
63
+ scrollTo.scrollIntoView({
64
+ block: 'center', // Centra verticalmente respecto al padre
65
+ inline: 'center', // Centra horizontalmente si aplica
66
+ behavior: 'smooth'
67
+ });
68
+ if (data.flashBrightScrollTarget) {
69
+ if (data.highlightItemClass) {
70
+ document.querySelectorAll(`.${data.highlightItemClass}`).forEach(el => {
71
+ el.classList.remove(data.highlightItemClass);
72
+ });
73
+ scrollTo.classList.add(data.highlightItemClass);
74
+ }
75
+ scrollTo.style.setProperty("--scroll-bg-color", data.flashBrightScrollBg || "yellow")
76
+ scrollTo.classList.add("scroll-select");
77
+ setTimeout(function() {
78
+ scrollTo.classList.remove("scroll-select");
79
+ }, 1000);
80
+ }
81
+
82
+ previousActive?.focus({ preventScroll: true });
83
+ }
84
+
85
+ }, 0);
86
+ }
87
+ """
88
+ _DYNAMIC_CONTAINER_COMPONENT = create_component(
89
+ "dynamic_container_script",
90
+ # html=___HTML,
91
+ css=__CSS,
92
+ js=__JS,
93
+ isolate_styles=False,
94
+ )
95
+
96
+ @dataclass
97
+ class DynamicContainerDataInput:
98
+ sourceContainerSelector: str
99
+ targetContainerSelector: str
100
+ sourceContainerKey: str
101
+ targetContainerKey: str
102
+ mimicVertical: bool
103
+ mimicHorizontal: bool
104
+
105
+ scrollToSelector: Optional[str] = None
106
+ flashBrightScrollTarget: Optional[bool] = None
107
+ flashBrightScrollBg: Optional[str] = None
108
+ highlightItemClass: Optional[str] = None
109
+
110
+ def to_dict(self):
111
+ return self.__dict__
112
+
113
+ @contextlib.contextmanager
114
+ def st_dynamic_container(
115
+ *,
116
+ container_source_key: str,
117
+ key: str,
118
+ mimic_vertical: bool = True,
119
+ mimic_horizontal: bool = False,
120
+ border: bool | None = None,
121
+ width: Width = "stretch",
122
+ height: Height = "content",
123
+ horizontal: bool = False,
124
+ horizontal_alignment: HorizontalAlignment = "left",
125
+ vertical_alignment: VerticalAlignment = "top",
126
+ gap: Gap | None = "small",
127
+ autoscroll: bool | None = None,
128
+ ) -> Generator[DynamicContainerDataInput, Any, None]:
129
+ data = DynamicContainerDataInput(
130
+ sourceContainerSelector=f".st-key-{container_source_key}",
131
+ targetContainerSelector=f".st-key-{key}",
132
+ sourceContainerKey=container_source_key,
133
+ targetContainerKey=key,
134
+ mimicVertical=mimic_vertical,
135
+ mimicHorizontal=mimic_horizontal,
136
+ )
137
+
138
+ with st.container(
139
+ key=key,
140
+ border=border,
141
+ width=width,
142
+ height=height,
143
+ horizontal=horizontal,
144
+ horizontal_alignment=horizontal_alignment,
145
+ vertical_alignment=vertical_alignment,
146
+ gap=gap,
147
+ autoscroll=autoscroll,
148
+ ):
149
+ yield data
150
+ _DYNAMIC_CONTAINER_COMPONENT(
151
+ key=f"{container_source_key}-{key}-sync",
152
+ data=data.to_dict()
153
+ )
154
+
155
+
156
+ __all__ = ["st_dynamic_container"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: streamlit-plugins
3
- Version: 0.7.13
3
+ Version: 0.7.14
4
4
  Summary: Components and Frameworks to give new features to streamlit
5
5
  Home-page: https://github.com/quiradev/streamlit-plugins
6
6
  Author: Victor Quilon Ranera
@@ -9,6 +9,7 @@ streamlit_plugins.egg-info/requires.txt
9
9
  streamlit_plugins.egg-info/top_level.txt
10
10
  streamlit_plugins/components/__init__.py
11
11
  streamlit_plugins/components/annotated_text/__init__.py
12
+ streamlit_plugins/components/document_blocks/__init__.py
12
13
  streamlit_plugins/components/input_chat_advance/__init__.py
13
14
  streamlit_plugins/components/label_studio/__init__.py
14
15
  streamlit_plugins/components/label_studio/frontend/build/asset-manifest.json
@@ -77,6 +78,7 @@ streamlit_plugins/components/theme_changer/inject_script.py
77
78
  streamlit_plugins/components/theme_changer/frontend/build/CrossOriginInterface.js
78
79
  streamlit_plugins/extension/__init__.py
79
80
  streamlit_plugins/extension/button_group/__init__.py
81
+ streamlit_plugins/extension/dynamic_container/__init__.py
80
82
  streamlit_plugins/framework/__init__.py
81
83
  streamlit_plugins/framework/multilit/__init__.py
82
84
  streamlit_plugins/framework/multilit/app_wrapper.py