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,221 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Datatable right-click context menu JavaScript."""
4
+
5
+ JS_DATATABLE_CONTEXT_MENU = """
6
+ // ============================================================================
7
+ // Context Menu (Right-Click Menu)
8
+ // ============================================================================
9
+ let datatableContextMenu = null;
10
+
11
+ function createContextMenu() {
12
+ if (datatableContextMenu) return;
13
+
14
+ datatableContextMenu = document.createElement('div');
15
+ datatableContextMenu.className = 'datatable-context-menu';
16
+ datatableContextMenu.style.display = 'none';
17
+ datatableContextMenu.innerHTML = `
18
+ <div class="context-menu-item" data-action="cut">
19
+ Cut<span class="shortcut">Ctrl+X</span>
20
+ </div>
21
+ <div class="context-menu-item" data-action="copy">
22
+ Copy<span class="shortcut">Ctrl+C</span>
23
+ </div>
24
+ <div class="context-menu-item" data-action="paste">
25
+ Paste<span class="shortcut">Ctrl+V</span>
26
+ </div>
27
+ <div class="context-menu-divider"></div>
28
+ <div class="context-menu-item" data-action="clear">
29
+ Clear cells<span class="shortcut">Del</span>
30
+ </div>
31
+ <div class="context-menu-divider"></div>
32
+ <div class="context-menu-item" data-action="insert-row">
33
+ Insert row below
34
+ </div>
35
+ <div class="context-menu-item" data-action="insert-col">
36
+ Insert column right
37
+ </div>
38
+ <div class="context-menu-divider"></div>
39
+ <div class="context-menu-item" data-action="delete-row">
40
+ Delete row
41
+ </div>
42
+ <div class="context-menu-item" data-action="delete-col">
43
+ Delete column
44
+ </div>
45
+ `;
46
+ document.body.appendChild(datatableContextMenu);
47
+ setupContextMenuListeners();
48
+ }
49
+
50
+ function setupContextMenuListeners() {
51
+ if (!datatableContextMenu) return;
52
+
53
+ // Click on menu items
54
+ datatableContextMenu.querySelectorAll('.context-menu-item').forEach(item => {
55
+ item.addEventListener('click', (e) => {
56
+ e.stopPropagation();
57
+ const action = item.dataset.action;
58
+ handleContextMenuAction(action);
59
+ hideContextMenu();
60
+ });
61
+ });
62
+
63
+ // Hide on click outside
64
+ document.addEventListener('click', hideContextMenu);
65
+ document.addEventListener('scroll', hideContextMenu, true);
66
+ document.addEventListener('keydown', (e) => {
67
+ if (e.key === 'Escape') hideContextMenu();
68
+ });
69
+ }
70
+
71
+ function handleContextMenuAction(action) {
72
+ if (!datatableData) return;
73
+
74
+ switch (action) {
75
+ case 'cut':
76
+ // Simulate Ctrl+X
77
+ document.execCommand('cut');
78
+ break;
79
+ case 'copy':
80
+ // Copy selected cells as TSV
81
+ const copyText = getSelectedCellsAsTSV();
82
+ navigator.clipboard.writeText(copyText).catch(console.error);
83
+ break;
84
+ case 'paste':
85
+ // Paste from clipboard
86
+ navigator.clipboard.readText().then(text => {
87
+ if (!text || !datatableCurrentCell) return;
88
+ const rows = text.split('\\n').map(line => line.split('\\t'));
89
+ const startRow = datatableCurrentCell.row;
90
+ const startCol = datatableCurrentCell.col;
91
+ rows.forEach((rowData, rOffset) => {
92
+ const targetRow = startRow + rOffset;
93
+ if (targetRow >= datatableData.rows.length) return;
94
+ rowData.forEach((value, cOffset) => {
95
+ const targetCol = startCol + cOffset;
96
+ if (targetCol >= datatableData.columns.length) return;
97
+ datatableData.rows[targetRow][targetCol] = value;
98
+ });
99
+ });
100
+ renderDatatable();
101
+ }).catch(console.error);
102
+ break;
103
+ case 'clear':
104
+ clearSelectedCells();
105
+ break;
106
+ case 'insert-row':
107
+ insertRowBelow();
108
+ break;
109
+ case 'insert-col':
110
+ insertColumnRight();
111
+ break;
112
+ case 'delete-row':
113
+ deleteCurrentRow();
114
+ break;
115
+ case 'delete-col':
116
+ deleteCurrentColumn();
117
+ break;
118
+ }
119
+ }
120
+
121
+ function insertRowBelow() {
122
+ if (!datatableData || !datatableCurrentCell) return;
123
+ const row = datatableCurrentCell.row;
124
+ const newRow = datatableData.columns.map(() => '');
125
+ datatableData.rows.splice(row + 1, 0, newRow);
126
+ renderDatatable();
127
+ }
128
+
129
+ function insertColumnRight() {
130
+ if (!datatableData || !datatableCurrentCell) return;
131
+ const col = datatableCurrentCell.col;
132
+ const newColIdx = datatableData.columns.length;
133
+ datatableData.columns.splice(col + 1, 0, {
134
+ name: `col${newColIdx + 1}`,
135
+ type: 'numeric',
136
+ index: col + 1
137
+ });
138
+ // Reindex columns
139
+ datatableData.columns.forEach((c, i) => c.index = i);
140
+ // Add cell to all rows
141
+ datatableData.rows.forEach(row => row.splice(col + 1, 0, ''));
142
+ renderDatatable();
143
+ updateVarAssignSlots();
144
+ }
145
+
146
+ function deleteCurrentRow() {
147
+ if (!datatableData || !datatableCurrentCell) return;
148
+ if (datatableData.rows.length <= 1) return; // Keep at least 1 row
149
+ const row = datatableCurrentCell.row;
150
+ datatableData.rows.splice(row, 1);
151
+ if (datatableCurrentCell.row >= datatableData.rows.length) {
152
+ datatableCurrentCell.row = datatableData.rows.length - 1;
153
+ }
154
+ renderDatatable();
155
+ }
156
+
157
+ function deleteCurrentColumn() {
158
+ if (!datatableData || !datatableCurrentCell) return;
159
+ if (datatableData.columns.length <= 1) return; // Keep at least 1 col
160
+ const col = datatableCurrentCell.col;
161
+ datatableData.columns.splice(col, 1);
162
+ // Reindex columns
163
+ datatableData.columns.forEach((c, i) => c.index = i);
164
+ // Remove cell from all rows
165
+ datatableData.rows.forEach(row => row.splice(col, 1));
166
+ if (datatableCurrentCell.col >= datatableData.columns.length) {
167
+ datatableCurrentCell.col = datatableData.columns.length - 1;
168
+ }
169
+ renderDatatable();
170
+ updateVarAssignSlots();
171
+ }
172
+
173
+ function showContextMenu(e) {
174
+ if (!datatableContextMenu) createContextMenu();
175
+
176
+ e.preventDefault();
177
+ e.stopPropagation();
178
+
179
+ const x = e.clientX;
180
+ const y = e.clientY;
181
+
182
+ // Position off-screen to measure
183
+ datatableContextMenu.style.left = '-9999px';
184
+ datatableContextMenu.style.top = '-9999px';
185
+ datatableContextMenu.style.display = 'block';
186
+
187
+ const menuWidth = datatableContextMenu.offsetWidth;
188
+ const menuHeight = datatableContextMenu.offsetHeight;
189
+
190
+ // Adjust position to fit in viewport
191
+ let left = x;
192
+ let top = y;
193
+ if (x + menuWidth > window.innerWidth - 10) {
194
+ left = x - menuWidth;
195
+ }
196
+ if (y + menuHeight > window.innerHeight - 10) {
197
+ top = y - menuHeight;
198
+ }
199
+
200
+ datatableContextMenu.style.left = `${Math.max(10, left)}px`;
201
+ datatableContextMenu.style.top = `${Math.max(10, top)}px`;
202
+ }
203
+
204
+ function hideContextMenu() {
205
+ if (datatableContextMenu) {
206
+ datatableContextMenu.style.display = 'none';
207
+ }
208
+ }
209
+
210
+ // Attach context menu to table
211
+ function attachContextMenuListener() {
212
+ const table = document.querySelector('.datatable-table');
213
+ if (table) {
214
+ table.addEventListener('contextmenu', showContextMenu);
215
+ }
216
+ }
217
+ """
218
+
219
+ __all__ = ["JS_DATATABLE_CONTEXT_MENU"]
220
+
221
+ # EOF
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Core datatable JavaScript: state, panel toggle, initialization."""
4
+
5
+ JS_DATATABLE_CORE = """
6
+ // ============================================================================
7
+ // Datatable Panel State
8
+ // ============================================================================
9
+ let datatableData = null; // Parsed data: {columns: [...], rows: [...]}
10
+ let datatableSelectedColumns = new Set(); // Selected column indices
11
+ let datatablePlotType = 'plot'; // Default plot type
12
+ let datatableTargetAxis = null; // null = new figure, 0+ = existing axis index
13
+ let datatableVarAssignments = {}; // Variable name -> column index mapping
14
+
15
+ // ============================================================================
16
+ // Panel Initialization
17
+ // ============================================================================
18
+ function initDatatablePanel() {
19
+ // Initialize sub-modules
20
+ initDatatableDropzone();
21
+ initPlotTypeButtons();
22
+
23
+ // Initialize plot button (New = create new panel)
24
+ const plotBtn = document.getElementById('btn-datatable-plot');
25
+ if (plotBtn) {
26
+ plotBtn.addEventListener('click', () => {
27
+ datatableTargetAxis = null; // New panel
28
+ plotFromVarAssignments();
29
+ });
30
+ }
31
+
32
+ // Initialize split button dropdown
33
+ initPlotDropdown();
34
+
35
+ // Load existing data if available from figure
36
+ loadExistingData();
37
+
38
+ // Hook into canvas selection to sync with datatable
39
+ hookCanvasSelection();
40
+ }
41
+
42
+ function hookCanvasSelection() {
43
+ // Wrap the selectElement function to add datatable sync
44
+ if (typeof window.selectElement === 'function') {
45
+ const originalSelectElement = window.selectElement;
46
+ window.selectElement = function(element) {
47
+ originalSelectElement(element);
48
+ if (typeof syncDatatableToElement === 'function') {
49
+ syncDatatableToElement(element);
50
+ }
51
+ };
52
+ }
53
+
54
+ // Also hook clearSelection
55
+ if (typeof window.clearSelection === 'function') {
56
+ const originalClearSelection = window.clearSelection;
57
+ window.clearSelection = function() {
58
+ originalClearSelection();
59
+ if (typeof clearDatatableHighlight === 'function') {
60
+ clearDatatableHighlight();
61
+ }
62
+ };
63
+ }
64
+ }
65
+
66
+ function initPlotDropdown() {
67
+ const dropdownBtn = document.getElementById('btn-plot-dropdown');
68
+ const dropdownMenu = document.getElementById('plot-dropdown-menu');
69
+ if (!dropdownBtn || !dropdownMenu) return;
70
+
71
+ // Toggle dropdown
72
+ dropdownBtn.addEventListener('click', (e) => {
73
+ e.stopPropagation();
74
+ dropdownMenu.classList.toggle('show');
75
+ if (dropdownMenu.classList.contains('show')) {
76
+ populatePlotDropdown();
77
+ }
78
+ });
79
+
80
+ // Close on outside click
81
+ document.addEventListener('click', () => {
82
+ dropdownMenu.classList.remove('show');
83
+ });
84
+ }
85
+
86
+ function populatePlotDropdown() {
87
+ const menu = document.getElementById('plot-dropdown-menu');
88
+ if (!menu) return;
89
+
90
+ fetch('/get_axes_positions').then(r => r.json()).then(data => {
91
+ const axes = Object.keys(data)
92
+ .filter(k => k.startsWith('ax_'))
93
+ .sort((a, b) => {
94
+ const matchA = a.match(/ax_(\\d+)_(\\d+)/);
95
+ const matchB = b.match(/ax_(\\d+)_(\\d+)/);
96
+ if (matchA && matchB) {
97
+ return parseInt(matchA[2]) - parseInt(matchB[2]) || parseInt(matchA[1]) - parseInt(matchB[1]);
98
+ }
99
+ return 0;
100
+ });
101
+
102
+ let html = '';
103
+ axes.forEach((key, i) => {
104
+ html += `<button class="dropdown-item" onclick="addToPanel(${i})">Add to P${i + 1}</button>`;
105
+ });
106
+ menu.innerHTML = html || '<div class="dropdown-item" style="color:var(--text-secondary)">No panels</div>';
107
+ }).catch(() => {
108
+ menu.innerHTML = '<div class="dropdown-item" style="color:var(--text-secondary)">No panels</div>';
109
+ });
110
+ }
111
+
112
+ function addToPanel(axisIndex) {
113
+ datatableTargetAxis = axisIndex;
114
+ document.getElementById('plot-dropdown-menu').classList.remove('show');
115
+ plotFromVarAssignments();
116
+ }
117
+
118
+ // ============================================================================
119
+ // Clear Data
120
+ // ============================================================================
121
+ function clearDatatableData() {
122
+ datatableData = null;
123
+ datatableSelectedColumns.clear();
124
+ datatableVarAssignments = {};
125
+
126
+ const content = document.getElementById('datatable-content');
127
+ if (content) {
128
+ content.innerHTML = '';
129
+ }
130
+
131
+ // Show dropzone again
132
+ const dropzone = document.getElementById('datatable-dropzone');
133
+ if (dropzone) dropzone.style.display = 'block';
134
+
135
+ const toolbar = document.querySelector('.datatable-toolbar');
136
+ if (toolbar) toolbar.style.display = 'none';
137
+
138
+ const varAssign = document.getElementById('datatable-var-assign');
139
+ if (varAssign) varAssign.style.display = 'none';
140
+
141
+ updateSelectionInfo();
142
+ }
143
+
144
+ // Initialize on page load
145
+ document.addEventListener('DOMContentLoaded', initDatatablePanel);
146
+ """
147
+
148
+ __all__ = ["JS_DATATABLE_CORE"]
149
+
150
+ # EOF