flexdesk 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/THIRD_PARTY_LICENSES.md +53 -0
- package/css/base.css +3439 -0
- package/css/flexdesk.css +5797 -0
- package/css/overrides.css +2349 -0
- package/css/reset.css +46 -0
- package/css/tokens.css +137 -0
- package/dist/charts.js +780 -0
- package/dist/charts.js.map +7 -0
- package/dist/chunk-3PHCPZHT.js +222 -0
- package/dist/chunk-3PHCPZHT.js.map +7 -0
- package/dist/chunk-CT4YXXLP.js +1770 -0
- package/dist/chunk-CT4YXXLP.js.map +7 -0
- package/dist/chunk-DRYCDMEG.js +2217 -0
- package/dist/chunk-DRYCDMEG.js.map +7 -0
- package/dist/chunk-DVU44T77.js +855 -0
- package/dist/chunk-DVU44T77.js.map +7 -0
- package/dist/chunk-FL5KFNQH.js +640 -0
- package/dist/chunk-FL5KFNQH.js.map +7 -0
- package/dist/chunk-FOOS3T5L.js +117 -0
- package/dist/chunk-FOOS3T5L.js.map +7 -0
- package/dist/chunk-JYWURG5T.js +32 -0
- package/dist/chunk-JYWURG5T.js.map +7 -0
- package/dist/chunk-TLZUUFOE.js +276 -0
- package/dist/chunk-TLZUUFOE.js.map +7 -0
- package/dist/chunk-UCJ2WD4D.js +625 -0
- package/dist/chunk-UCJ2WD4D.js.map +7 -0
- package/dist/chunk-WVFGV5FT.js +205 -0
- package/dist/chunk-WVFGV5FT.js.map +7 -0
- package/dist/core.js +1098 -0
- package/dist/core.js.map +7 -0
- package/dist/editor.js +1661 -0
- package/dist/editor.js.map +7 -0
- package/dist/flexdesk.css +5797 -0
- package/dist/host.js +132 -0
- package/dist/host.js.map +7 -0
- package/dist/reset.css +46 -0
- package/dist/tile_registry-6WZPTUZV.js +28 -0
- package/dist/tile_registry-6WZPTUZV.js.map +7 -0
- package/dist/tiles.js +2068 -0
- package/dist/tiles.js.map +7 -0
- package/dist/tokens.css +137 -0
- package/dist/widgets.js +5438 -0
- package/dist/widgets.js.map +7 -0
- package/dist/wm.js +4830 -0
- package/dist/wm.js.map +7 -0
- package/package.json +80 -0
- package/src/charts/chart_types.js +699 -0
- package/src/charts/data_series_plot_window.js +761 -0
- package/src/charts/downsample.js +119 -0
- package/src/charts/plot_config.js +176 -0
- package/src/charts/plot_popout_window.js +1560 -0
- package/src/charts/plotly_wrapper.js +432 -0
- package/src/core/event_bus.js +230 -0
- package/src/core/logging.js +300 -0
- package/src/core/settings.js +813 -0
- package/src/core/state_guard.js +382 -0
- package/src/core/state_guard_config.js +309 -0
- package/src/core/state_machine.js +258 -0
- package/src/editor/editor_pane.js +264 -0
- package/src/editor/monaco_editor_factory.js +449 -0
- package/src/editor/monaco_loader.js +74 -0
- package/src/editor/notebook_search.js +460 -0
- package/src/editor/notebook_tab_bar.js +456 -0
- package/src/editor/notebook_undo_manager.js +108 -0
- package/src/editor/split_pane_container.js +197 -0
- package/src/help/help_modal.js +409 -0
- package/src/help/help_registry.js +65 -0
- package/src/host/host.js +69 -0
- package/src/host/pywebview_host.js +133 -0
- package/src/tiles/config_schema.js +256 -0
- package/src/tiles/layout_persistence.js +662 -0
- package/src/tiles/tile_base.js +785 -0
- package/src/tiles/tile_grid.js +905 -0
- package/src/tiles/tile_registry.js +136 -0
- package/src/tiling/command_palette.js +328 -0
- package/src/tiling/content_registry.js +110 -0
- package/src/tiling/desktops.js +133 -0
- package/src/tiling/entity_sources.js +179 -0
- package/src/tiling/keymap.js +156 -0
- package/src/tiling/kind_taxonomy.js +172 -0
- package/src/tiling/landing_table.js +717 -0
- package/src/tiling/loading_overlay.js +33 -0
- package/src/tiling/nav_panel.js +142 -0
- package/src/tiling/page_factory.js +215 -0
- package/src/tiling/panel_keys.js +145 -0
- package/src/tiling/shell.js +437 -0
- package/src/tiling/tile_breadcrumb.js +222 -0
- package/src/tiling/tile_renderer.js +459 -0
- package/src/tiling/tile_tab_menu.js +340 -0
- package/src/tiling/tile_tree.js +880 -0
- package/src/tiling/wm.js +1352 -0
- package/src/ui/base/component_base.js +30 -0
- package/src/ui/base/controller_base.js +48 -0
- package/src/ui/base/page_base.js +102 -0
- package/src/ui/components/about_dialog.js +224 -0
- package/src/ui/components/action_dropdown.js +389 -0
- package/src/ui/components/attribute_list_editor.js +652 -0
- package/src/ui/components/autocomplete_field.js +732 -0
- package/src/ui/components/computing_status_window.js +81 -0
- package/src/ui/components/confirm_dialog.js +307 -0
- package/src/ui/components/context_menu.js +87 -0
- package/src/ui/components/data_table.js +2228 -0
- package/src/ui/components/detail_header.js +177 -0
- package/src/ui/components/drag_reorder.js +0 -0
- package/src/ui/components/gallery_picker.js +147 -0
- package/src/ui/components/inline_renamer.js +129 -0
- package/src/ui/components/managed_window.js +804 -0
- package/src/ui/components/modal.js +512 -0
- package/src/ui/components/notification_history.js +264 -0
- package/src/ui/components/slide_out_panel.js +185 -0
- package/src/ui/components/slider_field.js +122 -0
- package/src/ui/components/sortable_list.js +309 -0
- package/src/ui/components/table_state_store.js +81 -0
- package/src/ui/components/toast.js +59 -0
- package/src/ui/components/tree_view.js +460 -0
- package/src/ui/components/window_taskbar.js +143 -0
- package/src/ui/controllers/panel_state_machine.js +634 -0
- package/src/ui/controllers/window_chrome_controller.js +659 -0
- package/src/ui/notification_center.js +576 -0
- package/src/ui/utils/overlay_scrollbar.js +492 -0
- package/src/ui/utils/raf_resize_observer.js +31 -0
- package/src/ui/utils/tooltip_service.js +406 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tooltip_service.js
|
|
3
|
+
*
|
|
4
|
+
* JS-driven tooltip system for elements with `has-tooltip` class and `data-tooltip` attribute.
|
|
5
|
+
* Also supports LaTeX tooltips with `has-latex-tooltip` and `data-latex-tooltip`.
|
|
6
|
+
*
|
|
7
|
+
* This replaces CSS-based ::after pseudo-element tooltips for better stacking context
|
|
8
|
+
* and dynamic content support.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
let tooltipElement = null;
|
|
12
|
+
let currentTarget = null;
|
|
13
|
+
let listenersAttached = false;
|
|
14
|
+
let mouseCheckInterval = null;
|
|
15
|
+
let lastMousePosition = { x: 0, y: 0 };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create or get the tooltip DOM element.
|
|
19
|
+
* @returns {HTMLElement}
|
|
20
|
+
*/
|
|
21
|
+
function createTooltipElement() {
|
|
22
|
+
if (tooltipElement) return tooltipElement;
|
|
23
|
+
|
|
24
|
+
tooltipElement = document.createElement('div');
|
|
25
|
+
tooltipElement.className = 'twm-latex-tooltip';
|
|
26
|
+
document.body.appendChild(tooltipElement);
|
|
27
|
+
return tooltipElement;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Position the tooltip relative to target element with smart auto-positioning.
|
|
32
|
+
* Automatically flips placement if tooltip would extend outside viewport.
|
|
33
|
+
* @param {HTMLElement} target - Element to position near
|
|
34
|
+
* @param {HTMLElement} tooltip - Tooltip element
|
|
35
|
+
*/
|
|
36
|
+
function positionTooltip(target, tooltip) {
|
|
37
|
+
const rect = target.getBoundingClientRect();
|
|
38
|
+
const tooltipRect = tooltip.getBoundingClientRect();
|
|
39
|
+
const gap = 12;
|
|
40
|
+
const margin = 10; // Minimum distance from viewport edge
|
|
41
|
+
|
|
42
|
+
// Get requested placement from data attributes or context
|
|
43
|
+
const requestedPlacement = (target.dataset?.tooltipPlacement || target.getAttribute('data-tooltip-placement') || '').toLowerCase() ||
|
|
44
|
+
(target.dataset?.latexPlacement || target.getAttribute('data-latex-placement') || '').toLowerCase() ||
|
|
45
|
+
(target.closest('.twm-global-top-bar') ? 'bottom' : (target.closest('.twm-fl-bar') ? 'right' : 'top'));
|
|
46
|
+
|
|
47
|
+
// Auto-positioning: determine best placement based on available space
|
|
48
|
+
let placement = requestedPlacement || 'top';
|
|
49
|
+
|
|
50
|
+
// Check if explicit placement is set (not auto)
|
|
51
|
+
const isExplicitPlacement = target.hasAttribute('data-tooltip-placement') ||
|
|
52
|
+
target.hasAttribute('data-latex-placement');
|
|
53
|
+
|
|
54
|
+
if (!isExplicitPlacement) {
|
|
55
|
+
// Calculate available space in each direction
|
|
56
|
+
const spaceAbove = rect.top - margin;
|
|
57
|
+
const spaceBelow = window.innerHeight - rect.bottom - margin;
|
|
58
|
+
const spaceLeft = rect.left - margin;
|
|
59
|
+
const spaceRight = window.innerWidth - rect.right - margin;
|
|
60
|
+
|
|
61
|
+
// Auto-select placement based on available space
|
|
62
|
+
if (placement === 'top' && tooltipRect.height > spaceAbove && spaceBelow > spaceAbove) {
|
|
63
|
+
placement = 'bottom';
|
|
64
|
+
} else if (placement === 'bottom' && tooltipRect.height > spaceBelow && spaceAbove > spaceBelow) {
|
|
65
|
+
placement = 'top';
|
|
66
|
+
} else if (placement === 'left' && tooltipRect.width > spaceLeft && spaceRight > spaceLeft) {
|
|
67
|
+
placement = 'right';
|
|
68
|
+
} else if (placement === 'right' && tooltipRect.width > spaceRight && spaceLeft > spaceRight) {
|
|
69
|
+
placement = 'left';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Calculate position based on final placement
|
|
74
|
+
let left, top;
|
|
75
|
+
|
|
76
|
+
if (placement === 'top') {
|
|
77
|
+
left = rect.left + (rect.width / 2) - (tooltipRect.width / 2);
|
|
78
|
+
top = rect.top - tooltipRect.height - gap;
|
|
79
|
+
} else if (placement === 'bottom') {
|
|
80
|
+
left = rect.left + (rect.width / 2) - (tooltipRect.width / 2);
|
|
81
|
+
top = rect.bottom + gap;
|
|
82
|
+
} else if (placement === 'left') {
|
|
83
|
+
left = rect.left - tooltipRect.width - gap;
|
|
84
|
+
top = rect.top + (rect.height / 2) - (tooltipRect.height / 2);
|
|
85
|
+
} else if (placement === 'right') {
|
|
86
|
+
left = rect.right + gap;
|
|
87
|
+
top = rect.top + (rect.height / 2) - (tooltipRect.height / 2);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Clamp to viewport
|
|
91
|
+
const clampedLeft = Math.max(margin, Math.min(left, window.innerWidth - tooltipRect.width - margin));
|
|
92
|
+
const clampedTop = Math.max(margin, Math.min(top, window.innerHeight - tooltipRect.height - margin));
|
|
93
|
+
|
|
94
|
+
tooltip.style.left = `${clampedLeft}px`;
|
|
95
|
+
tooltip.style.top = `${clampedTop}px`;
|
|
96
|
+
|
|
97
|
+
// Calculate notch/arrow offset to point toward the target element center
|
|
98
|
+
const targetCenterX = rect.left + rect.width / 2;
|
|
99
|
+
const targetCenterY = rect.top + rect.height / 2;
|
|
100
|
+
let notchOffset = 0;
|
|
101
|
+
|
|
102
|
+
if (placement === 'top' || placement === 'bottom') {
|
|
103
|
+
// Horizontal offset for notch
|
|
104
|
+
const tooltipCenterX = clampedLeft + tooltipRect.width / 2;
|
|
105
|
+
notchOffset = targetCenterX - tooltipCenterX;
|
|
106
|
+
// Clamp notch offset to stay within tooltip bounds
|
|
107
|
+
const maxOffset = (tooltipRect.width / 2) - 16; // 16px from edge minimum
|
|
108
|
+
notchOffset = Math.max(-maxOffset, Math.min(notchOffset, maxOffset));
|
|
109
|
+
} else {
|
|
110
|
+
// Vertical offset for notch
|
|
111
|
+
const tooltipCenterY = clampedTop + tooltipRect.height / 2;
|
|
112
|
+
notchOffset = targetCenterY - tooltipCenterY;
|
|
113
|
+
// Clamp notch offset
|
|
114
|
+
const maxOffset = (tooltipRect.height / 2) - 16;
|
|
115
|
+
notchOffset = Math.max(-maxOffset, Math.min(notchOffset, maxOffset));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Set placement and notch offset attributes for CSS styling
|
|
119
|
+
const setAttributes = () => {
|
|
120
|
+
tooltip.setAttribute('data-placement', placement);
|
|
121
|
+
tooltip.style.setProperty('--notch-offset', `${notchOffset}px`);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const guard = window.stateGuard;
|
|
125
|
+
if (guard?.executeWithBypass) {
|
|
126
|
+
guard.executeWithBypass('latex-tooltip:set-placement', setAttributes);
|
|
127
|
+
} else {
|
|
128
|
+
setAttributes();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Show tooltip for a target element.
|
|
134
|
+
* @param {HTMLElement} target - Element with tooltip data
|
|
135
|
+
*/
|
|
136
|
+
function showTooltip(target) {
|
|
137
|
+
if (!target || !target.getAttribute) return;
|
|
138
|
+
|
|
139
|
+
const hasLatex = target.classList && target.classList.contains('has-latex-tooltip');
|
|
140
|
+
const hasPlain = target.classList && target.classList.contains('twm-has-tooltip');
|
|
141
|
+
|
|
142
|
+
if (!hasLatex && !hasPlain) return;
|
|
143
|
+
|
|
144
|
+
currentTarget = target;
|
|
145
|
+
const tooltip = createTooltipElement();
|
|
146
|
+
|
|
147
|
+
if (hasLatex) {
|
|
148
|
+
// Render LaTeX using KaTeX
|
|
149
|
+
const latex = target.getAttribute('data-latex-tooltip');
|
|
150
|
+
if (!latex) return;
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
if (typeof katex !== 'undefined') {
|
|
154
|
+
katex.render(latex, tooltip, {
|
|
155
|
+
displayMode: true,
|
|
156
|
+
throwOnError: false,
|
|
157
|
+
trust: false
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
tooltip.textContent = latex;
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
window.logger?.error('nodes', 'KaTeX rendering error:', err);
|
|
164
|
+
tooltip.textContent = latex;
|
|
165
|
+
}
|
|
166
|
+
} else if (hasPlain) {
|
|
167
|
+
// Plain text tooltip with line breaks preserved
|
|
168
|
+
const text = target.getAttribute('data-tooltip');
|
|
169
|
+
if (!text) return;
|
|
170
|
+
|
|
171
|
+
// Replace with actual line breaks, preserve formatting
|
|
172
|
+
const formattedText = text.replace(/ /g, '\n');
|
|
173
|
+
const lines = formattedText.split('\n');
|
|
174
|
+
|
|
175
|
+
tooltip.innerHTML = '';
|
|
176
|
+
lines.forEach((line, index) => {
|
|
177
|
+
if (index === 0) {
|
|
178
|
+
// First line is bold header
|
|
179
|
+
const header = document.createElement('strong');
|
|
180
|
+
header.textContent = line;
|
|
181
|
+
tooltip.appendChild(header);
|
|
182
|
+
} else {
|
|
183
|
+
// Add line break and text
|
|
184
|
+
tooltip.appendChild(document.createElement('br'));
|
|
185
|
+
const textNode = document.createTextNode(line);
|
|
186
|
+
tooltip.appendChild(textNode);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Optional per-target width override (for long plain-text content like
|
|
192
|
+
// widget documentation). Set data-tooltip-max-width on the target with a
|
|
193
|
+
// CSS length (e.g. "420px"). Reset between shows so sticky values don't
|
|
194
|
+
// leak across unrelated targets.
|
|
195
|
+
const widthOverride = target.getAttribute('data-tooltip-max-width');
|
|
196
|
+
tooltip.style.maxWidth = widthOverride || '';
|
|
197
|
+
|
|
198
|
+
// Position and show
|
|
199
|
+
requestAnimationFrame(() => {
|
|
200
|
+
positionTooltip(target, tooltip);
|
|
201
|
+
tooltip.classList.add('visible');
|
|
202
|
+
|
|
203
|
+
// Start mouse position check for auto-hide
|
|
204
|
+
startMouseCheck();
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Hide the tooltip and clean up.
|
|
210
|
+
*/
|
|
211
|
+
function hideTooltip() {
|
|
212
|
+
if (tooltipElement) {
|
|
213
|
+
tooltipElement.classList.remove('visible');
|
|
214
|
+
}
|
|
215
|
+
currentTarget = null;
|
|
216
|
+
|
|
217
|
+
// Clear mouse check interval
|
|
218
|
+
if (mouseCheckInterval) {
|
|
219
|
+
clearInterval(mouseCheckInterval);
|
|
220
|
+
mouseCheckInterval = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Start the mouse position check interval.
|
|
226
|
+
* Hides tooltip if mouse is no longer over the target element.
|
|
227
|
+
*/
|
|
228
|
+
function startMouseCheck() {
|
|
229
|
+
// Clear any existing interval
|
|
230
|
+
if (mouseCheckInterval) {
|
|
231
|
+
clearInterval(mouseCheckInterval);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Check every 3 seconds if mouse is still on the target element
|
|
235
|
+
mouseCheckInterval = setInterval(() => {
|
|
236
|
+
if (!currentTarget) {
|
|
237
|
+
clearInterval(mouseCheckInterval);
|
|
238
|
+
mouseCheckInterval = null;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Get element under the last known mouse position
|
|
243
|
+
const elementUnderMouse = document.elementFromPoint(lastMousePosition.x, lastMousePosition.y);
|
|
244
|
+
|
|
245
|
+
// Check if the element under mouse is the target or a child of it
|
|
246
|
+
if (elementUnderMouse) {
|
|
247
|
+
const isStillOnTarget = elementUnderMouse === currentTarget ||
|
|
248
|
+
currentTarget.contains(elementUnderMouse) ||
|
|
249
|
+
elementUnderMouse.closest('.twm-has-tooltip, .has-latex-tooltip') === currentTarget;
|
|
250
|
+
|
|
251
|
+
if (!isStillOnTarget) {
|
|
252
|
+
hideTooltip();
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
// Mouse is outside the document
|
|
256
|
+
hideTooltip();
|
|
257
|
+
}
|
|
258
|
+
}, 3000);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Attach global event listeners for tooltips.
|
|
263
|
+
*/
|
|
264
|
+
function attachListeners() {
|
|
265
|
+
if (listenersAttached) return;
|
|
266
|
+
listenersAttached = true;
|
|
267
|
+
|
|
268
|
+
// Remove all title attributes from elements with custom tooltips
|
|
269
|
+
document.querySelectorAll('.twm-has-tooltip, .has-latex-tooltip').forEach(el => {
|
|
270
|
+
el.removeAttribute('title');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Track mouse position for the 3-second auto-hide check
|
|
274
|
+
document.addEventListener('mousemove', (event) => {
|
|
275
|
+
lastMousePosition.x = event.clientX;
|
|
276
|
+
lastMousePosition.y = event.clientY;
|
|
277
|
+
}, { passive: true });
|
|
278
|
+
|
|
279
|
+
document.addEventListener('mouseenter', (event) => {
|
|
280
|
+
const target = event.target;
|
|
281
|
+
if (!(target instanceof Element)) return;
|
|
282
|
+
const tooltipTarget = target.closest('.has-latex-tooltip, .twm-has-tooltip');
|
|
283
|
+
if (!tooltipTarget) return;
|
|
284
|
+
// Skip template nodes in fixed-200 panel - handled elsewhere
|
|
285
|
+
if (tooltipTarget.closest('.panel.twm-fixed-200 .node.template')) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
showTooltip(tooltipTarget);
|
|
289
|
+
}, true);
|
|
290
|
+
|
|
291
|
+
document.addEventListener('mouseleave', (event) => {
|
|
292
|
+
const target = event.target;
|
|
293
|
+
if (!(target instanceof Element)) return;
|
|
294
|
+
const tooltipTarget = target.closest('.has-latex-tooltip, .twm-has-tooltip');
|
|
295
|
+
if (!tooltipTarget) return;
|
|
296
|
+
// Skip template nodes in fixed-200 panel
|
|
297
|
+
if (tooltipTarget.closest('.panel.twm-fixed-200 .node.template')) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
hideTooltip();
|
|
301
|
+
}, true);
|
|
302
|
+
|
|
303
|
+
// Hide on scroll
|
|
304
|
+
document.addEventListener('scroll', hideTooltip, true);
|
|
305
|
+
|
|
306
|
+
// ESC key hides all tooltips application-wide (does not stop propagation)
|
|
307
|
+
document.addEventListener('keydown', (event) => {
|
|
308
|
+
if (event.key === 'Escape') {
|
|
309
|
+
hideTooltip();
|
|
310
|
+
// Note: We intentionally do NOT call event.stopPropagation()
|
|
311
|
+
// so other listeners can also respond to ESC
|
|
312
|
+
}
|
|
313
|
+
}, true);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Initialize the tooltip system.
|
|
318
|
+
*/
|
|
319
|
+
function initializeTooltips() {
|
|
320
|
+
// Wait for KaTeX to load (optional, for latex tooltips)
|
|
321
|
+
const initInterval = setInterval(() => {
|
|
322
|
+
if (typeof katex !== 'undefined') {
|
|
323
|
+
clearInterval(initInterval);
|
|
324
|
+
if (!listenersAttached) {
|
|
325
|
+
attachListeners();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}, 100);
|
|
329
|
+
|
|
330
|
+
// Timeout after 2 seconds - attach listeners anyway for plain tooltips
|
|
331
|
+
setTimeout(() => {
|
|
332
|
+
clearInterval(initInterval);
|
|
333
|
+
if (!listenersAttached) {
|
|
334
|
+
attachListeners();
|
|
335
|
+
}
|
|
336
|
+
}, 2000);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Set a tooltip on an element programmatically.
|
|
341
|
+
* @param {HTMLElement} element - Target element
|
|
342
|
+
* @param {string} text - Tooltip text
|
|
343
|
+
* @param {boolean} [isLatex=false] - Whether to render as LaTeX
|
|
344
|
+
*/
|
|
345
|
+
function setTooltip(element, text, isLatex = false) {
|
|
346
|
+
if (!element || !text) return;
|
|
347
|
+
|
|
348
|
+
// Remove any existing title attribute
|
|
349
|
+
element.removeAttribute('title');
|
|
350
|
+
|
|
351
|
+
if (isLatex) {
|
|
352
|
+
element.classList.add('has-latex-tooltip');
|
|
353
|
+
element.classList.remove('twm-has-tooltip');
|
|
354
|
+
element.setAttribute('data-latex-tooltip', text);
|
|
355
|
+
} else {
|
|
356
|
+
element.classList.add('twm-has-tooltip');
|
|
357
|
+
element.classList.remove('has-latex-tooltip');
|
|
358
|
+
element.setAttribute('data-tooltip', text);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Refresh tooltips for dynamically added content.
|
|
364
|
+
* Removes title attributes that might cause native tooltips.
|
|
365
|
+
* @param {HTMLElement} [container=document] - Container to search within
|
|
366
|
+
*/
|
|
367
|
+
function refreshTooltips(container = document) {
|
|
368
|
+
container.querySelectorAll('.twm-has-tooltip, .has-latex-tooltip').forEach(el => {
|
|
369
|
+
el.removeAttribute('title');
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ES module exports — the real interface.
|
|
374
|
+
export const TooltipService = {
|
|
375
|
+
show: showTooltip,
|
|
376
|
+
hide: hideTooltip,
|
|
377
|
+
set: setTooltip,
|
|
378
|
+
refresh: refreshTooltips,
|
|
379
|
+
init: initializeTooltips
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
export default TooltipService;
|
|
383
|
+
|
|
384
|
+
// Everything below is a side effect of IMPORTING this file, and it is guarded.
|
|
385
|
+
//
|
|
386
|
+
// Unguarded, it touched `document` and `window` at module scope, which made
|
|
387
|
+
// @flexdesk/widgets throw `ReferenceError: window is not defined` the moment anything
|
|
388
|
+
// imported it outside a browser. The DOM check is not defensive programming; it
|
|
389
|
+
// is the difference between a module and a script.
|
|
390
|
+
//
|
|
391
|
+
// `window.LatexTooltip` stays, because unlike `window.InlineRenamer` it is real:
|
|
392
|
+
// roughly 25 call sites read it, all as `window.LatexTooltip?.set(...)`, and one
|
|
393
|
+
// of them is INSIDE the library (window_chrome_controller.js). That last one is a
|
|
394
|
+
// hidden dependency on a global and should import TooltipService instead — a small
|
|
395
|
+
// piece of P6 cleanup, noted rather than smuggled in here.
|
|
396
|
+
if (typeof document !== 'undefined') {
|
|
397
|
+
if (document.readyState === 'loading') {
|
|
398
|
+
document.addEventListener('DOMContentLoaded', initializeTooltips);
|
|
399
|
+
} else {
|
|
400
|
+
initializeTooltips();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (typeof window !== 'undefined') {
|
|
405
|
+
window.LatexTooltip = TooltipService;
|
|
406
|
+
}
|