@sybilion/uilib 1.3.18 → 1.3.20

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.
@@ -1,7 +1,7 @@
1
1
  import styleInject from 'style-inject';
2
2
 
3
- var css_248z = ".Chart_chartContainer__--q1l{aspect-ratio:16/9;display:flex;font-size:.75rem;justify-content:center;line-height:1rem;max-width:100%;touch-action:none;width:100%}.Chart_chartContainer__--q1l .recharts-cartesian-axis-tick text{fill:var(--muted-foreground)}.Chart_chartContainer__--q1l .recharts-cartesian-grid line[stroke=\"#ccc\"]{stroke:var(--border)}.dark .Chart_chartContainer__--q1l .recharts-cartesian-grid line[stroke=\"#ccc\"]{stroke:var(--sb-slate-900)}.Chart_chartContainer__--q1l .recharts-curve.recharts-tooltip-cursor,.Chart_chartContainer__--q1l .recharts-polar-grid [stroke=\"#ccc\"]{stroke:var(--border)}.Chart_chartContainer__--q1l .recharts-radial-bar-background-sector,.Chart_chartContainer__--q1l .recharts-rectangle.recharts-tooltip-cursor{fill:var(--muted)}.Chart_chartContainer__--q1l .recharts-reference-line [stroke=\"#ccc\"]{stroke:var(--border)}.Chart_chartContainer__--q1l .recharts-dot[stroke=\"#fff\"]{stroke:transparent}.Chart_chartContainer__--q1l .recharts-layer,.Chart_chartContainer__--q1l .recharts-sector{outline:none}.Chart_chartContainer__--q1l .recharts-sector[stroke=\"#fff\"]{stroke:transparent}.Chart_chartContainer__--q1l .recharts-surface{outline:none}.Chart_chartContainer__--q1l .recharts-wrapper{position:relative}.Chart_chartContainer__--q1l .recharts-surface{position:relative;z-index:1}.Chart_chartContainer__--q1l .recharts-tooltip-wrapper{z-index:3!important}.Chart_chartContainer__--q1l .recharts-active-dot{z-index:3}.Chart_chartGrid__t52WF{stroke-width:.6}.Chart_tooltipContainer__6tc0q{align-items:start;box-sizing:border-box;display:grid;max-width:100%;min-width:0;overflow-wrap:break-word;width:-moz-max-content;width:max-content;word-break:break-word;grid-gap:.375rem;background-color:var(--background);border:1px solid var(--border)/.5;border-radius:.5rem;box-shadow:0 10px 10px -5px rgba(0,0,0,.3),0 0 2px 0 rgba(0,0,0,.5);font-size:.75rem;gap:.375rem;line-height:1rem;opacity:0;padding:.375rem .625rem;transition:opacity .5s ease-out}.Chart_chartContainer__--q1l:hover .Chart_tooltipContainer__6tc0q{opacity:1;transition-duration:.5s}.Chart_tooltipItem__j8I9T{align-items:stretch;display:flex;flex-wrap:wrap;gap:.5rem;width:100%}.Chart_tooltipItem__j8I9T>svg{color:var(--muted-foreground);height:.625rem;width:.625rem}.Chart_tooltipIndicator__Z-JWp{background-color:var(--color-bg);border-color:var(--color-border);border-radius:2px;border-width:1px;flex-shrink:0}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-dot__MWcmW{height:.625rem;width:.625rem}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-line__MO3ul{width:.25rem}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-dashed__2LqIN{background-color:transparent;border-style:dashed;border-width:1.5px;width:0}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-dashed__2LqIN.Chart_nested__7EWWk{margin-bottom:.125rem;margin-top:.125rem}.Chart_tooltipContent__M3R-W{display:flex;flex:1 1 0%;justify-content:space-between;line-height:1}.Chart_tooltipLabel__zMpjZ{display:grid;grid-gap:.375rem;gap:.375rem}.Chart_tooltipValue__vTQxU{color:var(--foreground);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:500;margin-left:var(--p-3)}.Chart_legendContainer__u1J3U{align-items:center;display:flex;gap:1rem;justify-content:center}.Chart_legendItem__0CSyC{align-items:center;display:flex;gap:.375rem}.Chart_legendItem__0CSyC>svg{color:var(--muted-foreground);height:.75rem;width:.75rem}.Chart_legendIndicator__erzzP{border-radius:2px;flex-shrink:0;height:.5rem;width:.5rem}.chart-line-blinking path{animation:chart-line-blink 1s ease-in-out infinite;animation-direction:alternate}@keyframes Chart_chart-line-blink__4EI-g{0%{opacity:.5}to{opacity:1}}";
4
- var S = {"chartContainer":"Chart_chartContainer__--q1l","chartGrid":"Chart_chartGrid__t52WF","tooltipContainer":"Chart_tooltipContainer__6tc0q","tooltipItem":"Chart_tooltipItem__j8I9T","tooltipIndicator":"Chart_tooltipIndicator__Z-JWp","indicator-dot":"Chart_indicator-dot__MWcmW","indicator-line":"Chart_indicator-line__MO3ul","indicator-dashed":"Chart_indicator-dashed__2LqIN","nested":"Chart_nested__7EWWk","tooltipContent":"Chart_tooltipContent__M3R-W","tooltipLabel":"Chart_tooltipLabel__zMpjZ","tooltipValue":"Chart_tooltipValue__vTQxU","legendContainer":"Chart_legendContainer__u1J3U","legendItem":"Chart_legendItem__0CSyC","legendIndicator":"Chart_legendIndicator__erzzP","chart-line-blink":"Chart_chart-line-blink__4EI-g"};
3
+ var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.Chart_chartContainer__--q1l{aspect-ratio:16/9;display:flex;font-size:.75rem;justify-content:center;line-height:1rem;max-width:100%;touch-action:none;width:100%}.Chart_chartContainer__--q1l .recharts-cartesian-axis-tick text{fill:var(--muted-foreground)}.Chart_chartContainer__--q1l .recharts-cartesian-grid line[stroke=\"#ccc\"]{stroke:var(--border)}.dark .Chart_chartContainer__--q1l .recharts-cartesian-grid line[stroke=\"#ccc\"]{stroke:var(--sb-slate-900)}.Chart_chartContainer__--q1l .recharts-curve.recharts-tooltip-cursor,.Chart_chartContainer__--q1l .recharts-polar-grid [stroke=\"#ccc\"]{stroke:var(--border)}.Chart_chartContainer__--q1l .recharts-radial-bar-background-sector,.Chart_chartContainer__--q1l .recharts-rectangle.recharts-tooltip-cursor{fill:var(--muted)}.Chart_chartContainer__--q1l .recharts-reference-line [stroke=\"#ccc\"]{stroke:var(--border)}.Chart_chartContainer__--q1l .recharts-dot[stroke=\"#fff\"]{stroke:transparent}.Chart_chartContainer__--q1l .recharts-layer,.Chart_chartContainer__--q1l .recharts-sector{outline:none}.Chart_chartContainer__--q1l .recharts-sector[stroke=\"#fff\"]{stroke:transparent}.Chart_chartContainer__--q1l .recharts-surface{outline:none}.Chart_chartContainer__--q1l .recharts-wrapper{position:relative}.Chart_chartContainer__--q1l .recharts-surface{position:relative;z-index:1}.Chart_chartContainer__--q1l .recharts-tooltip-wrapper{box-sizing:border-box;max-width:90%;z-index:3!important}.Chart_chartContainer__--q1l .recharts-active-dot{z-index:3}.Chart_chartGrid__t52WF{stroke-width:.6}.Chart_tooltipContainer__6tc0q{align-items:start;box-sizing:border-box;display:grid;max-width:min(500px,90vw);min-width:0;overflow-wrap:break-word;width:100%;word-break:break-word;grid-gap:.375rem;backdrop-filter:blur(10px);background-color:color-mix(in srgb,var(--background) 50%,transparent);border:1px solid var(--border)/.5;border-radius:.5rem;box-shadow:0 10px 10px -5px rgba(0,0,0,.2),0 0 1px 0 var(--muted-foreground);font-size:.75rem;gap:.375rem;line-height:1rem;opacity:0;padding:.375rem .625rem;transition:opacity .5s ease-out}.dark .Chart_tooltipContainer__6tc0q{box-shadow:0 0 1px 0 var(--sb-slate-700),0 10px 10px -5px rgba(0,0,0,.7)}.Chart_chartContainer__--q1l:hover .Chart_tooltipContainer__6tc0q{opacity:1;transition-duration:.5s}.Chart_tooltipItem__j8I9T{align-items:stretch;display:flex;flex-wrap:wrap;gap:.5rem;width:100%}.Chart_tooltipItem__j8I9T>svg{color:var(--muted-foreground);height:.625rem;width:.625rem}.Chart_tooltipIndicator__Z-JWp{background-color:var(--color-bg);border-color:var(--color-border);border-radius:2px;border-width:1px;flex-shrink:0}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-dot__MWcmW{height:.625rem;width:.625rem}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-line__MO3ul{width:.25rem}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-dashed__2LqIN{background-color:transparent;border-style:dashed;border-width:1.5px;width:0}.Chart_tooltipIndicator__Z-JWp.Chart_indicator-dashed__2LqIN.Chart_nested__7EWWk{margin-bottom:.125rem;margin-top:.125rem}.Chart_tooltipContent__M3R-W{display:flex;flex:1 1 0%;justify-content:space-between;line-height:1;min-width:0}.Chart_tooltipLabel__zMpjZ{display:grid;min-width:0;grid-gap:.375rem;gap:.375rem}.Chart_tooltipLabelText__45osJ{display:-webkit-box;-webkit-line-clamp:3;max-width:100%;min-width:0;overflow:hidden;word-break:break-word;-webkit-box-orient:vertical}.Chart_tooltipValue__vTQxU{color:var(--foreground);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:500;margin-left:var(--p-3);white-space:nowrap}.Chart_legendContainer__u1J3U{align-items:center;display:flex;gap:1rem;justify-content:center}.Chart_legendItem__0CSyC{align-items:center;display:flex;gap:.375rem}.Chart_legendItem__0CSyC>svg{color:var(--muted-foreground);height:.75rem;width:.75rem}.Chart_legendIndicator__erzzP{border-radius:2px;flex-shrink:0;height:.5rem;width:.5rem}.chart-line-blinking path{animation:chart-line-blink 1s ease-in-out infinite;animation-direction:alternate}@keyframes Chart_chart-line-blink__4EI-g{0%{opacity:.5}to{opacity:1}}";
4
+ var S = {"chartContainer":"Chart_chartContainer__--q1l","chartGrid":"Chart_chartGrid__t52WF","tooltipContainer":"Chart_tooltipContainer__6tc0q","tooltipItem":"Chart_tooltipItem__j8I9T","tooltipIndicator":"Chart_tooltipIndicator__Z-JWp","indicator-dot":"Chart_indicator-dot__MWcmW","indicator-line":"Chart_indicator-line__MO3ul","indicator-dashed":"Chart_indicator-dashed__2LqIN","nested":"Chart_nested__7EWWk","tooltipContent":"Chart_tooltipContent__M3R-W","tooltipLabel":"Chart_tooltipLabel__zMpjZ","tooltipLabelText":"Chart_tooltipLabelText__45osJ","tooltipValue":"Chart_tooltipValue__vTQxU","legendContainer":"Chart_legendContainer__u1J3U","legendItem":"Chart_legendItem__0CSyC","legendIndicator":"Chart_legendIndicator__erzzP","chart-line-blink":"Chart_chart-line-blink__4EI-g"};
5
5
  styleInject(css_248z);
6
6
 
7
7
  export { S as default };
@@ -16,7 +16,7 @@ function ChartTooltipItem({ item, index, config, indicator, hideIndicator, nestL
16
16
  return (jsx("div", { className: cn(S.tooltipItem, indicator === 'dot' && 'items-center'), children: formatter && item?.value !== undefined && item.name ? (formatter(item.value, item.name, item, index, item.payload)) : (jsxs(Fragment, { children: [itemConfig?.icon ? (jsx(itemConfig.icon, {})) : (!hideIndicator && (jsx("div", { className: cn(S.tooltipIndicator, S[`indicator-${indicator}`], nestLabel && S.nested), style: {
17
17
  '--color-bg': indicatorColor,
18
18
  '--color-border': indicatorColor,
19
- } }))), jsxs("div", { className: cn(S.tooltipContent, nestLabel ? 'items-end' : 'items-center'), children: [jsxs("div", { className: S.tooltipLabel, children: [nestLabel ? tooltipLabel : null, jsx("span", { className: "text-muted-foreground", children: itemConfig?.label || item.name })] }), item.value && (jsx("span", { className: S.tooltipValue, children: Array.isArray(item.value)
19
+ } }))), jsxs("div", { className: cn(S.tooltipContent, nestLabel ? 'items-end' : 'items-center'), children: [jsxs("div", { className: S.tooltipLabel, children: [nestLabel ? tooltipLabel : null, jsx("span", { className: cn('text-muted-foreground', S.tooltipLabelText), children: itemConfig?.label || item.name })] }), item.value && (jsx("span", { className: S.tooltipValue, children: Array.isArray(item.value)
20
20
  ? `${item.value[0].toLocaleString(undefined, { maximumFractionDigits: 20 })} – ${item.value[1].toLocaleString(undefined, { maximumFractionDigits: 20 })}`
21
21
  : item.value.toLocaleString(undefined, {
22
22
  maximumFractionDigits: 20,
@@ -111,7 +111,7 @@ function ChartAreaInteractive({ className, chartContainerClassName, legendClassN
111
111
  return (jsx(BaseChartWrapper, { ...baseChartProps, chartClassName: cn(S.chartContainer, chartContainerClassName) }));
112
112
  }
113
113
  };
114
- return (jsxs(InteractionOverlay, { className: cn(className, loading && S.loading), children: [(!disableTimeRangeSelector || headerActions) && (jsxs("div", { className: S.chartHeaderContainer, children: [!disableTimeRangeSelector && (jsx(TimeRangeControls, { timeRange: timeRange, onTimeRangeChange: onTimeRangeChange, loading: loading })), headerActions] })), jsx(TimeRangeBrushHost, { chartData: bridgedChartData, onTimeRangeChange: onTimeRangeChange, enabled: brushEnabled, layoutKey: chartRenderId ?? null, children: renderChart() })] }));
114
+ return (jsxs(InteractionOverlay, { className: cn(className, loading && S.loading), disabled: loading, children: [(!disableTimeRangeSelector || headerActions) && (jsxs("div", { className: S.chartHeaderContainer, children: [!disableTimeRangeSelector && (jsx(TimeRangeControls, { timeRange: timeRange, onTimeRangeChange: onTimeRangeChange, loading: loading })), headerActions] })), jsx(TimeRangeBrushHost, { chartData: bridgedChartData, onTimeRangeChange: onTimeRangeChange, enabled: brushEnabled, layoutKey: chartRenderId ?? null, children: renderChart() })] }));
115
115
  }
116
116
 
117
117
  export { ChartAreaInteractive, chartConfig };
@@ -4,7 +4,7 @@ import { MessageRole, GENERATING_DASHBOARD_SYSTEM_TEXT } from '../Chat.types.js'
4
4
  import { isGraphIntakeAssistantStepComplete, matchUserTextToQuickReply, parseScriptLine, textHasQuickReplyMarkers, branchKeysUsedFromChatHistory, branchKeysUsedByUserMessages, extractQuickReplyLabelKeyPairsFromText, entryBranchKeyBeforeLastAssistant, isPresetScriptGraph, branchesFromPresetScriptGraph } from '../ChatMessage/presetScript.js';
5
5
  import { buildChatSendMessagePayload, displayTextFromSendPayload } from '../buildChatSendMessagePayload.js';
6
6
  import { usedPresetIdsFromMessages, formatChatTranscript } from '../chat-preset-utils.js';
7
- import { useChatsForScopeId, useChat, useChatOutboundPending, isChatEmpty } from '../../../../contexts/chat-context.js';
7
+ import { useChatsForScopeId, useChat, useChatOutboundPending, useSyncChatPanelBusy, isChatEmpty } from '../../../../contexts/chat-context.js';
8
8
  import useEvent from '../../../../hooks/useEvent.js';
9
9
  import { useIsMobile } from '../../../../hooks/useIsMobile.js';
10
10
  import { useQueryParams } from '../../../../hooks/useQueryParams.js';
@@ -31,6 +31,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
31
31
  const chat = useChat(effectiveScopeId, currentChatId);
32
32
  const isOutboundPending = useChatOutboundPending(effectiveScopeId, currentChatId);
33
33
  const isLoading = isOutboundPending || localUiBusy;
34
+ useSyncChatPanelBusy(isLoading);
34
35
  const { searchParams, addSearchParams, removeSearchParams, mutateSearchParams, } = useQueryParams();
35
36
  const chatOpen = searchParams.has(CHAT_QUERY_PARAM);
36
37
  const [isOpen, setIsOpen] = useState(false);
@@ -1,22 +1,25 @@
1
1
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
2
  import cn from 'classnames';
3
3
  import { useState, useCallback, useEffect } from 'react';
4
+ import { useIsChatLoading } from '../../../contexts/chat-context.js';
4
5
  import { useIsTouchDevice } from '../../../hooks/useIsTouchDevice.js';
5
6
  import S from './InteractionOverlay.styl.js';
6
7
 
7
- function InteractionOverlay({ className, children, message = 'Touch to interact', }) {
8
+ function InteractionOverlay({ className, children, message = 'Touch to interact', disabled = false, }) {
8
9
  const isTouchDevice = useIsTouchDevice();
10
+ const isChatLoading = useIsChatLoading();
11
+ const suppressOverlay = disabled || isChatLoading;
9
12
  const [isVisible, setIsVisible] = useState(true);
10
13
  const handleClick = useCallback(() => {
11
14
  setIsVisible(false);
12
15
  }, []);
13
16
  useEffect(() => {
14
- if (isTouchDevice) {
17
+ if (isTouchDevice && !suppressOverlay) {
15
18
  setIsVisible(true);
16
19
  }
17
- }, [isTouchDevice]);
20
+ }, [isTouchDevice, suppressOverlay]);
18
21
  useEffect(() => {
19
- if (!isTouchDevice)
22
+ if (!isTouchDevice || suppressOverlay)
20
23
  return;
21
24
  const handleScroll = () => {
22
25
  setIsVisible(true);
@@ -25,11 +28,11 @@ function InteractionOverlay({ className, children, message = 'Touch to interact'
25
28
  return () => {
26
29
  document.removeEventListener('scroll', handleScroll, { capture: true });
27
30
  };
28
- }, [isTouchDevice]);
31
+ }, [isTouchDevice, suppressOverlay]);
29
32
  if (!isTouchDevice) {
30
33
  return jsx(Fragment, { children: children });
31
34
  }
32
- return (jsxs("div", { className: cn(S.root, !isVisible && S.hidden, className), children: [children, jsx("button", { type: "button", className: S.overlay, onClick: handleClick, "aria-label": message, children: jsx("span", { className: S.message, children: message }) })] }));
35
+ return (jsxs("div", { className: cn(S.root, (!isVisible || suppressOverlay) && S.hidden, className), children: [children, jsx("button", { type: "button", className: S.overlay, onClick: handleClick, "aria-label": message, children: jsx("span", { className: S.message, children: message }) })] }));
33
36
  }
34
37
 
35
38
  export { InteractionOverlay };
@@ -1,6 +1,6 @@
1
1
  import styleInject from 'style-inject';
2
2
 
3
- var css_248z = ".InteractionOverlay_root__9FV58{position:relative}.InteractionOverlay_overlay__Nwawr{background:transparent;border:none;box-shadow:inset 0 0 80px 40px var(--overlay-background-color);cursor:pointer;inset:0;margin:0;padding:0;position:absolute;touch-action:manipulation;transition:box-shadow .2s ease-out;z-index:4}.InteractionOverlay_hidden__NRlgG .InteractionOverlay_overlay__Nwawr{box-shadow:inset 0 0 0 0 var(--overlay-background-color);pointer-events:none}.InteractionOverlay_message__oCuPR{backdrop-filter:blur(10px);background-color:color-mix(in srgb,var(--overlay-background-color) 80%,transparent);border:1px solid color-mix(in srgb,var(--foreground) 14%,transparent);border-radius:9999px;box-shadow:0 0 24px 10px var(--overlay-background-color),0 0 0 1px color-mix(in srgb,var(--overlay-background-color) 40%,transparent);color:var(--foreground);font-size:.875rem;font-weight:500;left:50%;line-height:1.25;padding:var(--p-3) var(--p-5);pointer-events:none;position:absolute;top:50%;transform:translate(-50%,-50%);transition:.2s ease-out;transition-property:transform,opacity;white-space:nowrap;z-index:1}.InteractionOverlay_hidden__NRlgG .InteractionOverlay_message__oCuPR{opacity:0;transform:translate(-50%,-50%) scale(.3)}:root{--overlay-background-color:var(--page-color)}";
3
+ var css_248z = ".InteractionOverlay_root__9FV58{position:relative}.InteractionOverlay_overlay__Nwawr{background:transparent;border:none;cursor:pointer;inset:0;margin:0;padding:0;position:absolute;touch-action:manipulation;z-index:4}.InteractionOverlay_overlay__Nwawr:before{background-color:color-mix(in srgb,var(--overlay-background-color) 70%,transparent);bottom:calc(var(--p-2)*-1);box-shadow:inset 0 0 80px 40px var(--overlay-background-color);content:\"\";left:calc(var(--p-2)*-1);pointer-events:none;position:absolute;right:calc(var(--p-2)*-1);top:calc(var(--p-2)*-1);transition:.2s ease-out;transition-property:box-shadow,background-color;z-index:-1}.InteractionOverlay_hidden__NRlgG .InteractionOverlay_overlay__Nwawr{pointer-events:none}.InteractionOverlay_hidden__NRlgG .InteractionOverlay_overlay__Nwawr:before{background-color:transparent;box-shadow:inset 0 0 0 0 var(--overlay-background-color)}.InteractionOverlay_message__oCuPR{backdrop-filter:blur(10px);background-color:color-mix(in srgb,var(--overlay-background-color) 80%,transparent);border:1px solid color-mix(in srgb,var(--foreground) 14%,transparent);border-radius:9999px;box-shadow:0 0 24px 10px var(--overlay-background-color),0 0 0 1px color-mix(in srgb,var(--overlay-background-color) 40%,transparent);color:var(--foreground);font-size:.875rem;font-weight:500;left:50%;line-height:1.25;padding:var(--p-3) var(--p-5);pointer-events:none;position:absolute;top:50%;transform:translate(-50%,-50%);transition:.2s ease-out;transition-property:transform,opacity;white-space:nowrap;z-index:1}.InteractionOverlay_hidden__NRlgG .InteractionOverlay_message__oCuPR{opacity:0;transform:translate(-50%,-50%) scale(.3)}:root{--overlay-background-color:var(--page-color)}";
4
4
  var S = {"root":"InteractionOverlay_root__9FV58","overlay":"InteractionOverlay_overlay__Nwawr","hidden":"InteractionOverlay_hidden__NRlgG","message":"InteractionOverlay_message__oCuPR"};
5
5
  styleInject(css_248z);
6
6
 
@@ -92,6 +92,13 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
92
92
  return loadChatsFromLS(userSwitchKey).currentChatId;
93
93
  });
94
94
  const [outboundPendingByKey, setOutboundPendingByKey] = useState({});
95
+ const [panelBusyCount, setPanelBusyCount] = useState(0);
96
+ const acquirePanelBusy = useCallback(() => {
97
+ setPanelBusyCount(count => count + 1);
98
+ }, []);
99
+ const releasePanelBusy = useCallback(() => {
100
+ setPanelBusyCount(count => Math.max(0, count - 1));
101
+ }, []);
95
102
  const beginOutboundPending = useCallback((scopeId, chatSessionId) => {
96
103
  const key = outboundPendingKey(scopeId, chatSessionId);
97
104
  setOutboundPendingByKey(prev => ({
@@ -282,6 +289,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
282
289
  setChats({});
283
290
  setCurrentChatIdState({});
284
291
  setOutboundPendingByKey({});
292
+ setPanelBusyCount(0);
285
293
  return;
286
294
  }
287
295
  const loaded = loadChatsFromLS(userSwitchKey);
@@ -306,6 +314,9 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
306
314
  getCurrentChatId,
307
315
  deleteChat,
308
316
  outboundPendingByKey,
317
+ panelBusyCount,
318
+ acquirePanelBusy,
319
+ releasePanelBusy,
309
320
  }, children: children }));
310
321
  }
311
322
  const isChatEmpty = (chat) => chat?.messages.length === 0;
@@ -334,6 +345,29 @@ function useChatOutboundPending(scopeId, chatSessionId) {
334
345
  return (outboundPendingByKey[key] ?? 0) > 0;
335
346
  }, [scopeId, chatSessionId, outboundPendingByKey]);
336
347
  }
348
+ /** True when chat is waiting on API or panel UI busy (safe outside ChatProvider). */
349
+ function useIsChatLoading() {
350
+ const context = useContext(ChatContext);
351
+ return useMemo(() => {
352
+ if (!context)
353
+ return false;
354
+ const outboundPending = Object.values(context.outboundPendingByKey).some(count => count > 0);
355
+ return outboundPending || context.panelBusyCount > 0;
356
+ }, [context?.outboundPendingByKey, context?.panelBusyCount]);
357
+ }
358
+ /** Syncs chat panel `isLoading` into context for touch-overlay suppression. */
359
+ function useSyncChatPanelBusy(isLoading) {
360
+ const acquirePanelBusy = useContext(ChatContext)?.acquirePanelBusy;
361
+ const releasePanelBusy = useContext(ChatContext)?.releasePanelBusy;
362
+ useEffect(() => {
363
+ if (!isLoading || !acquirePanelBusy || !releasePanelBusy)
364
+ return;
365
+ acquirePanelBusy();
366
+ return () => {
367
+ releasePanelBusy();
368
+ };
369
+ }, [isLoading, acquirePanelBusy, releasePanelBusy]);
370
+ }
337
371
  function useChatsForScopeId(scopeId) {
338
372
  const { getChatsForScopeId, getCurrentChatId, setCurrentChatId, newChat, addMessage, removeMessageById, sendMessage, deleteChat, } = useChats();
339
373
  const chats = getChatsForScopeId(scopeId);
@@ -363,4 +397,4 @@ function useCurrentChat(scopeId) {
363
397
  return useChat(scopeId, chatId ?? undefined);
364
398
  }
365
399
 
366
- export { ChatContext, ChatProvider, isChatEmpty, outboundPendingKey, useChat, useChatOutboundPending, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat };
400
+ export { ChatContext, ChatProvider, isChatEmpty, outboundPendingKey, useChat, useChatOutboundPending, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat, useIsChatLoading, useSyncChatPanelBusy };
package/dist/esm/index.js CHANGED
@@ -6,7 +6,7 @@ export { DEFAULT_THEME_ACTIVE_COLOR } from './docs/lib/theme.js';
6
6
  export { SybilionAuthProvider, createSybilionApiFetch, getSybilionApiOriginFromSdk, sybilionApiFetch, useSybilionApiFetch, useSybilionAuth } from './sybilion-auth/SybilionAuthProvider.js';
7
7
  export { SYBILION_AUTH_LOGIN_PATH, normalizeApiBaseUrl } from './sybilion-auth/authPaths.js';
8
8
  export { exchangeAuth0AccessTokenForSybilionJwt } from './sybilion-auth/exchangeSybilionToken.js';
9
- export { ChatContext, ChatProvider, isChatEmpty, outboundPendingKey, useChat, useChatOutboundPending, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat } from './contexts/chat-context.js';
9
+ export { ChatContext, ChatProvider, isChatEmpty, outboundPendingKey, useChat, useChatOutboundPending, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat, useIsChatLoading, useSyncChatPanelBusy } from './contexts/chat-context.js';
10
10
  export { AnalysesSelector } from './components/ui/AnalysesSelector/AnalysesSelector.js';
11
11
  export { AnalysisLineIcon } from './components/ui/AnalysisLineIcon/AnalysisLineIcon.js';
12
12
  export { AppHeaderHost, AppHeaderPortal } from './components/ui/AppHeader/AppHeader.js';
@@ -2,6 +2,8 @@ interface InteractionOverlayProps {
2
2
  className?: string;
3
3
  children?: React.ReactNode;
4
4
  message?: string;
5
+ /** When true, hides the touch overlay (e.g. chart data loading). */
6
+ disabled?: boolean;
5
7
  }
6
- export declare function InteractionOverlay({ className, children, message, }: InteractionOverlayProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function InteractionOverlay({ className, children, message, disabled, }: InteractionOverlayProps): import("react/jsx-runtime").JSX.Element;
7
9
  export {};
@@ -21,6 +21,10 @@ export interface ChatContextType {
21
21
  * `outboundPendingKey(scopeId, chatSessionId)` (session id at send start).
22
22
  */
23
23
  outboundPendingByKey: Readonly<Record<string, number>>;
24
+ /** Ref-count of chat panels reporting UI busy (outbound wait, preset scripts, etc.). */
25
+ panelBusyCount: number;
26
+ acquirePanelBusy: () => void;
27
+ releasePanelBusy: () => void;
24
28
  }
25
29
  declare const ChatContext: import("react").Context<ChatContextType>;
26
30
  /** Stable composite key; avoids collisions if `scopeId` contains `:`. */
@@ -36,6 +40,10 @@ export declare const isChatEmpty: (chat: Chat | null) => boolean;
36
40
  export declare function useChats(): ChatContextType;
37
41
  export declare function useChat(scopeId: string | undefined, chatId: string | undefined): Chat | null;
38
42
  export declare function useChatOutboundPending(scopeId: string | undefined | null, chatSessionId: string | null | undefined): boolean;
43
+ /** True when chat is waiting on API or panel UI busy (safe outside ChatProvider). */
44
+ export declare function useIsChatLoading(): boolean;
45
+ /** Syncs chat panel `isLoading` into context for touch-overlay suppression. */
46
+ export declare function useSyncChatPanelBusy(isLoading: boolean): void;
39
47
  export declare function useChatsForScopeId(scopeId: string): {
40
48
  chats: Chat[];
41
49
  currentChat: Chat;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.18",
3
+ "version": "1.3.20",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -1,3 +1,5 @@
1
+ @import 'lib/theme.styl'
2
+
1
3
  // Chart container
2
4
  .chartContainer
3
5
  display flex
@@ -60,6 +62,8 @@
60
62
  // Tooltip portal positions with absolute + transform; do not override with relative.
61
63
  .recharts-tooltip-wrapper
62
64
  z-index 3 !important
65
+ max-width 90%
66
+ box-sizing border-box
63
67
 
64
68
  // Active dots and cursor
65
69
  .recharts-active-dot
@@ -75,8 +79,8 @@
75
79
  .tooltipContainer
76
80
  display grid
77
81
  box-sizing border-box
78
- width max-content
79
- max-width 100%
82
+ width 100%
83
+ max-width unquote('min(500px, 90vw)')
80
84
  min-width 0
81
85
  overflow-wrap break-word
82
86
  word-break break-word
@@ -85,13 +89,18 @@
85
89
  padding 0.375rem 0.625rem /* py-1.5 px-2.5 */
86
90
  border-radius 0.5rem /* rounded-lg */
87
91
  border 1px solid var(--border) / 0.5
88
- background-color var(--background)
92
+ // background-color var(--background)
93
+ backdrop-filter blur(10px)
94
+ background-color unquote('color-mix(in srgb, var(--background) 50%, transparent)')
89
95
  font-size 0.75rem /* text-xs */
90
96
  line-height 1rem
91
- box-shadow 0 10px 10px -5px rgba(0 0 0 0.3), 0 0 2px 0 rgba(0 0 0 0.5) /* shadow-xl */
97
+ box-shadow 0 10px 10px -5px rgba(0 0 0 0.2), 0 0 1px 0 var(--muted-foreground)
92
98
  opacity 0
93
99
  transition opacity 0.5s ease-out
94
100
 
101
+ :global(.dark) &
102
+ box-shadow 0 0 1px 0 var(--sb-slate-700), 0 10px 10px -5px rgba(0 0 0 0.7)
103
+
95
104
  .chartContainer:hover &
96
105
  opacity 1
97
106
  transition-duration 0.5s
@@ -135,19 +144,29 @@
135
144
  .tooltipContent
136
145
  display flex
137
146
  flex 1 1 0%
147
+ min-width 0
138
148
  justify-content space-between
139
149
  line-height 1
140
150
 
141
151
  .tooltipLabel
142
152
  display grid
153
+ min-width 0
143
154
  gap 0.375rem /* gap-1.5 */
144
155
 
156
+ .tooltipLabelText
157
+ min-width 0
158
+ max-width 100%
159
+ overflow hidden
160
+ word-break break-word
161
+ line-clamp(3)
162
+
145
163
  .tooltipValue
146
164
  font-family ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace
147
165
  font-weight 500
148
166
  font-variant-numeric tabular-nums
149
167
  color var(--foreground)
150
168
  margin-left var(--p-3)
169
+ white-space nowrap
151
170
 
152
171
  // Legend styles
153
172
  .legendContainer
@@ -16,6 +16,7 @@ interface CssExports {
16
16
  'tooltipIndicator': string;
17
17
  'tooltipItem': string;
18
18
  'tooltipLabel': string;
19
+ 'tooltipLabelText': string;
19
20
  'tooltipValue': string;
20
21
  }
21
22
  export const cssExports: CssExports;
@@ -92,7 +92,7 @@ export function ChartTooltipItem({
92
92
  >
93
93
  <div className={S.tooltipLabel}>
94
94
  {nestLabel ? tooltipLabel : null}
95
- <span className="text-muted-foreground">
95
+ <span className={cn('text-muted-foreground', S.tooltipLabelText)}>
96
96
  {itemConfig?.label || item.name}
97
97
  </span>
98
98
  </div>
@@ -231,7 +231,10 @@ export function ChartAreaInteractive({
231
231
  };
232
232
 
233
233
  return (
234
- <InteractionOverlay className={cn(className, loading && S.loading)}>
234
+ <InteractionOverlay
235
+ className={cn(className, loading && S.loading)}
236
+ disabled={loading}
237
+ >
235
238
  {(!disableTimeRangeSelector || headerActions) && (
236
239
  <div className={S.chartHeaderContainer}>
237
240
  {!disableTimeRangeSelector && (
@@ -32,6 +32,7 @@ import {
32
32
  useChat,
33
33
  useChatOutboundPending,
34
34
  useChatsForScopeId,
35
+ useSyncChatPanelBusy,
35
36
  } from '#uilib/contexts/chat-context';
36
37
  import useEvent from '#uilib/hooks/useEvent';
37
38
  import { useIsMobile } from '#uilib/hooks/useIsMobile';
@@ -144,6 +145,7 @@ export function useChatPanelChromeModel({
144
145
  currentChatId,
145
146
  );
146
147
  const isLoading = isOutboundPending || localUiBusy;
148
+ useSyncChatPanelBusy(isLoading);
147
149
 
148
150
  const {
149
151
  searchParams,
@@ -11,13 +11,28 @@
11
11
  cursor pointer
12
12
  touch-action manipulation
13
13
  background transparent
14
- box-shadow inset 0 0 80px 40px var(--overlay-background-color)
15
- transition box-shadow 200ms ease-out
14
+
15
+ &::before
16
+ content ''
17
+ position absolute
18
+ top calc(-1 * var(--p-2))
19
+ right calc(-1 * var(--p-2))
20
+ bottom calc(-1 * var(--p-2))
21
+ left calc(-1 * var(--p-2))
22
+ background-color unquote('color-mix(in srgb, var(--overlay-background-color) 70%, transparent)')
23
+ box-shadow inset 0 0 80px 40px var(--overlay-background-color)
24
+ pointer-events none
25
+ z-index -1
26
+ transition 200ms ease-out
27
+ transition-property box-shadow, background-color
16
28
 
17
29
  .hidden &
18
- box-shadow inset 0 0 0 0 var(--overlay-background-color)
19
30
  pointer-events none
20
31
 
32
+ &::before
33
+ box-shadow inset 0 0 0 0 var(--overlay-background-color)
34
+ background-color transparent
35
+
21
36
  .message
22
37
  position absolute
23
38
  top 50%
@@ -1,6 +1,7 @@
1
1
  import cn from 'classnames';
2
2
  import { useCallback, useEffect, useState } from 'react';
3
3
 
4
+ import { useIsChatLoading } from '#uilib/contexts/chat-context';
4
5
  import { useIsTouchDevice } from '#uilib/hooks/useIsTouchDevice';
5
6
 
6
7
  import S from './InteractionOverlay.styl';
@@ -9,14 +10,19 @@ interface InteractionOverlayProps {
9
10
  className?: string;
10
11
  children?: React.ReactNode;
11
12
  message?: string;
13
+ /** When true, hides the touch overlay (e.g. chart data loading). */
14
+ disabled?: boolean;
12
15
  }
13
16
 
14
17
  export function InteractionOverlay({
15
18
  className,
16
19
  children,
17
20
  message = 'Touch to interact',
21
+ disabled = false,
18
22
  }: InteractionOverlayProps) {
19
23
  const isTouchDevice = useIsTouchDevice();
24
+ const isChatLoading = useIsChatLoading();
25
+ const suppressOverlay = disabled || isChatLoading;
20
26
  const [isVisible, setIsVisible] = useState(true);
21
27
 
22
28
  const handleClick = useCallback(() => {
@@ -24,13 +30,13 @@ export function InteractionOverlay({
24
30
  }, []);
25
31
 
26
32
  useEffect(() => {
27
- if (isTouchDevice) {
33
+ if (isTouchDevice && !suppressOverlay) {
28
34
  setIsVisible(true);
29
35
  }
30
- }, [isTouchDevice]);
36
+ }, [isTouchDevice, suppressOverlay]);
31
37
 
32
38
  useEffect(() => {
33
- if (!isTouchDevice) return;
39
+ if (!isTouchDevice || suppressOverlay) return;
34
40
 
35
41
  const handleScroll = () => {
36
42
  setIsVisible(true);
@@ -40,14 +46,20 @@ export function InteractionOverlay({
40
46
  return () => {
41
47
  document.removeEventListener('scroll', handleScroll, { capture: true });
42
48
  };
43
- }, [isTouchDevice]);
49
+ }, [isTouchDevice, suppressOverlay]);
44
50
 
45
51
  if (!isTouchDevice) {
46
52
  return <>{children}</>;
47
53
  }
48
54
 
49
55
  return (
50
- <div className={cn(S.root, !isVisible && S.hidden, className)}>
56
+ <div
57
+ className={cn(
58
+ S.root,
59
+ (!isVisible || suppressOverlay) && S.hidden,
60
+ className,
61
+ )}
62
+ >
51
63
  {children}
52
64
  <button
53
65
  type="button"
@@ -66,6 +66,10 @@ export interface ChatContextType {
66
66
  * `outboundPendingKey(scopeId, chatSessionId)` (session id at send start).
67
67
  */
68
68
  outboundPendingByKey: Readonly<Record<string, number>>;
69
+ /** Ref-count of chat panels reporting UI busy (outbound wait, preset scripts, etc.). */
70
+ panelBusyCount: number;
71
+ acquirePanelBusy: () => void;
72
+ releasePanelBusy: () => void;
69
73
  }
70
74
 
71
75
  const ChatContext = createContext<ChatContextType | undefined>(undefined);
@@ -175,6 +179,15 @@ export function ChatProvider({
175
179
  const [outboundPendingByKey, setOutboundPendingByKey] = useState<
176
180
  Record<string, number>
177
181
  >({});
182
+ const [panelBusyCount, setPanelBusyCount] = useState(0);
183
+
184
+ const acquirePanelBusy = useCallback(() => {
185
+ setPanelBusyCount(count => count + 1);
186
+ }, []);
187
+
188
+ const releasePanelBusy = useCallback(() => {
189
+ setPanelBusyCount(count => Math.max(0, count - 1));
190
+ }, []);
178
191
 
179
192
  const beginOutboundPending = useCallback(
180
193
  (scopeId: string, chatSessionId: string) => {
@@ -444,6 +457,7 @@ export function ChatProvider({
444
457
  setChats({});
445
458
  setCurrentChatIdState({});
446
459
  setOutboundPendingByKey({});
460
+ setPanelBusyCount(0);
447
461
  return;
448
462
  }
449
463
 
@@ -475,6 +489,9 @@ export function ChatProvider({
475
489
  getCurrentChatId,
476
490
  deleteChat,
477
491
  outboundPendingByKey,
492
+ panelBusyCount,
493
+ acquirePanelBusy,
494
+ releasePanelBusy,
478
495
  }}
479
496
  >
480
497
  {children}
@@ -519,6 +536,31 @@ export function useChatOutboundPending(
519
536
  }, [scopeId, chatSessionId, outboundPendingByKey]);
520
537
  }
521
538
 
539
+ /** True when chat is waiting on API or panel UI busy (safe outside ChatProvider). */
540
+ export function useIsChatLoading(): boolean {
541
+ const context = useContext(ChatContext);
542
+ return useMemo(() => {
543
+ if (!context) return false;
544
+ const outboundPending = Object.values(context.outboundPendingByKey).some(
545
+ count => count > 0,
546
+ );
547
+ return outboundPending || context.panelBusyCount > 0;
548
+ }, [context?.outboundPendingByKey, context?.panelBusyCount]);
549
+ }
550
+
551
+ /** Syncs chat panel `isLoading` into context for touch-overlay suppression. */
552
+ export function useSyncChatPanelBusy(isLoading: boolean): void {
553
+ const acquirePanelBusy = useContext(ChatContext)?.acquirePanelBusy;
554
+ const releasePanelBusy = useContext(ChatContext)?.releasePanelBusy;
555
+ useEffect(() => {
556
+ if (!isLoading || !acquirePanelBusy || !releasePanelBusy) return;
557
+ acquirePanelBusy();
558
+ return () => {
559
+ releasePanelBusy();
560
+ };
561
+ }, [isLoading, acquirePanelBusy, releasePanelBusy]);
562
+ }
563
+
522
564
  export function useChatsForScopeId(scopeId: string) {
523
565
  const {
524
566
  getChatsForScopeId,
@@ -6,7 +6,9 @@ import type {
6
6
  OverlayMode,
7
7
  } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.types';
8
8
  import { ForecastItemData } from '#uilib/components/ui/ChartAreaInteractive/ChartLines';
9
+ import { Label } from '#uilib/components/ui/Label';
9
10
  import { PageContentSection } from '#uilib/components/ui/Page';
11
+ import { Switch } from '#uilib/components/ui/Switch';
10
12
  import { Tabs, TabsList, TabsTrigger } from '#uilib/components/ui/Tabs';
11
13
  import { useTheme } from '#uilib/contexts/theme-context';
12
14
  import type { ForecastData } from '#uilib/types/forecast-data';
@@ -83,7 +85,10 @@ const INITIAL_CHART: ChartDataPoint[] = [
83
85
  ];
84
86
 
85
87
  const DEMO_FORECAST_ITEMS: ForecastItemData[] = [
86
- { id: DEMO_FORECAST_ID, name: 'My custom forecast' },
88
+ {
89
+ id: DEMO_FORECAST_ID,
90
+ name: 'My custom forecast with an extremely long descriptive name that should wrap and clamp inside the chart tooltip after three lines',
91
+ },
87
92
  ];
88
93
 
89
94
  type DemoMode = 'none' | OverlayMode;
@@ -102,6 +107,7 @@ export default function ChartAreaInteractivePage() {
102
107
  string | null
103
108
  >('0.9');
104
109
  const [upperThreshold, setUpperThreshold] = useState(15);
110
+ const [loading, setLoading] = useState(false);
105
111
 
106
112
  const toggleLegendSeries = useCallback((key: string) => {
107
113
  setHidden(prev => {
@@ -155,6 +161,21 @@ export default function ChartAreaInteractivePage() {
155
161
  <TabsTrigger value="thresholds">Thresholds</TabsTrigger>
156
162
  </TabsList>
157
163
  </Tabs>
164
+ <div
165
+ style={{
166
+ display: 'flex',
167
+ alignItems: 'center',
168
+ gap: 8,
169
+ marginBottom: 16,
170
+ }}
171
+ >
172
+ <Switch
173
+ id="chart-loading"
174
+ checked={loading}
175
+ onCheckedChange={setLoading}
176
+ />
177
+ <Label htmlFor="chart-loading">Loading</Label>
178
+ </div>
158
179
  <ChartAreaInteractive
159
180
  timeRange={timeRange}
160
181
  onTimeRangeChange={setTimeRange}
@@ -163,7 +184,7 @@ export default function ChartAreaInteractivePage() {
163
184
  mode={mode}
164
185
  chartData={chartData}
165
186
  forecastData={DEMO_FORECAST_ITEMS}
166
- loading={false}
187
+ loading={loading}
167
188
  isDarkTheme={isDarkMode}
168
189
  toggleLegendSeries={toggleLegendSeries}
169
190
  ensureAnalysisSeriesVisible={ensureAnalysisSeriesVisible}