figrecipe 0.7.4__py3-none-any.whl → 0.9.0__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 (143) hide show
  1. figrecipe/__init__.py +74 -76
  2. figrecipe/__main__.py +12 -0
  3. figrecipe/_api/_panel.py +67 -0
  4. figrecipe/_api/_save.py +100 -4
  5. figrecipe/_cli/__init__.py +7 -0
  6. figrecipe/_cli/_compose.py +87 -0
  7. figrecipe/_cli/_convert.py +117 -0
  8. figrecipe/_cli/_crop.py +82 -0
  9. figrecipe/_cli/_edit.py +70 -0
  10. figrecipe/_cli/_extract.py +128 -0
  11. figrecipe/_cli/_fonts.py +47 -0
  12. figrecipe/_cli/_info.py +67 -0
  13. figrecipe/_cli/_main.py +58 -0
  14. figrecipe/_cli/_reproduce.py +79 -0
  15. figrecipe/_cli/_style.py +77 -0
  16. figrecipe/_cli/_validate.py +66 -0
  17. figrecipe/_cli/_version.py +50 -0
  18. figrecipe/_composition/__init__.py +32 -0
  19. figrecipe/_composition/_alignment.py +452 -0
  20. figrecipe/_composition/_compose.py +179 -0
  21. figrecipe/_composition/_import_axes.py +127 -0
  22. figrecipe/_composition/_visibility.py +125 -0
  23. figrecipe/_dev/__init__.py +2 -0
  24. figrecipe/_dev/browser/__init__.py +69 -0
  25. figrecipe/_dev/browser/_audio.py +240 -0
  26. figrecipe/_dev/browser/_caption.py +356 -0
  27. figrecipe/_dev/browser/_click_effect.py +146 -0
  28. figrecipe/_dev/browser/_cursor.py +196 -0
  29. figrecipe/_dev/browser/_highlight.py +105 -0
  30. figrecipe/_dev/browser/_narration.py +237 -0
  31. figrecipe/_dev/browser/_recorder.py +446 -0
  32. figrecipe/_dev/browser/_utils.py +178 -0
  33. figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
  34. figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
  35. figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
  36. figrecipe/_editor/__init__.py +36 -36
  37. figrecipe/_editor/_bbox/_extract.py +155 -9
  38. figrecipe/_editor/_bbox/_extract_text.py +124 -0
  39. figrecipe/_editor/_call_overrides.py +183 -0
  40. figrecipe/_editor/_datatable_plot_handlers.py +249 -0
  41. figrecipe/_editor/_figure_layout.py +211 -0
  42. figrecipe/_editor/_flask_app.py +157 -16
  43. figrecipe/_editor/_helpers.py +17 -8
  44. figrecipe/_editor/_hitmap/_detect.py +89 -32
  45. figrecipe/_editor/_hitmap_main.py +4 -4
  46. figrecipe/_editor/_overrides.py +4 -1
  47. figrecipe/_editor/_plot_types_registry.py +190 -0
  48. figrecipe/_editor/_render_overrides.py +38 -11
  49. figrecipe/_editor/_renderer.py +46 -1
  50. figrecipe/_editor/_routes_annotation.py +114 -0
  51. figrecipe/_editor/_routes_axis.py +35 -6
  52. figrecipe/_editor/_routes_captions.py +130 -0
  53. figrecipe/_editor/_routes_composition.py +270 -0
  54. figrecipe/_editor/_routes_core.py +15 -173
  55. figrecipe/_editor/_routes_datatable.py +364 -0
  56. figrecipe/_editor/_routes_element.py +37 -19
  57. figrecipe/_editor/_routes_files.py +443 -0
  58. figrecipe/_editor/_routes_image.py +200 -0
  59. figrecipe/_editor/_routes_snapshot.py +94 -0
  60. figrecipe/_editor/_routes_style.py +28 -8
  61. figrecipe/_editor/_templates/__init__.py +40 -2
  62. figrecipe/_editor/_templates/_html.py +97 -103
  63. figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
  64. figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
  65. figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
  66. figrecipe/_editor/_templates/_html_datatable.py +92 -0
  67. figrecipe/_editor/_templates/_scripts/__init__.py +58 -0
  68. figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
  69. figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
  70. figrecipe/_editor/_templates/_scripts/_api.py +1 -1
  71. figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
  72. figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
  73. figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
  74. figrecipe/_editor/_templates/_scripts/_core.py +94 -37
  75. figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
  76. figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
  77. figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
  78. figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
  79. figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
  80. figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
  81. figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
  82. figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
  83. figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
  84. figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
  85. figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
  86. figrecipe/_editor/_templates/_scripts/_element_editor.py +17 -2
  87. figrecipe/_editor/_templates/_scripts/_files.py +274 -40
  88. figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
  89. figrecipe/_editor/_templates/_scripts/_hitmap.py +87 -84
  90. figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
  91. figrecipe/_editor/_templates/_scripts/_legend_drag.py +5 -0
  92. figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
  93. figrecipe/_editor/_templates/_scripts/_panel_drag.py +219 -48
  94. figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
  95. figrecipe/_editor/_templates/_scripts/_panel_position.py +238 -54
  96. figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
  97. figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
  98. figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
  99. figrecipe/_editor/_templates/_scripts/_selection.py +8 -1
  100. figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
  101. figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
  102. figrecipe/_editor/_templates/_scripts/_zoom.py +52 -19
  103. figrecipe/_editor/_templates/_styles/__init__.py +9 -0
  104. figrecipe/_editor/_templates/_styles/_base.py +47 -0
  105. figrecipe/_editor/_templates/_styles/_buttons.py +127 -6
  106. figrecipe/_editor/_templates/_styles/_composition.py +87 -0
  107. figrecipe/_editor/_templates/_styles/_controls.py +168 -3
  108. figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
  109. figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
  110. figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
  111. figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
  112. figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
  113. figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
  114. figrecipe/_editor/_templates/_styles/_dynamic_props.py +5 -5
  115. figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
  116. figrecipe/_editor/_templates/_styles/_forms.py +98 -0
  117. figrecipe/_editor/_templates/_styles/_hitmap.py +7 -0
  118. figrecipe/_editor/_templates/_styles/_modals.py +29 -0
  119. figrecipe/_editor/_templates/_styles/_overlays.py +5 -5
  120. figrecipe/_editor/_templates/_styles/_preview.py +213 -8
  121. figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
  122. figrecipe/_editor/static/audio/click.mp3 +0 -0
  123. figrecipe/_editor/static/click.mp3 +0 -0
  124. figrecipe/_editor/static/icons/favicon.ico +0 -0
  125. figrecipe/_integrations/__init__.py +17 -0
  126. figrecipe/_integrations/_scitex_stats.py +298 -0
  127. figrecipe/_params/_DECORATION_METHODS.py +2 -0
  128. figrecipe/_recorder.py +28 -3
  129. figrecipe/_reproducer/_core.py +60 -49
  130. figrecipe/_utils/__init__.py +3 -0
  131. figrecipe/_utils/_bundle.py +205 -0
  132. figrecipe/_wrappers/_axes.py +150 -2
  133. figrecipe/_wrappers/_caption_generator.py +218 -0
  134. figrecipe/_wrappers/_figure.py +26 -1
  135. figrecipe/_wrappers/_stat_annotation.py +274 -0
  136. figrecipe/styles/_style_applier.py +10 -2
  137. figrecipe/styles/presets/SCITEX.yaml +11 -4
  138. {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/METADATA +144 -146
  139. figrecipe-0.9.0.dist-info/RECORD +277 -0
  140. figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
  141. figrecipe-0.7.4.dist-info/RECORD +0 -188
  142. {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
  143. {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Panel accordion JavaScript for the figure editor.
4
+
5
+ This module handles collapsing/expanding of panels:
6
+ - File browser panel (left)
7
+ - Data panel (second from left)
8
+ - Properties panel (right)
9
+ - State persistence in localStorage
10
+ """
11
+
12
+ SCRIPTS_ACCORDION = """
13
+ // ==================== PANEL ACCORDION ====================
14
+ // Enables collapsing/expanding panels with state persistence
15
+
16
+ let propertiesPanelCollapsed = false;
17
+ let dataPanelCollapsed = false;
18
+ let previewPanelCollapsed = false;
19
+ // Note: fileBrowserCollapsed is declared in _files.py
20
+
21
+ // Smart expansion: when a panel collapses, expand collapsed neighbors to fill space
22
+ function checkSmartExpansion(collapsedPanelId) {
23
+ // Panel order: Files | Data | Canvas | Properties
24
+ // When one collapses, adjacent collapsed panels should expand
25
+
26
+ const filePanel = document.getElementById('file-browser-panel');
27
+ const dataPanel = document.getElementById('datatable-panel');
28
+ const previewPanel = document.getElementById('preview-panel');
29
+ const propsPanel = document.getElementById('controls-panel');
30
+
31
+ const isFileCollapsed = filePanel?.classList.contains('collapsed');
32
+ const isDataCollapsed = dataPanel?.classList.contains('collapsed');
33
+ const isPreviewCollapsed = previewPanel?.classList.contains('collapsed');
34
+ const isPropsCollapsed = propsPanel?.classList.contains('collapsed');
35
+
36
+ console.log('[SmartExpansion] Checking after', collapsedPanelId, 'collapsed');
37
+ console.log('[SmartExpansion] States - Files:', isFileCollapsed, 'Data:', isDataCollapsed,
38
+ 'Preview:', isPreviewCollapsed, 'Props:', isPropsCollapsed);
39
+
40
+ // When Canvas collapses -> expand Data if collapsed
41
+ if (collapsedPanelId === 'preview-panel' && isDataCollapsed) {
42
+ console.log('[SmartExpansion] Canvas collapsed, expanding Data panel');
43
+ expandDataPanel();
44
+ }
45
+
46
+ // When Data collapses -> expand Canvas if collapsed (prioritize canvas for viewing)
47
+ if (collapsedPanelId === 'datatable-panel' && isPreviewCollapsed) {
48
+ console.log('[SmartExpansion] Data collapsed, expanding Canvas panel');
49
+ expandPreviewPanel();
50
+ }
51
+
52
+ // When Properties collapses -> expand Canvas if collapsed
53
+ if (collapsedPanelId === 'controls-panel' && isPreviewCollapsed) {
54
+ console.log('[SmartExpansion] Properties collapsed, expanding Canvas panel');
55
+ expandPreviewPanel();
56
+ }
57
+
58
+ // When Files collapses -> expand Data if collapsed
59
+ if (collapsedPanelId === 'file-browser-panel' && isDataCollapsed) {
60
+ console.log('[SmartExpansion] Files collapsed, expanding Data panel');
61
+ expandDataPanel();
62
+ }
63
+ }
64
+
65
+ // Helper functions to expand without toggling
66
+ function expandDataPanel() {
67
+ const panel = document.getElementById('datatable-panel');
68
+ const collapseBtn = document.getElementById('btn-collapse-datatable');
69
+ if (!panel || !panel.classList.contains('collapsed')) return;
70
+
71
+ dataPanelCollapsed = false;
72
+ panel.classList.remove('collapsed');
73
+ if (collapseBtn) collapseBtn.title = 'Collapse panel';
74
+ localStorage.setItem('figrecipe_data_collapsed', 'false');
75
+ console.log('[PanelAccordion] Data panel auto-expanded');
76
+ }
77
+
78
+ function expandPreviewPanel() {
79
+ const panel = document.getElementById('preview-panel');
80
+ const collapseBtn = document.getElementById('btn-collapse-preview');
81
+ if (!panel || !panel.classList.contains('collapsed')) return;
82
+
83
+ previewPanelCollapsed = false;
84
+ panel.classList.remove('collapsed');
85
+ if (collapseBtn) collapseBtn.title = 'Collapse canvas';
86
+ localStorage.setItem('figrecipe_preview_collapsed', 'false');
87
+ console.log('[PanelAccordion] Preview panel auto-expanded');
88
+ }
89
+
90
+ function expandPropertiesPanel() {
91
+ const panel = document.getElementById('controls-panel');
92
+ const collapseBtn = document.getElementById('btn-collapse-properties');
93
+ if (!panel || !panel.classList.contains('collapsed')) return;
94
+
95
+ propertiesPanelCollapsed = false;
96
+ panel.classList.remove('collapsed');
97
+ if (collapseBtn) collapseBtn.title = 'Collapse panel';
98
+ localStorage.setItem('figrecipe_properties_collapsed', 'false');
99
+ console.log('[PanelAccordion] Properties panel auto-expanded');
100
+ }
101
+
102
+ function expandFileBrowserPanel() {
103
+ const panel = document.getElementById('file-browser-panel');
104
+ const collapseBtn = document.getElementById('btn-collapse-browser');
105
+ if (!panel || !panel.classList.contains('collapsed')) return;
106
+
107
+ // fileBrowserCollapsed is defined in _files.py
108
+ if (typeof fileBrowserCollapsed !== 'undefined') fileBrowserCollapsed = false;
109
+ panel.classList.remove('collapsed');
110
+ if (collapseBtn) {
111
+ collapseBtn.innerHTML = '❮';
112
+ collapseBtn.title = 'Collapse panel';
113
+ }
114
+ localStorage.setItem('figrecipe_filebrowser_collapsed', 'false');
115
+ console.log('[PanelAccordion] File browser auto-expanded');
116
+ }
117
+
118
+ function initPanelAccordion() {
119
+ // Properties panel collapse button
120
+ const propertiesCollapseBtn = document.getElementById('btn-collapse-properties');
121
+ const propertiesPanel = document.getElementById('controls-panel');
122
+
123
+ if (propertiesCollapseBtn && propertiesPanel) {
124
+ propertiesCollapseBtn.addEventListener('click', togglePropertiesPanel);
125
+
126
+ // Restore collapsed state from localStorage
127
+ const wasCollapsed = localStorage.getItem('figrecipe_properties_collapsed');
128
+ if (wasCollapsed === 'true') {
129
+ propertiesPanelCollapsed = true;
130
+ propertiesPanel.classList.add('collapsed');
131
+ propertiesCollapseBtn.title = 'Expand panel';
132
+ }
133
+ }
134
+
135
+ // Data panel collapse button
136
+ const dataCollapseBtn = document.getElementById('btn-collapse-datatable');
137
+ const dataPanel = document.getElementById('datatable-panel');
138
+
139
+ if (dataCollapseBtn && dataPanel) {
140
+ dataCollapseBtn.addEventListener('click', toggleDataPanel);
141
+
142
+ // Restore collapsed state from localStorage
143
+ const wasDataCollapsed = localStorage.getItem('figrecipe_data_collapsed');
144
+ if (wasDataCollapsed === 'true') {
145
+ dataPanelCollapsed = true;
146
+ dataPanel.classList.add('collapsed');
147
+ dataCollapseBtn.title = 'Expand panel';
148
+ }
149
+ }
150
+
151
+ // File browser accordion - add localStorage persistence
152
+ const fileBrowserPanel = document.getElementById('file-browser-panel');
153
+ if (fileBrowserPanel) {
154
+ const wasFileBrowserCollapsed = localStorage.getItem('figrecipe_filebrowser_collapsed');
155
+ if (wasFileBrowserCollapsed === 'true') {
156
+ fileBrowserPanel.classList.add('collapsed');
157
+ const collapseBtn = document.getElementById('btn-collapse-browser');
158
+ if (collapseBtn) {
159
+ collapseBtn.innerHTML = '❯';
160
+ collapseBtn.title = 'Expand panel';
161
+ }
162
+ }
163
+ }
164
+
165
+ // Preview/canvas panel collapse button
166
+ const previewCollapseBtn = document.getElementById('btn-collapse-preview');
167
+ const previewPanel = document.getElementById('preview-panel');
168
+
169
+ if (previewCollapseBtn && previewPanel) {
170
+ previewCollapseBtn.addEventListener('click', togglePreviewPanel);
171
+
172
+ // Restore collapsed state from localStorage
173
+ const wasPreviewCollapsed = localStorage.getItem('figrecipe_preview_collapsed');
174
+ if (wasPreviewCollapsed === 'true') {
175
+ previewPanelCollapsed = true;
176
+ previewPanel.classList.add('collapsed');
177
+ previewCollapseBtn.title = 'Expand canvas';
178
+ }
179
+ }
180
+
181
+ // Add keyboard shortcuts for toggling panels
182
+ document.addEventListener('keydown', (e) => {
183
+ // Alt+1: Toggle file browser
184
+ if (e.altKey && e.key === '1') {
185
+ e.preventDefault();
186
+ if (typeof toggleFileBrowser === 'function') {
187
+ toggleFileBrowser();
188
+ saveFileBrowserState();
189
+ }
190
+ }
191
+ // Alt+2: Toggle data panel
192
+ if (e.altKey && e.key === '2') {
193
+ e.preventDefault();
194
+ toggleDataPanel();
195
+ }
196
+ // Alt+3: Toggle canvas/preview panel
197
+ if (e.altKey && e.key === '3') {
198
+ e.preventDefault();
199
+ togglePreviewPanel();
200
+ }
201
+ // Alt+4: Toggle properties panel
202
+ if (e.altKey && e.key === '4') {
203
+ e.preventDefault();
204
+ togglePropertiesPanel();
205
+ }
206
+ });
207
+
208
+ console.log('[PanelAccordion] Initialized');
209
+ }
210
+
211
+ function toggleDataPanel() {
212
+ const panel = document.getElementById('datatable-panel');
213
+ const collapseBtn = document.getElementById('btn-collapse-datatable');
214
+ if (!panel) return;
215
+
216
+ dataPanelCollapsed = !dataPanelCollapsed;
217
+ panel.classList.toggle('collapsed', dataPanelCollapsed);
218
+
219
+ if (collapseBtn) {
220
+ collapseBtn.title = dataPanelCollapsed ? 'Expand panel' : 'Collapse panel';
221
+ }
222
+
223
+ // Persist state
224
+ localStorage.setItem('figrecipe_data_collapsed', dataPanelCollapsed);
225
+
226
+ console.log('[PanelAccordion] Data panel', dataPanelCollapsed ? 'collapsed' : 'expanded');
227
+
228
+ // Smart expansion: if collapsing, check if neighbors should expand
229
+ if (dataPanelCollapsed) {
230
+ checkSmartExpansion('datatable-panel');
231
+ }
232
+ }
233
+
234
+ function togglePropertiesPanel() {
235
+ const panel = document.getElementById('controls-panel');
236
+ const collapseBtn = document.getElementById('btn-collapse-properties');
237
+ if (!panel) return;
238
+
239
+ propertiesPanelCollapsed = !propertiesPanelCollapsed;
240
+ panel.classList.toggle('collapsed', propertiesPanelCollapsed);
241
+
242
+ if (collapseBtn) {
243
+ collapseBtn.title = propertiesPanelCollapsed ? 'Expand panel' : 'Collapse panel';
244
+ }
245
+
246
+ // Persist state
247
+ localStorage.setItem('figrecipe_properties_collapsed', propertiesPanelCollapsed);
248
+
249
+ console.log('[PanelAccordion] Properties panel', propertiesPanelCollapsed ? 'collapsed' : 'expanded');
250
+
251
+ // Smart expansion: if collapsing, check if neighbors should expand
252
+ if (propertiesPanelCollapsed) {
253
+ checkSmartExpansion('controls-panel');
254
+ }
255
+ }
256
+
257
+ function togglePreviewPanel() {
258
+ const panel = document.getElementById('preview-panel');
259
+ const collapseBtn = document.getElementById('btn-collapse-preview');
260
+ if (!panel) return;
261
+
262
+ previewPanelCollapsed = !previewPanelCollapsed;
263
+ panel.classList.toggle('collapsed', previewPanelCollapsed);
264
+
265
+ if (collapseBtn) {
266
+ collapseBtn.title = previewPanelCollapsed ? 'Expand canvas' : 'Collapse canvas';
267
+ }
268
+
269
+ // Persist state
270
+ localStorage.setItem('figrecipe_preview_collapsed', previewPanelCollapsed);
271
+
272
+ console.log('[PanelAccordion] Preview panel', previewPanelCollapsed ? 'collapsed' : 'expanded');
273
+
274
+ // Smart expansion: if collapsing, check if neighbors should expand
275
+ if (previewPanelCollapsed) {
276
+ checkSmartExpansion('preview-panel');
277
+ }
278
+ }
279
+
280
+ function saveFileBrowserState() {
281
+ const panel = document.getElementById('file-browser-panel');
282
+ if (panel) {
283
+ const isCollapsed = panel.classList.contains('collapsed');
284
+ localStorage.setItem('figrecipe_filebrowser_collapsed', isCollapsed);
285
+ }
286
+ }
287
+
288
+ // Hook into existing toggleFileBrowser to save state
289
+ const originalToggleFileBrowser = typeof window.toggleFileBrowser === 'function'
290
+ ? window.toggleFileBrowser
291
+ : null;
292
+
293
+ window.toggleFileBrowser = function() {
294
+ if (originalToggleFileBrowser) {
295
+ originalToggleFileBrowser();
296
+ } else {
297
+ // Fallback implementation
298
+ const panel = document.getElementById('file-browser-panel');
299
+ const collapseBtn = document.getElementById('btn-collapse-browser');
300
+ if (!panel) return;
301
+
302
+ panel.classList.toggle('collapsed');
303
+ if (collapseBtn) {
304
+ const isCollapsed = panel.classList.contains('collapsed');
305
+ collapseBtn.innerHTML = isCollapsed ? '❯' : '❮';
306
+ collapseBtn.title = isCollapsed ? 'Expand panel' : 'Collapse panel';
307
+ }
308
+ }
309
+ saveFileBrowserState();
310
+
311
+ // Smart expansion: if collapsing, check if neighbors should expand
312
+ const panel = document.getElementById('file-browser-panel');
313
+ if (panel && panel.classList.contains('collapsed')) {
314
+ checkSmartExpansion('file-browser-panel');
315
+ }
316
+ };
317
+
318
+ // Initialize on DOM ready
319
+ if (document.readyState === 'loading') {
320
+ document.addEventListener('DOMContentLoaded', initPanelAccordion);
321
+ } else {
322
+ initPanelAccordion();
323
+ }
324
+ """
325
+
326
+ __all__ = ["SCRIPTS_ACCORDION"]
327
+
328
+ # EOF