@perses-dev/components 0.56.0-beta.0 → 0.56.0-beta.1

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 +1 @@
1
- {"version":3,"sources":["../../../src/Table/hooks/useVirtualizedTableKeyboardNav.tsx"],"sourcesContent":["// Copyright The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport type { FocusEventHandler, KeyboardEventHandler, MutableRefObject, RefObject } from 'react';\nimport type { TableVirtuosoHandle } from 'react-virtuoso';\n\nimport type { UseTableKeyboardNavProps } from './useTableKeyboardNav';\nimport { useTableKeyboardNav } from './useTableKeyboardNav';\n\ninterface UseVirtualizedTableKeyboardNavProps extends Omit<UseTableKeyboardNavProps, 'onActiveCellChange'> {\n visibleRange: MutableRefObject<{\n startIndex: number;\n endIndex: number;\n }>;\n virtualTable: RefObject<TableVirtuosoHandle | null>;\n maxRows: number;\n maxColumns: number;\n}\n\ntype TableCellPosition = {\n row: number;\n column: number;\n};\n\n/**\n * Hook for managing keyboard navigation when using a virtualized table.\n */\nexport function useVirtualizedTableKeyboardNav({\n visibleRange,\n virtualTable,\n maxRows,\n maxColumns,\n}: UseVirtualizedTableKeyboardNavProps): {\n activeCell: TableCellPosition;\n isActive: boolean;\n onTableKeyDown: KeyboardEventHandler<HTMLTableElement>;\n onCellFocus: (cellPosition: TableCellPosition) => void;\n onTableBlur: FocusEventHandler<HTMLTableElement>;\n} {\n const baseKeyboard = useTableKeyboardNav({\n maxRows,\n maxColumns,\n onActiveCellChange: (e, currentPosition, defaultNewPosition) => {\n const key = e.key;\n\n const defaultValueChanged =\n defaultNewPosition &&\n (currentPosition.column !== defaultNewPosition.column || currentPosition.row !== defaultNewPosition.row);\n const nextRow = defaultNewPosition?.row ?? currentPosition.row;\n\n if (key === 'ArrowDown' && defaultValueChanged) {\n // Use default cell position. Shift the virtual table scroll position\n // when needed to make the active cell visible.\n if (nextRow - 1 < visibleRange.current.startIndex || nextRow - 1 > visibleRange.current.endIndex) {\n virtualTable.current?.scrollToIndex({\n index: nextRow - 1,\n align: 'end',\n });\n }\n } else if (key === 'ArrowUp' && defaultValueChanged) {\n // Use default cell position. Shift the virtual table scroll position\n // when needed to make the active cell visible.\n if (nextRow - 1 < visibleRange.current.startIndex || nextRow - 1 > visibleRange.current.endIndex) {\n virtualTable.current?.scrollToIndex({\n index: nextRow - 1,\n align: 'start',\n });\n }\n } else if (defaultValueChanged && (key === 'Home' || key === 'End')) {\n // Use default cell position. Shift the virtual table scroll position\n // when needed to make the active cell visible.\n virtualTable.current?.scrollToIndex({\n index: Math.max(nextRow - 1, 0),\n align: 'start',\n });\n } else if (key === 'PageDown') {\n // Full handling of logic for `PageDown` because there is no default.\n e.preventDefault();\n\n let nextRow = currentPosition.row;\n // Add 1 to account for header\n\n nextRow = Math.min(maxRows - 1, visibleRange.current.endIndex + 1);\n\n virtualTable.current?.scrollToIndex({\n index: nextRow - 1,\n align: 'start',\n });\n\n return {\n row: nextRow,\n column: currentPosition.column,\n };\n } else if (key === 'PageUp') {\n // Full handling of logic for `PageUp` because there is no default.\n let nextRow = currentPosition.row;\n // Minus 1 to account for header\n nextRow = Math.max(0, visibleRange.current.startIndex - 1);\n virtualTable.current?.scrollToIndex({\n index: nextRow - 1,\n align: 'end',\n });\n\n return {\n row: nextRow,\n column: currentPosition.column,\n };\n }\n\n return defaultNewPosition;\n },\n });\n\n return baseKeyboard;\n}\n"],"names":["useTableKeyboardNav"],"mappings":"AAAA,+BAA+B;AAC/B,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;;AAMjC,SAASA,mBAAmB,QAAQ,2BAAwB;AAiB5D;;CAEC,GACD,OAAO"}
1
+ {"version":3,"sources":["../../../src/Table/hooks/useVirtualizedTableKeyboardNav.tsx"],"sourcesContent":["// Copyright The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport type { FocusEventHandler, KeyboardEventHandler, MutableRefObject, RefObject } from 'react';\nimport React, { useCallback } from 'react';\nimport type { TableVirtuosoHandle } from 'react-virtuoso';\n\nimport type { UseTableKeyboardNavProps } from './useTableKeyboardNav';\nimport { useTableKeyboardNav } from './useTableKeyboardNav';\n\ninterface UseVirtualizedTableKeyboardNavProps extends Omit<UseTableKeyboardNavProps, 'onActiveCellChange'> {\n visibleRange: MutableRefObject<{\n startIndex: number;\n endIndex: number;\n }>;\n virtualTable: RefObject<TableVirtuosoHandle | null>;\n maxRows: number;\n maxColumns: number;\n}\n\ntype TableCellPosition = {\n row: number;\n column: number;\n};\n\n/**\n * Hook for managing keyboard navigation when using a virtualized table.\n */\nexport function useVirtualizedTableKeyboardNav({\n visibleRange,\n virtualTable,\n maxRows,\n maxColumns,\n}: UseVirtualizedTableKeyboardNavProps): {\n activeCell: TableCellPosition;\n isActive: boolean;\n onTableKeyDown: KeyboardEventHandler<HTMLTableElement>;\n onCellFocus: (cellPosition: TableCellPosition) => void;\n onTableBlur: FocusEventHandler<HTMLTableElement>;\n} {\n const onActiveCellChange = useCallback(\n (\n e: React.KeyboardEvent<HTMLTableElement>,\n currentPosition: TableCellPosition,\n defaultNewPosition: TableCellPosition | undefined,\n ) => {\n const key = e.key;\n\n const defaultValueChanged =\n defaultNewPosition &&\n (currentPosition.column !== defaultNewPosition.column || currentPosition.row !== defaultNewPosition.row);\n const nextRow = defaultNewPosition?.row ?? currentPosition.row;\n\n if (key === 'ArrowDown' && defaultValueChanged) {\n // Use default cell position. Shift the virtual table scroll position\n // when needed to make the active cell visible.\n if (nextRow - 1 < visibleRange.current.startIndex || nextRow - 1 > visibleRange.current.endIndex) {\n virtualTable.current?.scrollToIndex({\n index: nextRow - 1,\n align: 'end',\n });\n }\n } else if (key === 'ArrowUp' && defaultValueChanged) {\n // Use default cell position. Shift the virtual table scroll position\n // when needed to make the active cell visible.\n if (nextRow - 1 < visibleRange.current.startIndex || nextRow - 1 > visibleRange.current.endIndex) {\n virtualTable.current?.scrollToIndex({\n index: nextRow - 1,\n align: 'start',\n });\n }\n } else if (defaultValueChanged && (key === 'Home' || key === 'End')) {\n // Use default cell position. Shift the virtual table scroll position\n // when needed to make the active cell visible.\n virtualTable.current?.scrollToIndex({\n index: Math.max(nextRow - 1, 0),\n align: 'start',\n });\n } else if (key === 'PageDown') {\n // Full handling of logic for `PageDown` because there is no default.\n e.preventDefault();\n\n let nextRow = currentPosition.row;\n // Add 1 to account for header\n\n nextRow = Math.min(maxRows - 1, visibleRange.current.endIndex + 1);\n\n virtualTable.current?.scrollToIndex({\n index: nextRow - 1,\n align: 'start',\n });\n\n return {\n row: nextRow,\n column: currentPosition.column,\n };\n } else if (key === 'PageUp') {\n // Full handling of logic for `PageUp` because there is no default.\n let nextRow = currentPosition.row;\n // Minus 1 to account for header\n nextRow = Math.max(0, visibleRange.current.startIndex - 1);\n virtualTable.current?.scrollToIndex({\n index: nextRow - 1,\n align: 'end',\n });\n\n return {\n row: nextRow,\n column: currentPosition.column,\n };\n }\n\n return defaultNewPosition;\n },\n [maxRows, visibleRange, virtualTable],\n );\n\n return useTableKeyboardNav({\n maxRows,\n maxColumns,\n onActiveCellChange,\n });\n}\n"],"names":["React","useTableKeyboardNav"],"mappings":"AAAA,+BAA+B;AAC/B,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;;AAGjC,OAAOA,WAA4B,QAAQ;AAI3C,SAASC,mBAAmB,QAAQ,2BAAwB;AAiB5D;;CAEC,GACD,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/TimeSeriesTooltip/utils.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,KAAK,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AASrE;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,EAC9B,SAAS,EAAE,iBAAiB,GAAG,IAAI,EACnC,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,EACpB,gBAAgB,CAAC,EAAE,OAAO,GAAG,IAAI,GAChC,MAAM,GAAG,SAAS,CAuDpB;AASD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,iBAAiB,GAAG,IAAI,EACnC,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwBzB;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAO1F;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,sBAAsB,EACrC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,MAAM,CAcR;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,CAiCnH;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAQjC;AAED,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,eAAe,GACrB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAexC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/TimeSeriesTooltip/utils.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,KAAK,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AASrE;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,EAC9B,SAAS,EAAE,iBAAiB,GAAG,IAAI,EACnC,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,EACpB,gBAAgB,CAAC,EAAE,OAAO,GAAG,IAAI,GAChC,MAAM,GAAG,SAAS,CAmEpB;AASD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,iBAAiB,GAAG,IAAI,EACnC,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwBzB;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAO1F;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,sBAAsB,EACrC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,MAAM,CAcR;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,CAiCnH;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAQjC;AAED,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,eAAe,GACrB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAexC"}
@@ -28,20 +28,32 @@ import { TOOLTIP_MAX_WIDTH, TOOLTIP_MAX_HEIGHT, TOOLTIP_MIN_WIDTH, TOOLTIP_BG_CO
28
28
  const effectiveWidth = tooltipWidth > 0 ? tooltipWidth : TOOLTIP_MAX_WIDTH;
29
29
  let x = mousePos.page.x + cursorPaddingX; // Default to right side of the cursor
30
30
  let y = mousePos.page.y + cursorPaddingY;
31
+ let cursorBaseY = mousePos.page.y;
31
32
  // If containerElement is defined, adjust coordinates relative to the container
32
33
  if (containerElement) {
33
34
  const containerRect = containerElement.getBoundingClientRect();
34
35
  x = x - containerRect.left + containerElement.scrollLeft;
35
36
  y = y - containerRect.top + containerElement.scrollTop;
36
- // Ensure tooltip does not go out of the container's bottom
37
+ cursorBaseY = cursorBaseY - containerRect.top + containerElement.scrollTop;
38
+ // Ensure tooltip does not go out of the container's bottom: flip it above the
39
+ // cursor so it keeps pointing at the hovered marker instead of covering it.
37
40
  const containerBottom = containerRect.top + containerElement.scrollHeight;
38
41
  if (y + effectiveHeight > containerBottom) {
39
- y = Math.max(containerBottom - effectiveHeight - cursorPaddingY, TOOLTIP_PADDING / 2);
42
+ y = cursorBaseY - effectiveHeight - cursorPaddingY;
43
+ // When it does not fit above the cursor either, fall back to the container bottom.
44
+ if (y < containerElement.scrollTop + TOOLTIP_PADDING / 2 || y + effectiveHeight > containerBottom) {
45
+ y = Math.max(containerBottom - effectiveHeight - cursorPaddingY, TOOLTIP_PADDING / 2);
46
+ }
40
47
  }
41
48
  } else {
42
- // Ensure tooltip does not go out of the screen on the bottom
49
+ // Ensure tooltip does not go out of the screen on the bottom: flip it above the
50
+ // cursor so it keeps pointing at the hovered marker instead of covering it.
43
51
  if (y + effectiveHeight > window.innerHeight + window.scrollY) {
44
- y = Math.max(window.innerHeight + window.scrollY - effectiveHeight - cursorPaddingY, TOOLTIP_PADDING / 2);
52
+ y = cursorBaseY - effectiveHeight - cursorPaddingY;
53
+ // When it does not fit above the cursor either, fall back to the screen bottom.
54
+ if (y < window.scrollY + TOOLTIP_PADDING / 2 || y + effectiveHeight > window.innerHeight + window.scrollY) {
55
+ y = Math.max(window.innerHeight + window.scrollY - effectiveHeight - cursorPaddingY, TOOLTIP_PADDING / 2);
56
+ }
45
57
  }
46
58
  }
47
59
  // Ensure tooltip does not go out of the screen on the right
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/TimeSeriesTooltip/utils.ts"],"sourcesContent":["// Copyright The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport type { Theme } from '@mui/material';\nimport type { BarSeriesOption, LineSeriesOption } from 'echarts/charts';\nimport type { ECharts as EChartsInstance } from 'echarts/core';\n\nimport type { TimeChartSeriesMapping } from '../model';\nimport type { CursorCoordinates, CursorData } from './tooltip-model';\nimport {\n TOOLTIP_MAX_WIDTH,\n TOOLTIP_MAX_HEIGHT,\n TOOLTIP_MIN_WIDTH,\n TOOLTIP_BG_COLOR_FALLBACK,\n TOOLTIP_PADDING,\n} from './tooltip-model';\n\n/**\n * Determine position of tooltip depending on chart dimensions and the number of focused series.\n */\nexport function assembleTransform(\n mousePos: CursorData['coords'],\n pinnedPos: CursorCoordinates | null,\n tooltipHeight: number,\n tooltipWidth: number,\n containerElement?: Element | null,\n): string | undefined {\n if (mousePos === null) {\n return undefined;\n }\n\n const cursorPaddingX = 32;\n const cursorPaddingY = 16;\n\n if (pinnedPos !== null) {\n mousePos = pinnedPos;\n }\n\n if (mousePos.plotCanvas.x === undefined) return undefined;\n\n // Fall back to max size before the resize observer reports a real measurement.\n const effectiveHeight = tooltipHeight > 0 ? tooltipHeight : TOOLTIP_MAX_HEIGHT;\n const effectiveWidth = tooltipWidth > 0 ? tooltipWidth : TOOLTIP_MAX_WIDTH;\n\n let x = mousePos.page.x + cursorPaddingX; // Default to right side of the cursor\n let y = mousePos.page.y + cursorPaddingY;\n\n // If containerElement is defined, adjust coordinates relative to the container\n if (containerElement) {\n const containerRect = containerElement.getBoundingClientRect();\n x = x - containerRect.left + containerElement.scrollLeft;\n y = y - containerRect.top + containerElement.scrollTop;\n\n // Ensure tooltip does not go out of the container's bottom\n const containerBottom = containerRect.top + containerElement.scrollHeight;\n if (y + effectiveHeight > containerBottom) {\n y = Math.max(containerBottom - effectiveHeight - cursorPaddingY, TOOLTIP_PADDING / 2);\n }\n } else {\n // Ensure tooltip does not go out of the screen on the bottom\n if (y + effectiveHeight > window.innerHeight + window.scrollY) {\n y = Math.max(window.innerHeight + window.scrollY - effectiveHeight - cursorPaddingY, TOOLTIP_PADDING / 2);\n }\n }\n\n // Ensure tooltip does not go out of the screen on the right\n if (x + effectiveWidth > window.innerWidth) {\n x = mousePos.page.x - effectiveWidth - cursorPaddingX; // Move to the left of the cursor\n }\n\n // Ensure tooltip does not go out of the screen on the left\n if (x < cursorPaddingX) {\n x = cursorPaddingX;\n }\n\n // Ensure tooltip does not go out of the screen on the top\n if (y < TOOLTIP_PADDING / 2) {\n y = TOOLTIP_PADDING / 2;\n }\n\n return `translate3d(${x}px, ${y}px, 0)`;\n}\n\n/**\n * Stacking level of a pinned tooltip. It must cover the table legend's sticky header, which\n * react-virtuoso renders at z-index 2, and stay below PanelHeader (5, in @perses-dev/dashboards),\n * the drawer and the sticky dashboard header.\n */\nconst PINNED_TOOLTIP_Z_INDEX = 3;\n\n/**\n * Helper for tooltip positioning styles\n */\nexport function getTooltipStyles(\n theme: Theme,\n pinnedPos: CursorCoordinates | null,\n maxHeight?: number,\n): Record<string, unknown> {\n const adjustedMaxHeight = maxHeight ? maxHeight - TOOLTIP_PADDING : undefined;\n return {\n minWidth: TOOLTIP_MIN_WIDTH,\n maxWidth: TOOLTIP_MAX_WIDTH,\n maxHeight: adjustedMaxHeight ?? TOOLTIP_MAX_HEIGHT,\n padding: 0,\n position: 'absolute',\n top: 0,\n left: 0,\n backgroundColor: theme.palette.designSystem?.grey[800] ?? TOOLTIP_BG_COLOR_FALLBACK,\n borderRadius: '6px',\n color: '#fff',\n fontSize: '11px',\n visibility: 'visible',\n opacity: 1,\n // Animating transform causes intermediate positions outside the viewport; animate opacity/visibility instead.\n transition: 'opacity 0.1s ease-out, visibility 0.1s ease-out',\n zIndex: pinnedPos !== null ? PINNED_TOOLTIP_Z_INDEX : theme.zIndex.tooltip,\n overflow: 'hidden',\n '&:hover': {\n overflowY: 'auto',\n },\n };\n}\n\nexport function getPixelXFromGrid(timestamp: number, chart: EChartsInstance): number | null {\n try {\n const pixelCoords = chart.convertToPixel('grid', [timestamp, 0]);\n return pixelCoords?.[0] ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Returns the cumulative (visual) Y for a series, accumulating stack totals in-place.\n * For non-stacked series, returns the raw yValue unchanged.\n * Mutates `stackTotals` — pass a fresh Map for each cursor evaluation.\n */\nexport function calculateVisualYForSeries(\n seriesIdx: number,\n yValue: number,\n seriesMapping: TimeChartSeriesMapping,\n stackTotals: Map<string, number>,\n): number {\n const currentSeries = seriesMapping[seriesIdx];\n if (!currentSeries) return yValue;\n\n const stackId = (currentSeries as LineSeriesOption | BarSeriesOption).stack;\n if (!stackId) {\n return yValue;\n }\n\n const stackIdStr = stackId.toString();\n const currentTotal = stackTotals.get(stackIdStr) ?? 0;\n const newTotal = currentTotal + yValue;\n stackTotals.set(stackIdStr, newTotal);\n return newTotal;\n}\n\nexport function calculateBarBandwidth(timestamp: number, sortedTimestamps: number[], chart: EChartsInstance): number {\n const currentIdx = sortedTimestamps.indexOf(timestamp);\n if (currentIdx === -1) {\n return 20;\n }\n\n const prevTimestamp = currentIdx > 0 ? (sortedTimestamps[currentIdx - 1] ?? null) : null;\n const nextTimestamp = currentIdx < sortedTimestamps.length - 1 ? (sortedTimestamps[currentIdx + 1] ?? null) : null;\n\n const currentPixelX = getPixelXFromGrid(timestamp, chart);\n if (currentPixelX === null) return 20;\n\n let leftBound: number;\n let rightBound: number;\n\n if (prevTimestamp !== null && nextTimestamp !== null) {\n const prevPixelX = getPixelXFromGrid(prevTimestamp, chart) ?? currentPixelX;\n const nextPixelX = getPixelXFromGrid(nextTimestamp, chart) ?? currentPixelX;\n leftBound = (currentPixelX + prevPixelX) / 2;\n rightBound = (currentPixelX + nextPixelX) / 2;\n } else if (prevTimestamp !== null) {\n const prevPixelX = getPixelXFromGrid(prevTimestamp, chart) ?? currentPixelX;\n leftBound = (currentPixelX + prevPixelX) / 2;\n rightBound = currentPixelX + (currentPixelX - leftBound);\n } else if (nextTimestamp !== null) {\n const nextPixelX = getPixelXFromGrid(nextTimestamp, chart) ?? currentPixelX;\n rightBound = (currentPixelX + nextPixelX) / 2;\n leftBound = currentPixelX - (rightBound - currentPixelX);\n } else {\n return 20;\n }\n\n return Math.max(1, rightBound - leftBound);\n}\n\n/**\n * Computes the pixel left/right bounds of one bar segment within a group.\n * @param barRelativeIdx - zero-based index among bar-only series\n * @param bandwidth - total pixel width for the bar group\n * @param centerPixelX - pixel X of the bar group centre\n * @param barCount - total bar series count (lines excluded)\n */\nexport function calculateBarSegmentBounds(\n barRelativeIdx: number,\n bandwidth: number,\n centerPixelX: number,\n barCount: number,\n): { left: number; right: number } {\n const count = Math.max(1, barCount);\n const segmentWidth = bandwidth / count;\n const segmentLeft = centerPixelX - bandwidth / 2 + barRelativeIdx * segmentWidth;\n return {\n left: segmentLeft,\n right: segmentLeft + segmentWidth,\n };\n}\n\nexport function calculateBarYBounds(\n visualYBottom: number,\n visualYTop: number,\n chart: EChartsInstance,\n): { top: number; bottom: number } | null {\n try {\n const bottomPixel = chart.convertToPixel('grid', [0, visualYBottom]);\n const topPixel = chart.convertToPixel('grid', [0, visualYTop]);\n\n if (!bottomPixel || !topPixel || bottomPixel[1] === undefined || topPixel[1] === undefined) return null;\n\n // Y increases downward in pixels; min/max normalizes the mapping for negative bar values.\n return {\n top: Math.min(topPixel[1], bottomPixel[1]),\n bottom: Math.max(topPixel[1], bottomPixel[1]),\n };\n } catch {\n return null;\n }\n}\n"],"names":["TOOLTIP_MAX_WIDTH","TOOLTIP_MAX_HEIGHT","TOOLTIP_MIN_WIDTH","TOOLTIP_BG_COLOR_FALLBACK","TOOLTIP_PADDING","assembleTransform","mousePos","pinnedPos","tooltipHeight","tooltipWidth","containerElement","undefined","cursorPaddingX","cursorPaddingY","plotCanvas","x","effectiveHeight","effectiveWidth","page","y","containerRect","getBoundingClientRect","left","scrollLeft","top","scrollTop","containerBottom","scrollHeight","Math","max","window","innerHeight","scrollY","innerWidth","PINNED_TOOLTIP_Z_INDEX","getTooltipStyles","theme","maxHeight","adjustedMaxHeight","minWidth","maxWidth","padding","position","backgroundColor","palette","designSystem","grey","borderRadius","color","fontSize","visibility","opacity","transition","zIndex","tooltip","overflow","overflowY","getPixelXFromGrid","timestamp","chart","pixelCoords","convertToPixel","calculateVisualYForSeries","seriesIdx","yValue","seriesMapping","stackTotals","currentSeries","stackId","stack","stackIdStr","toString","currentTotal","get","newTotal","set","calculateBarBandwidth","sortedTimestamps","currentIdx","indexOf","prevTimestamp","nextTimestamp","length","currentPixelX","leftBound","rightBound","prevPixelX","nextPixelX","calculateBarSegmentBounds","barRelativeIdx","bandwidth","centerPixelX","barCount","count","segmentWidth","segmentLeft","right","calculateBarYBounds","visualYBottom","visualYTop","bottomPixel","topPixel","min","bottom"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/TimeSeriesTooltip/utils.ts"],"sourcesContent":["// Copyright The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport type { Theme } from '@mui/material';\nimport type { BarSeriesOption, LineSeriesOption } from 'echarts/charts';\nimport type { ECharts as EChartsInstance } from 'echarts/core';\n\nimport type { TimeChartSeriesMapping } from '../model';\nimport type { CursorCoordinates, CursorData } from './tooltip-model';\nimport {\n TOOLTIP_MAX_WIDTH,\n TOOLTIP_MAX_HEIGHT,\n TOOLTIP_MIN_WIDTH,\n TOOLTIP_BG_COLOR_FALLBACK,\n TOOLTIP_PADDING,\n} from './tooltip-model';\n\n/**\n * Determine position of tooltip depending on chart dimensions and the number of focused series.\n */\nexport function assembleTransform(\n mousePos: CursorData['coords'],\n pinnedPos: CursorCoordinates | null,\n tooltipHeight: number,\n tooltipWidth: number,\n containerElement?: Element | null,\n): string | undefined {\n if (mousePos === null) {\n return undefined;\n }\n\n const cursorPaddingX = 32;\n const cursorPaddingY = 16;\n\n if (pinnedPos !== null) {\n mousePos = pinnedPos;\n }\n\n if (mousePos.plotCanvas.x === undefined) return undefined;\n\n // Fall back to max size before the resize observer reports a real measurement.\n const effectiveHeight = tooltipHeight > 0 ? tooltipHeight : TOOLTIP_MAX_HEIGHT;\n const effectiveWidth = tooltipWidth > 0 ? tooltipWidth : TOOLTIP_MAX_WIDTH;\n\n let x = mousePos.page.x + cursorPaddingX; // Default to right side of the cursor\n let y = mousePos.page.y + cursorPaddingY;\n let cursorBaseY = mousePos.page.y;\n\n // If containerElement is defined, adjust coordinates relative to the container\n if (containerElement) {\n const containerRect = containerElement.getBoundingClientRect();\n x = x - containerRect.left + containerElement.scrollLeft;\n y = y - containerRect.top + containerElement.scrollTop;\n cursorBaseY = cursorBaseY - containerRect.top + containerElement.scrollTop;\n\n // Ensure tooltip does not go out of the container's bottom: flip it above the\n // cursor so it keeps pointing at the hovered marker instead of covering it.\n const containerBottom = containerRect.top + containerElement.scrollHeight;\n if (y + effectiveHeight > containerBottom) {\n y = cursorBaseY - effectiveHeight - cursorPaddingY;\n // When it does not fit above the cursor either, fall back to the container bottom.\n if (y < containerElement.scrollTop + TOOLTIP_PADDING / 2 || y + effectiveHeight > containerBottom) {\n y = Math.max(containerBottom - effectiveHeight - cursorPaddingY, TOOLTIP_PADDING / 2);\n }\n }\n } else {\n // Ensure tooltip does not go out of the screen on the bottom: flip it above the\n // cursor so it keeps pointing at the hovered marker instead of covering it.\n if (y + effectiveHeight > window.innerHeight + window.scrollY) {\n y = cursorBaseY - effectiveHeight - cursorPaddingY;\n // When it does not fit above the cursor either, fall back to the screen bottom.\n if (y < window.scrollY + TOOLTIP_PADDING / 2 || y + effectiveHeight > window.innerHeight + window.scrollY) {\n y = Math.max(window.innerHeight + window.scrollY - effectiveHeight - cursorPaddingY, TOOLTIP_PADDING / 2);\n }\n }\n }\n\n // Ensure tooltip does not go out of the screen on the right\n if (x + effectiveWidth > window.innerWidth) {\n x = mousePos.page.x - effectiveWidth - cursorPaddingX; // Move to the left of the cursor\n }\n\n // Ensure tooltip does not go out of the screen on the left\n if (x < cursorPaddingX) {\n x = cursorPaddingX;\n }\n\n // Ensure tooltip does not go out of the screen on the top\n if (y < TOOLTIP_PADDING / 2) {\n y = TOOLTIP_PADDING / 2;\n }\n\n return `translate3d(${x}px, ${y}px, 0)`;\n}\n\n/**\n * Stacking level of a pinned tooltip. It must cover the table legend's sticky header, which\n * react-virtuoso renders at z-index 2, and stay below PanelHeader (5, in @perses-dev/dashboards),\n * the drawer and the sticky dashboard header.\n */\nconst PINNED_TOOLTIP_Z_INDEX = 3;\n\n/**\n * Helper for tooltip positioning styles\n */\nexport function getTooltipStyles(\n theme: Theme,\n pinnedPos: CursorCoordinates | null,\n maxHeight?: number,\n): Record<string, unknown> {\n const adjustedMaxHeight = maxHeight ? maxHeight - TOOLTIP_PADDING : undefined;\n return {\n minWidth: TOOLTIP_MIN_WIDTH,\n maxWidth: TOOLTIP_MAX_WIDTH,\n maxHeight: adjustedMaxHeight ?? TOOLTIP_MAX_HEIGHT,\n padding: 0,\n position: 'absolute',\n top: 0,\n left: 0,\n backgroundColor: theme.palette.designSystem?.grey[800] ?? TOOLTIP_BG_COLOR_FALLBACK,\n borderRadius: '6px',\n color: '#fff',\n fontSize: '11px',\n visibility: 'visible',\n opacity: 1,\n // Animating transform causes intermediate positions outside the viewport; animate opacity/visibility instead.\n transition: 'opacity 0.1s ease-out, visibility 0.1s ease-out',\n zIndex: pinnedPos !== null ? PINNED_TOOLTIP_Z_INDEX : theme.zIndex.tooltip,\n overflow: 'hidden',\n '&:hover': {\n overflowY: 'auto',\n },\n };\n}\n\nexport function getPixelXFromGrid(timestamp: number, chart: EChartsInstance): number | null {\n try {\n const pixelCoords = chart.convertToPixel('grid', [timestamp, 0]);\n return pixelCoords?.[0] ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Returns the cumulative (visual) Y for a series, accumulating stack totals in-place.\n * For non-stacked series, returns the raw yValue unchanged.\n * Mutates `stackTotals` — pass a fresh Map for each cursor evaluation.\n */\nexport function calculateVisualYForSeries(\n seriesIdx: number,\n yValue: number,\n seriesMapping: TimeChartSeriesMapping,\n stackTotals: Map<string, number>,\n): number {\n const currentSeries = seriesMapping[seriesIdx];\n if (!currentSeries) return yValue;\n\n const stackId = (currentSeries as LineSeriesOption | BarSeriesOption).stack;\n if (!stackId) {\n return yValue;\n }\n\n const stackIdStr = stackId.toString();\n const currentTotal = stackTotals.get(stackIdStr) ?? 0;\n const newTotal = currentTotal + yValue;\n stackTotals.set(stackIdStr, newTotal);\n return newTotal;\n}\n\nexport function calculateBarBandwidth(timestamp: number, sortedTimestamps: number[], chart: EChartsInstance): number {\n const currentIdx = sortedTimestamps.indexOf(timestamp);\n if (currentIdx === -1) {\n return 20;\n }\n\n const prevTimestamp = currentIdx > 0 ? (sortedTimestamps[currentIdx - 1] ?? null) : null;\n const nextTimestamp = currentIdx < sortedTimestamps.length - 1 ? (sortedTimestamps[currentIdx + 1] ?? null) : null;\n\n const currentPixelX = getPixelXFromGrid(timestamp, chart);\n if (currentPixelX === null) return 20;\n\n let leftBound: number;\n let rightBound: number;\n\n if (prevTimestamp !== null && nextTimestamp !== null) {\n const prevPixelX = getPixelXFromGrid(prevTimestamp, chart) ?? currentPixelX;\n const nextPixelX = getPixelXFromGrid(nextTimestamp, chart) ?? currentPixelX;\n leftBound = (currentPixelX + prevPixelX) / 2;\n rightBound = (currentPixelX + nextPixelX) / 2;\n } else if (prevTimestamp !== null) {\n const prevPixelX = getPixelXFromGrid(prevTimestamp, chart) ?? currentPixelX;\n leftBound = (currentPixelX + prevPixelX) / 2;\n rightBound = currentPixelX + (currentPixelX - leftBound);\n } else if (nextTimestamp !== null) {\n const nextPixelX = getPixelXFromGrid(nextTimestamp, chart) ?? currentPixelX;\n rightBound = (currentPixelX + nextPixelX) / 2;\n leftBound = currentPixelX - (rightBound - currentPixelX);\n } else {\n return 20;\n }\n\n return Math.max(1, rightBound - leftBound);\n}\n\n/**\n * Computes the pixel left/right bounds of one bar segment within a group.\n * @param barRelativeIdx - zero-based index among bar-only series\n * @param bandwidth - total pixel width for the bar group\n * @param centerPixelX - pixel X of the bar group centre\n * @param barCount - total bar series count (lines excluded)\n */\nexport function calculateBarSegmentBounds(\n barRelativeIdx: number,\n bandwidth: number,\n centerPixelX: number,\n barCount: number,\n): { left: number; right: number } {\n const count = Math.max(1, barCount);\n const segmentWidth = bandwidth / count;\n const segmentLeft = centerPixelX - bandwidth / 2 + barRelativeIdx * segmentWidth;\n return {\n left: segmentLeft,\n right: segmentLeft + segmentWidth,\n };\n}\n\nexport function calculateBarYBounds(\n visualYBottom: number,\n visualYTop: number,\n chart: EChartsInstance,\n): { top: number; bottom: number } | null {\n try {\n const bottomPixel = chart.convertToPixel('grid', [0, visualYBottom]);\n const topPixel = chart.convertToPixel('grid', [0, visualYTop]);\n\n if (!bottomPixel || !topPixel || bottomPixel[1] === undefined || topPixel[1] === undefined) return null;\n\n // Y increases downward in pixels; min/max normalizes the mapping for negative bar values.\n return {\n top: Math.min(topPixel[1], bottomPixel[1]),\n bottom: Math.max(topPixel[1], bottomPixel[1]),\n };\n } catch {\n return null;\n }\n}\n"],"names":["TOOLTIP_MAX_WIDTH","TOOLTIP_MAX_HEIGHT","TOOLTIP_MIN_WIDTH","TOOLTIP_BG_COLOR_FALLBACK","TOOLTIP_PADDING","assembleTransform","mousePos","pinnedPos","tooltipHeight","tooltipWidth","containerElement","undefined","cursorPaddingX","cursorPaddingY","plotCanvas","x","effectiveHeight","effectiveWidth","page","y","cursorBaseY","containerRect","getBoundingClientRect","left","scrollLeft","top","scrollTop","containerBottom","scrollHeight","Math","max","window","innerHeight","scrollY","innerWidth","PINNED_TOOLTIP_Z_INDEX","getTooltipStyles","theme","maxHeight","adjustedMaxHeight","minWidth","maxWidth","padding","position","backgroundColor","palette","designSystem","grey","borderRadius","color","fontSize","visibility","opacity","transition","zIndex","tooltip","overflow","overflowY","getPixelXFromGrid","timestamp","chart","pixelCoords","convertToPixel","calculateVisualYForSeries","seriesIdx","yValue","seriesMapping","stackTotals","currentSeries","stackId","stack","stackIdStr","toString","currentTotal","get","newTotal","set","calculateBarBandwidth","sortedTimestamps","currentIdx","indexOf","prevTimestamp","nextTimestamp","length","currentPixelX","leftBound","rightBound","prevPixelX","nextPixelX","calculateBarSegmentBounds","barRelativeIdx","bandwidth","centerPixelX","barCount","count","segmentWidth","segmentLeft","right","calculateBarYBounds","visualYBottom","visualYTop","bottomPixel","topPixel","min","bottom"],"mappings":"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"}
@@ -15,4 +15,5 @@ export * from './time-series-data';
15
15
  export * from './trace-data';
16
16
  export * from './types';
17
17
  export * from './text';
18
+ export * from './url';
18
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAaA,cAAc,QAAQ,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,UAAU,CAAC;AACzB,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAaA,cAAc,QAAQ,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,UAAU,CAAC;AACzB,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,OAAO,CAAC"}
@@ -27,5 +27,6 @@ export * from "./time-series-data.js";
27
27
  export * from "./trace-data.js";
28
28
  export * from "./types.js";
29
29
  export * from "./text.js";
30
+ export * from "./url.js";
30
31
 
31
32
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["// Copyright The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nexport * from './axis';\nexport * from './browser-storage';\nexport * from './chart-actions';\nexport * from './combine-sx';\nexport * from './data-field-interpolation';\nexport * from './format';\nexport * from './request-interpolation';\nexport * from './selection-interpolation';\nexport * from './theme-gen';\nexport * from './variable-interpolation';\nexport * from './value-mapping';\nexport * from './regexp';\nexport * from './transform-data';\nexport * from './time-series-data';\nexport * from './trace-data';\nexport * from './types';\nexport * from './text';\n"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAEjC,cAAc,YAAS;AACvB,cAAc,uBAAoB;AAClC,cAAc,qBAAkB;AAChC,cAAc,kBAAe;AAC7B,cAAc,gCAA6B;AAC3C,cAAc,cAAW;AACzB,cAAc,6BAA0B;AACxC,cAAc,+BAA4B;AAC1C,cAAc,iBAAc;AAC5B,cAAc,8BAA2B;AACzC,cAAc,qBAAkB;AAChC,cAAc,cAAW;AACzB,cAAc,sBAAmB;AACjC,cAAc,wBAAqB;AACnC,cAAc,kBAAe;AAC7B,cAAc,aAAU;AACxB,cAAc,YAAS"}
1
+ {"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["// Copyright The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nexport * from './axis';\nexport * from './browser-storage';\nexport * from './chart-actions';\nexport * from './combine-sx';\nexport * from './data-field-interpolation';\nexport * from './format';\nexport * from './request-interpolation';\nexport * from './selection-interpolation';\nexport * from './theme-gen';\nexport * from './variable-interpolation';\nexport * from './value-mapping';\nexport * from './regexp';\nexport * from './transform-data';\nexport * from './time-series-data';\nexport * from './trace-data';\nexport * from './types';\nexport * from './text';\nexport * from './url';\n"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAEjC,cAAc,YAAS;AACvB,cAAc,uBAAoB;AAClC,cAAc,qBAAkB;AAChC,cAAc,kBAAe;AAC7B,cAAc,gCAA6B;AAC3C,cAAc,cAAW;AACzB,cAAc,6BAA0B;AACxC,cAAc,+BAA4B;AAC1C,cAAc,iBAAc;AAC5B,cAAc,8BAA2B;AACzC,cAAc,qBAAkB;AAChC,cAAc,cAAW;AACzB,cAAc,sBAAmB;AACjC,cAAc,wBAAqB;AACnC,cAAc,kBAAe;AAC7B,cAAc,aAAU;AACxB,cAAc,YAAS;AACvB,cAAc,WAAQ"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Returns true when the URL is relative or uses one of the allowed schemes (`http:`, `https:`, `mailto:`, `tel:`,
3
+ * `sms:`, `slack:`, `msteams:`, `vscode:`, `vscode-insiders:`, `zoommtg:`),
4
+ * i.e. when it is safe to use as the `href` of a link built from user-provided data.
5
+ * Any other scheme, such as `javascript:`, `data:` or `vbscript:`, is considered unsafe.
6
+ *
7
+ * The check relies on the WHATWG URL parser, the one browsers use to resolve an `href`,
8
+ * so obfuscated schemes (mixed case, leading whitespace or control characters, tabs or newlines inside the scheme, ...)
9
+ * are normalized before being verified.
10
+ */
11
+ export declare function isSafeUrl(url: string): boolean;
12
+ /**
13
+ * Returns the URL when it is safe to use as the `href` of a link (see {@link isSafeUrl}), undefined otherwise.
14
+ * It must be applied to the final URL, after any interpolation (variables, data fields, ...).
15
+ */
16
+ export declare function sanitizeUrl(url: string | undefined): string | undefined;
17
+ //# sourceMappingURL=url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAgCA;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAM9C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAKvE"}
@@ -0,0 +1,56 @@
1
+ // Copyright The Perses Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ // None of these schemes can execute a script in the current origin. Besides the web and communication schemes, it
14
+ // includes the deep links of applications commonly used from dashboards, which are handed off to the operating system.
15
+ // Keep it aligned with the list of the Perses API (internal/api/validate/link.go in perses/perses).
16
+ const SAFE_URL_PROTOCOLS = new Set([
17
+ 'http:',
18
+ 'https:',
19
+ 'mailto:',
20
+ 'tel:',
21
+ 'sms:',
22
+ 'slack:',
23
+ 'msteams:',
24
+ 'vscode:',
25
+ 'vscode-insiders:',
26
+ 'zoommtg:'
27
+ ]);
28
+ // Only used to resolve relative URLs, so that they are parsed the way a browser resolves them against the page.
29
+ const RELATIVE_URL_BASE = 'http://perses.local';
30
+ /**
31
+ * Returns true when the URL is relative or uses one of the allowed schemes (`http:`, `https:`, `mailto:`, `tel:`,
32
+ * `sms:`, `slack:`, `msteams:`, `vscode:`, `vscode-insiders:`, `zoommtg:`),
33
+ * i.e. when it is safe to use as the `href` of a link built from user-provided data.
34
+ * Any other scheme, such as `javascript:`, `data:` or `vbscript:`, is considered unsafe.
35
+ *
36
+ * The check relies on the WHATWG URL parser, the one browsers use to resolve an `href`,
37
+ * so obfuscated schemes (mixed case, leading whitespace or control characters, tabs or newlines inside the scheme, ...)
38
+ * are normalized before being verified.
39
+ */ export function isSafeUrl(url) {
40
+ try {
41
+ return SAFE_URL_PROTOCOLS.has(new URL(url, RELATIVE_URL_BASE).protocol);
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
46
+ /**
47
+ * Returns the URL when it is safe to use as the `href` of a link (see {@link isSafeUrl}), undefined otherwise.
48
+ * It must be applied to the final URL, after any interpolation (variables, data fields, ...).
49
+ */ export function sanitizeUrl(url) {
50
+ if (url === undefined || !isSafeUrl(url)) {
51
+ return undefined;
52
+ }
53
+ return url;
54
+ }
55
+
56
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/url.ts"],"sourcesContent":["// Copyright The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// None of these schemes can execute a script in the current origin. Besides the web and communication schemes, it\n// includes the deep links of applications commonly used from dashboards, which are handed off to the operating system.\n// Keep it aligned with the list of the Perses API (internal/api/validate/link.go in perses/perses).\nconst SAFE_URL_PROTOCOLS = new Set([\n 'http:',\n 'https:',\n 'mailto:',\n 'tel:',\n 'sms:',\n 'slack:',\n 'msteams:',\n 'vscode:',\n 'vscode-insiders:',\n 'zoommtg:',\n]);\n\n// Only used to resolve relative URLs, so that they are parsed the way a browser resolves them against the page.\nconst RELATIVE_URL_BASE = 'http://perses.local';\n\n/**\n * Returns true when the URL is relative or uses one of the allowed schemes (`http:`, `https:`, `mailto:`, `tel:`,\n * `sms:`, `slack:`, `msteams:`, `vscode:`, `vscode-insiders:`, `zoommtg:`),\n * i.e. when it is safe to use as the `href` of a link built from user-provided data.\n * Any other scheme, such as `javascript:`, `data:` or `vbscript:`, is considered unsafe.\n *\n * The check relies on the WHATWG URL parser, the one browsers use to resolve an `href`,\n * so obfuscated schemes (mixed case, leading whitespace or control characters, tabs or newlines inside the scheme, ...)\n * are normalized before being verified.\n */\nexport function isSafeUrl(url: string): boolean {\n try {\n return SAFE_URL_PROTOCOLS.has(new URL(url, RELATIVE_URL_BASE).protocol);\n } catch {\n return false;\n }\n}\n\n/**\n * Returns the URL when it is safe to use as the `href` of a link (see {@link isSafeUrl}), undefined otherwise.\n * It must be applied to the final URL, after any interpolation (variables, data fields, ...).\n */\nexport function sanitizeUrl(url: string | undefined): string | undefined {\n if (url === undefined || !isSafeUrl(url)) {\n return undefined;\n }\n return url;\n}\n"],"names":["SAFE_URL_PROTOCOLS","Set","RELATIVE_URL_BASE","isSafeUrl","url","has","URL","protocol","sanitizeUrl","undefined"],"mappings":"AAAA,+BAA+B;AAC/B,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAEjC,kHAAkH;AAClH,uHAAuH;AACvH,oGAAoG;AACpG,MAAMA,qBAAqB,IAAIC,IAAI;IACjC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,gHAAgH;AAChH,MAAMC,oBAAoB;AAE1B;;;;;;;;;CASC,GACD,OAAO,SAASC,UAAUC,GAAW;IACnC,IAAI;QACF,OAAOJ,mBAAmBK,GAAG,CAAC,IAAIC,IAAIF,KAAKF,mBAAmBK,QAAQ;IACxE,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA;;;CAGC,GACD,OAAO,SAASC,YAAYJ,GAAuB;IACjD,IAAIA,QAAQK,aAAa,CAACN,UAAUC,MAAM;QACxC,OAAOK;IACT;IACA,OAAOL;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perses-dev/components",
3
- "version": "0.56.0-beta.0",
3
+ "version": "0.56.0-beta.1",
4
4
  "description": "Common UI components used across Perses features",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/perses/perses/blob/main/README.md",
@@ -15,6 +15,7 @@
15
15
  "module": "dist/index.js",
16
16
  "main": "dist/index.js",
17
17
  "types": "dist/index.d.ts",
18
+ "sideEffects": false,
18
19
  "scripts": {
19
20
  "clean": "rimraf dist/",
20
21
  "build": "concurrently \"npm:build:*\"",
@@ -37,14 +38,14 @@
37
38
  "@fontsource/inter": "^5.0.0",
38
39
  "@mui/x-date-pickers": "^7.23.1",
39
40
  "@perses-dev/spec": "0.3.0",
40
- "@perses-dev/client": "0.56.0-beta.0",
41
+ "@perses-dev/client": "0.56.0-beta.1",
41
42
  "numbro": "^2.3.6",
42
43
  "@tanstack/match-sorter-utils": "^8.19.4",
43
44
  "@tanstack/react-table": "^9.2.8",
44
45
  "@uiw/react-codemirror": "^4.19.1",
45
46
  "date-fns": "^4.1.0",
46
- "echarts": "5.5.0",
47
- "immer": "^10.1.1",
47
+ "echarts": "6.1.0",
48
+ "immer": "^11.2.9",
48
49
  "lodash": "^4.17.21",
49
50
  "mathjs": "^10.6.4",
50
51
  "mdi-material-ui": "^7.9.2",