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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/THIRD_PARTY_LICENSES.md +53 -0
  4. package/css/base.css +3439 -0
  5. package/css/flexdesk.css +5797 -0
  6. package/css/overrides.css +2349 -0
  7. package/css/reset.css +46 -0
  8. package/css/tokens.css +137 -0
  9. package/dist/charts.js +780 -0
  10. package/dist/charts.js.map +7 -0
  11. package/dist/chunk-3PHCPZHT.js +222 -0
  12. package/dist/chunk-3PHCPZHT.js.map +7 -0
  13. package/dist/chunk-CT4YXXLP.js +1770 -0
  14. package/dist/chunk-CT4YXXLP.js.map +7 -0
  15. package/dist/chunk-DRYCDMEG.js +2217 -0
  16. package/dist/chunk-DRYCDMEG.js.map +7 -0
  17. package/dist/chunk-DVU44T77.js +855 -0
  18. package/dist/chunk-DVU44T77.js.map +7 -0
  19. package/dist/chunk-FL5KFNQH.js +640 -0
  20. package/dist/chunk-FL5KFNQH.js.map +7 -0
  21. package/dist/chunk-FOOS3T5L.js +117 -0
  22. package/dist/chunk-FOOS3T5L.js.map +7 -0
  23. package/dist/chunk-JYWURG5T.js +32 -0
  24. package/dist/chunk-JYWURG5T.js.map +7 -0
  25. package/dist/chunk-TLZUUFOE.js +276 -0
  26. package/dist/chunk-TLZUUFOE.js.map +7 -0
  27. package/dist/chunk-UCJ2WD4D.js +625 -0
  28. package/dist/chunk-UCJ2WD4D.js.map +7 -0
  29. package/dist/chunk-WVFGV5FT.js +205 -0
  30. package/dist/chunk-WVFGV5FT.js.map +7 -0
  31. package/dist/core.js +1098 -0
  32. package/dist/core.js.map +7 -0
  33. package/dist/editor.js +1661 -0
  34. package/dist/editor.js.map +7 -0
  35. package/dist/flexdesk.css +5797 -0
  36. package/dist/host.js +132 -0
  37. package/dist/host.js.map +7 -0
  38. package/dist/reset.css +46 -0
  39. package/dist/tile_registry-6WZPTUZV.js +28 -0
  40. package/dist/tile_registry-6WZPTUZV.js.map +7 -0
  41. package/dist/tiles.js +2068 -0
  42. package/dist/tiles.js.map +7 -0
  43. package/dist/tokens.css +137 -0
  44. package/dist/widgets.js +5438 -0
  45. package/dist/widgets.js.map +7 -0
  46. package/dist/wm.js +4830 -0
  47. package/dist/wm.js.map +7 -0
  48. package/package.json +80 -0
  49. package/src/charts/chart_types.js +699 -0
  50. package/src/charts/data_series_plot_window.js +761 -0
  51. package/src/charts/downsample.js +119 -0
  52. package/src/charts/plot_config.js +176 -0
  53. package/src/charts/plot_popout_window.js +1560 -0
  54. package/src/charts/plotly_wrapper.js +432 -0
  55. package/src/core/event_bus.js +230 -0
  56. package/src/core/logging.js +300 -0
  57. package/src/core/settings.js +813 -0
  58. package/src/core/state_guard.js +382 -0
  59. package/src/core/state_guard_config.js +309 -0
  60. package/src/core/state_machine.js +258 -0
  61. package/src/editor/editor_pane.js +264 -0
  62. package/src/editor/monaco_editor_factory.js +449 -0
  63. package/src/editor/monaco_loader.js +74 -0
  64. package/src/editor/notebook_search.js +460 -0
  65. package/src/editor/notebook_tab_bar.js +456 -0
  66. package/src/editor/notebook_undo_manager.js +108 -0
  67. package/src/editor/split_pane_container.js +197 -0
  68. package/src/help/help_modal.js +409 -0
  69. package/src/help/help_registry.js +65 -0
  70. package/src/host/host.js +69 -0
  71. package/src/host/pywebview_host.js +133 -0
  72. package/src/tiles/config_schema.js +256 -0
  73. package/src/tiles/layout_persistence.js +662 -0
  74. package/src/tiles/tile_base.js +785 -0
  75. package/src/tiles/tile_grid.js +905 -0
  76. package/src/tiles/tile_registry.js +136 -0
  77. package/src/tiling/command_palette.js +328 -0
  78. package/src/tiling/content_registry.js +110 -0
  79. package/src/tiling/desktops.js +133 -0
  80. package/src/tiling/entity_sources.js +179 -0
  81. package/src/tiling/keymap.js +156 -0
  82. package/src/tiling/kind_taxonomy.js +172 -0
  83. package/src/tiling/landing_table.js +717 -0
  84. package/src/tiling/loading_overlay.js +33 -0
  85. package/src/tiling/nav_panel.js +142 -0
  86. package/src/tiling/page_factory.js +215 -0
  87. package/src/tiling/panel_keys.js +145 -0
  88. package/src/tiling/shell.js +437 -0
  89. package/src/tiling/tile_breadcrumb.js +222 -0
  90. package/src/tiling/tile_renderer.js +459 -0
  91. package/src/tiling/tile_tab_menu.js +340 -0
  92. package/src/tiling/tile_tree.js +880 -0
  93. package/src/tiling/wm.js +1352 -0
  94. package/src/ui/base/component_base.js +30 -0
  95. package/src/ui/base/controller_base.js +48 -0
  96. package/src/ui/base/page_base.js +102 -0
  97. package/src/ui/components/about_dialog.js +224 -0
  98. package/src/ui/components/action_dropdown.js +389 -0
  99. package/src/ui/components/attribute_list_editor.js +652 -0
  100. package/src/ui/components/autocomplete_field.js +732 -0
  101. package/src/ui/components/computing_status_window.js +81 -0
  102. package/src/ui/components/confirm_dialog.js +307 -0
  103. package/src/ui/components/context_menu.js +87 -0
  104. package/src/ui/components/data_table.js +2228 -0
  105. package/src/ui/components/detail_header.js +177 -0
  106. package/src/ui/components/drag_reorder.js +0 -0
  107. package/src/ui/components/gallery_picker.js +147 -0
  108. package/src/ui/components/inline_renamer.js +129 -0
  109. package/src/ui/components/managed_window.js +804 -0
  110. package/src/ui/components/modal.js +512 -0
  111. package/src/ui/components/notification_history.js +264 -0
  112. package/src/ui/components/slide_out_panel.js +185 -0
  113. package/src/ui/components/slider_field.js +122 -0
  114. package/src/ui/components/sortable_list.js +309 -0
  115. package/src/ui/components/table_state_store.js +81 -0
  116. package/src/ui/components/toast.js +59 -0
  117. package/src/ui/components/tree_view.js +460 -0
  118. package/src/ui/components/window_taskbar.js +143 -0
  119. package/src/ui/controllers/panel_state_machine.js +634 -0
  120. package/src/ui/controllers/window_chrome_controller.js +659 -0
  121. package/src/ui/notification_center.js +576 -0
  122. package/src/ui/utils/overlay_scrollbar.js +492 -0
  123. package/src/ui/utils/raf_resize_observer.js +31 -0
  124. 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
+ }